diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6577201e..627eb3d6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: - name: Build run: npm run docs:build - - uses: actions/configure-pages@v4 + - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 with: path: docs/.vitepress/dist diff --git a/bin/prepare-examples.js b/bin/prepare-examples.js index 952ccb10..a46b0c4a 100644 --- a/bin/prepare-examples.js +++ b/bin/prepare-examples.js @@ -14,6 +14,7 @@ import { parse } from 'yaml'; const specDir = join('specs', 'yaml'); const esmTestDir = join('specs', 'esm'); const jsonTestDir = join('specs', 'json'); +const tsTestDir = join('specs', 'ts'); const docsDir = 'docs'; const yamlDocsDir = join(docsDir, 'public', 'specs', 'yaml'); @@ -21,6 +22,13 @@ const jsonDocsDir = join(docsDir, 'public', 'specs', 'json'); const esmDocsDir = join(docsDir, 'public', 'specs', 'esm'); const exampleDir = join(docsDir, 'examples'); +const specToTS = spec => { + return `import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = ${JSON.stringify(spec, 0, 2)}; +`; +} + const files = await Promise.allSettled((await readdir(specDir)) .filter(name => extname(name) === '.yaml') .map(async name => { @@ -43,6 +51,11 @@ const files = await Promise.allSettled((await readdir(specDir)) resolve(jsonTestDir, `${base}.json`), JSON.stringify(ast.toJSON(), 0, 2) ), + // write TS JSON spec to tests + writeFile( + resolve(tsTestDir, `${base}.ts`), + specToTS(spec) + ), // copy YAML file to docs copyFile(file, resolve(yamlDocsDir, `${base}.yaml`)), // write JSON spec to docs diff --git a/docs/public/specs/esm/axes.js b/docs/public/specs/esm/axes.js index b2cee2bd..f268f001 100644 --- a/docs/public/specs/esm/axes.js +++ b/docs/public/specs/esm/axes.js @@ -1,7 +1,7 @@ import * as vg from "@uwdata/vgplot"; export default vg.plot( - vg.gridY({strokeDasharray: [0.75, 2], strokeOpacity: 1}), + vg.gridY({strokeDasharray: "0.75 2", strokeOpacity: 1}), vg.axisY({anchor: "left", tickSize: 0, dx: 38, dy: -4, lineAnchor: "bottom"}), vg.axisY({ anchor: "right", diff --git a/docs/public/specs/esm/seattle-temp.js b/docs/public/specs/esm/seattle-temp.js index f7bc2e82..3aade9b7 100644 --- a/docs/public/specs/esm/seattle-temp.js +++ b/docs/public/specs/esm/seattle-temp.js @@ -29,7 +29,7 @@ export default vg.plot( ), vg.ruleY( [15], - {strokeOpacity: 0.5, strokeDasharray: [5, 5]} + {strokeOpacity: 0.5, strokeDasharray: "5 5"} ), vg.xTickFormat("%b"), vg.yLabel("Temperature Range (°C)"), diff --git a/docs/public/specs/esm/voronoi.js b/docs/public/specs/esm/voronoi.js index 39d967c6..579e9cb9 100644 --- a/docs/public/specs/esm/voronoi.js +++ b/docs/public/specs/esm/voronoi.js @@ -17,7 +17,6 @@ export default vg.vconcat( stroke: "white", strokeWidth: 1, strokeOpacity: 0.5, - inset: 1, fill: "species", fillOpacity: 0.2 } diff --git a/docs/public/specs/json/axes.json b/docs/public/specs/json/axes.json index 8dd85d9d..9078bba6 100644 --- a/docs/public/specs/json/axes.json +++ b/docs/public/specs/json/axes.json @@ -6,10 +6,7 @@ "plot": [ { "mark": "gridY", - "strokeDasharray": [ - 0.75, - 2 - ], + "strokeDasharray": "0.75 2", "strokeOpacity": 1 }, { diff --git a/docs/public/specs/json/seattle-temp.json b/docs/public/specs/json/seattle-temp.json index 2f6aac79..d90c9c6d 100644 --- a/docs/public/specs/json/seattle-temp.json +++ b/docs/public/specs/json/seattle-temp.json @@ -51,10 +51,7 @@ 15 ], "strokeOpacity": 0.5, - "strokeDasharray": [ - 5, - 5 - ] + "strokeDasharray": "5 5" } ], "xTickFormat": "%b", diff --git a/docs/public/specs/json/voronoi.json b/docs/public/specs/json/voronoi.json index 7372723f..7730fc6b 100644 --- a/docs/public/specs/json/voronoi.json +++ b/docs/public/specs/json/voronoi.json @@ -26,7 +26,6 @@ "stroke": "white", "strokeWidth": 1, "strokeOpacity": 0.5, - "inset": 1, "fill": "species", "fillOpacity": 0.2 }, diff --git a/docs/public/specs/yaml/axes.yaml b/docs/public/specs/yaml/axes.yaml index 3514c2fa..9cdd0a08 100644 --- a/docs/public/specs/yaml/axes.yaml +++ b/docs/public/specs/yaml/axes.yaml @@ -5,7 +5,7 @@ meta: scale attributes such as `xAxis`, `yGrid`, etc. Just add data! plot: - mark: gridY - strokeDasharray: [0.75, 2] # dashed + strokeDasharray: 0.75 2 # dashed strokeOpacity: 1 # opaque - mark: axisY anchor: left diff --git a/docs/public/specs/yaml/seattle-temp.yaml b/docs/public/specs/yaml/seattle-temp.yaml index d9d013ad..43658195 100644 --- a/docs/public/specs/yaml/seattle-temp.yaml +++ b/docs/public/specs/yaml/seattle-temp.yaml @@ -26,7 +26,7 @@ plot: - mark: ruleY data: [15] strokeOpacity: 0.5 - strokeDasharray: [5, 5] + strokeDasharray: 5 5 xTickFormat: '%b' yLabel: Temperature Range (°C) width: 680 diff --git a/docs/public/specs/yaml/voronoi.yaml b/docs/public/specs/yaml/voronoi.yaml index 0f86eaa7..aa8b2ef0 100644 --- a/docs/public/specs/yaml/voronoi.yaml +++ b/docs/public/specs/yaml/voronoi.yaml @@ -19,7 +19,6 @@ vconcat: stroke: white strokeWidth: 1 strokeOpacity: 0.5 - inset: 1 fill: species fillOpacity: 0.2 - mark: hull diff --git a/package-lock.json b/package-lock.json index 3b6f9d5c..b41eaad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,13 @@ "eslint": "^8.57.0", "eslint-plugin-jsdoc": "^48.2.1", "lerna": "^8.1.2", - "mocha": "^10.3.0", + "mocha": "^10.4.0", "nodemon": "^3.1.0", "rimraf": "^5.0.5", "timezone-mock": "^1.3.6", - "typescript": "^5.3.3", - "vite": "^5.1.6", - "vitepress": "1.0.0-rc.45", + "typescript": "^5.4.3", + "vite": "^5.2.6", + "vitepress": "1.0.1", "yaml": "^2.4.1" } }, @@ -99,132 +99,151 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", - "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz", + "integrity": "sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.22.1" + "@algolia/cache-common": "4.23.2" } }, "node_modules/@algolia/cache-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", - "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.2.tgz", + "integrity": "sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==", "dev": true }, "node_modules/@algolia/cache-in-memory": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", - "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz", + "integrity": "sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.22.1" + "@algolia/cache-common": "4.23.2" } }, "node_modules/@algolia/client-account": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", - "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.2.tgz", + "integrity": "sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ==", "dev": true, "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/transporter": "4.22.1" + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-analytics": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", - "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.2.tgz", + "integrity": "sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", - "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.2.tgz", + "integrity": "sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-personalization": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", - "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.2.tgz", + "integrity": "sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg==", "dev": true, "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" + "@algolia/client-common": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-search": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", - "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.2.tgz", + "integrity": "sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" + "@algolia/client-common": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/logger-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", - "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.2.tgz", + "integrity": "sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==", "dev": true }, "node_modules/@algolia/logger-console": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", - "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.2.tgz", + "integrity": "sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.22.1" + "@algolia/logger-common": "4.23.2" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.2.tgz", + "integrity": "sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.2", + "@algolia/cache-common": "4.23.2", + "@algolia/cache-in-memory": "4.23.2", + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/logger-console": "4.23.2", + "@algolia/requester-browser-xhr": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/requester-node-http": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", - "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz", + "integrity": "sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.22.1" + "@algolia/requester-common": "4.23.2" } }, "node_modules/@algolia/requester-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", - "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.2.tgz", + "integrity": "sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==", "dev": true }, "node_modules/@algolia/requester-node-http": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", - "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz", + "integrity": "sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.22.1" + "@algolia/requester-common": "4.23.2" } }, "node_modules/@algolia/transporter": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", - "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.2.tgz", + "integrity": "sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/requester-common": "4.22.1" + "@algolia/cache-common": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/requester-common": "4.23.2" } }, "node_modules/@anywidget/types": { @@ -436,30 +455,30 @@ } }, "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", "dev": true }, "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", "dev": true, "dependencies": { - "@docsearch/react": "3.5.2", + "@docsearch/react": "3.6.0", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", "dev": true, "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", + "@docsearch/css": "3.6.0", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -2445,9 +2464,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", + "integrity": "sha512-4C4UERETjXpC4WpBXDbkgNVgHyWfG3B/NKY46e7w5H134UDOFqUJKpsLm0UYmuupW+aJmRgeScrDNfvZ5WV80A==", "cpu": [ "arm" ], @@ -2458,9 +2477,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.1.tgz", + "integrity": "sha512-TrTaFJ9pXgfXEiJKQ3yQRelpQFqgRzVR9it8DbeRzG0RX7mKUy0bqhCFsgevwXLJepQKTnLl95TnPGf9T9AMOA==", "cpu": [ "arm64" ], @@ -2471,9 +2490,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.1.tgz", + "integrity": "sha512-fz7jN6ahTI3cKzDO2otQuybts5cyu0feymg0bjvYCBrZQ8tSgE8pc0sSNEuGvifrQJWiwx9F05BowihmLxeQKw==", "cpu": [ "arm64" ], @@ -2484,9 +2503,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.1.tgz", + "integrity": "sha512-WTvdz7SLMlJpektdrnWRUN9C0N2qNHwNbWpNo0a3Tod3gb9leX+yrYdCeB7VV36OtoyiPAivl7/xZ3G1z5h20g==", "cpu": [ "x64" ], @@ -2497,9 +2516,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.1.tgz", + "integrity": "sha512-dBHQl+7wZzBYcIF6o4k2XkAfwP2ks1mYW2q/Gzv9n39uDcDiAGDqEyml08OdY0BIct0yLSPkDTqn4i6czpBLLw==", "cpu": [ "arm" ], @@ -2510,9 +2529,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.1.tgz", + "integrity": "sha512-bur4JOxvYxfrAmocRJIW0SADs3QdEYK6TQ7dTNz6Z4/lySeu3Z1H/+tl0a4qDYv0bCdBpUYM0sYa/X+9ZqgfSQ==", "cpu": [ "arm64" ], @@ -2523,9 +2542,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.1.tgz", + "integrity": "sha512-ssp77SjcDIUSoUyj7DU7/5iwM4ZEluY+N8umtCT9nBRs3u045t0KkW02LTyHouHDomnMXaXSZcCSr2bdMK63kA==", "cpu": [ "arm64" ], @@ -2536,9 +2555,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.1.tgz", + "integrity": "sha512-Jv1DkIvwEPAb+v25/Unrnnq9BO3F5cbFPT821n3S5litkz+O5NuXuNhqtPx5KtcwOTtaqkTsO+IVzJOsxd11aQ==", "cpu": [ "riscv64" ], @@ -2548,10 +2567,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.1.tgz", + "integrity": "sha512-U564BrhEfaNChdATQaEODtquCC7Ez+8Hxz1h5MAdMYj0AqD0GA9rHCpElajb/sQcaFL6NXmHc5O+7FXpWMa73Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.1.tgz", + "integrity": "sha512-zGRDulLTeDemR8DFYyFIQ8kMP02xpUsX4IBikc7lwL9PrwR3gWmX2NopqiGlI2ZVWMl15qZeUjumTwpv18N7sQ==", "cpu": [ "x64" ], @@ -2562,9 +2594,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.1.tgz", + "integrity": "sha512-VTk/MveyPdMFkYJJPCkYBw07KcTkGU2hLEyqYMsU4NjiOfzoaDTW9PWGRsNwiOA3qI0k/JQPjkl/4FCK1smskQ==", "cpu": [ "x64" ], @@ -2575,9 +2607,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.1.tgz", + "integrity": "sha512-L+hX8Dtibb02r/OYCsp4sQQIi3ldZkFI0EUkMTDwRfFykXBPptoz/tuuGqEd3bThBSLRWPR6wsixDSgOx/U3Zw==", "cpu": [ "arm64" ], @@ -2588,9 +2620,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.1.tgz", + "integrity": "sha512-+dI2jVPfM5A8zme8riEoNC7UKk0Lzc7jCj/U89cQIrOjrZTCWZl/+IXUeRT2rEZ5j25lnSA9G9H1Ob9azaF/KQ==", "cpu": [ "ia32" ], @@ -2601,9 +2633,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.1.tgz", + "integrity": "sha512-YY1Exxo2viZ/O2dMHuwQvimJ0SqvL+OAWQLLY6rvXavgQKjhQUzn7nc1Dd29gjB5Fqi00nrBWctJBOyfVMIVxw==", "cpu": [ "x64" ], @@ -2614,18 +2646,18 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.7.tgz", - "integrity": "sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.2.1.tgz", + "integrity": "sha512-KaIS0H4EQ3KI2d++TjYqRNgwp8E3M/68e9veR4QtInzA7kKFgcjeiJqb80fuXW+blDy5fmd11PN9g9soz/3ANQ==", "dev": true }, "node_modules/@shikijs/transformers": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.1.7.tgz", - "integrity": "sha512-lXz011ao4+rvweps/9h3CchBfzb1U5OtP5D51Tqc9lQYdLblWMIxQxH6Ybe1GeGINcEVM4goMyPrI0JvlIp4UQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.2.1.tgz", + "integrity": "sha512-H7cVtrdv6BW2kx83t2IQgP5ri1IA50mE3QnzgJ0AvOKCGtCEieXu0JIP3245cgjNLrL+LBwb8DtTXdky1iQL9Q==", "dev": true, "dependencies": { - "shiki": "1.1.7" + "shiki": "1.2.1" } }, "node_modules/@sigstore/bundle": { @@ -3193,21 +3225,21 @@ } }, "node_modules/@vue/devtools-api": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.15.tgz", - "integrity": "sha512-kgEYWosDyWpS1vFSuJNNWUnHkP+VkL3Y+9mw+rf7ex41SwbYL/WdC3KXqAtjiSrEs7r/FrHmUTh0BkINJPFkbA==", + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.24.tgz", + "integrity": "sha512-4dsRfL+7CnRZ9TgkmFNHRc7fKSE6v74GNojwxop+F5lotUTxNDbN7HTYg/vJ7DfJtwKFYGQjMFbHxEDZQ4Sahg==", "dev": true, "dependencies": { - "@vue/devtools-kit": "^7.0.15" + "@vue/devtools-kit": "^7.0.24" } }, "node_modules/@vue/devtools-kit": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.15.tgz", - "integrity": "sha512-dT7OeCe1LUCIhHIb/yRR6Hn+XHh73r1o78onqCrxEKHdoZwBItiIeVnmJZPEUDFstIxfs+tJL231mySk3laTow==", + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.24.tgz", + "integrity": "sha512-6XD4ZRjbnk8XC5IM/GfuqB9O9UlmUU53pybuxg0/xBI9pxQfH3mOu5Gpyb55cG18uMW4c7hdEOgkxwFpUgYIrg==", "dev": true, "dependencies": { - "@vue/devtools-shared": "^7.0.15", + "@vue/devtools-shared": "^7.0.24", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", @@ -3218,9 +3250,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.15.tgz", - "integrity": "sha512-fpfvMVvS7aDgO7x2JPFiTQ1MHcCc63/bE7yTgs278gMBybuO9b3hdiZ/k0Pw1rN+RefaU9yQiFA+5CCFc1D+6w==", + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.24.tgz", + "integrity": "sha512-hocNGlaQuc0s3hLNky64zXQK6DjQcIGEI0TJbizEAH7l5eZ6BCPB3P19ofu9HyUbbIn/PTJWqyLT8clzWqo0Xw==", "dev": true, "dependencies": { "rfdc": "^1.3.1" @@ -3602,25 +3634,26 @@ } }, "node_modules/algoliasearch": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", - "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.22.1", - "@algolia/cache-common": "4.22.1", - "@algolia/cache-in-memory": "4.22.1", - "@algolia/client-account": "4.22.1", - "@algolia/client-analytics": "4.22.1", - "@algolia/client-common": "4.22.1", - "@algolia/client-personalization": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/logger-console": "4.22.1", - "@algolia/requester-browser-xhr": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/requester-node-http": "4.22.1", - "@algolia/transporter": "4.22.1" + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.2.tgz", + "integrity": "sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.2", + "@algolia/cache-common": "4.23.2", + "@algolia/cache-in-memory": "4.23.2", + "@algolia/client-account": "4.23.2", + "@algolia/client-analytics": "4.23.2", + "@algolia/client-common": "4.23.2", + "@algolia/client-personalization": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/logger-console": "4.23.2", + "@algolia/recommend": "4.23.2", + "@algolia/requester-browser-xhr": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/requester-node-http": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/ansi-colors": { @@ -8597,9 +8630,9 @@ } }, "node_modules/mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -10388,9 +10421,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -10409,16 +10442,16 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.19.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz", - "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==", + "version": "10.20.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", + "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", "dev": true, "funding": { "type": "opencollective", @@ -11172,9 +11205,9 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.1.tgz", + "integrity": "sha512-hFi+fU132IvJ2ZuihN56dwgpltpmLZHZWsx27rMCTZ2sYwrqlgL5sECGy1eeV2lAihD8EzChBVVhsXci0wD4Tg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -11187,19 +11220,20 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.13.1", + "@rollup/rollup-android-arm64": "4.13.1", + "@rollup/rollup-darwin-arm64": "4.13.1", + "@rollup/rollup-darwin-x64": "4.13.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.1", + "@rollup/rollup-linux-arm64-gnu": "4.13.1", + "@rollup/rollup-linux-arm64-musl": "4.13.1", + "@rollup/rollup-linux-riscv64-gnu": "4.13.1", + "@rollup/rollup-linux-s390x-gnu": "4.13.1", + "@rollup/rollup-linux-x64-gnu": "4.13.1", + "@rollup/rollup-linux-x64-musl": "4.13.1", + "@rollup/rollup-win32-arm64-msvc": "4.13.1", + "@rollup/rollup-win32-ia32-msvc": "4.13.1", + "@rollup/rollup-win32-x64-msvc": "4.13.1", "fsevents": "~2.3.2" } }, @@ -11363,12 +11397,12 @@ } }, "node_modules/shiki": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.7.tgz", - "integrity": "sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.2.1.tgz", + "integrity": "sha512-u+XW6o0vCkUNlneZb914dLO+AayEIwK5tI62WeS//R5HIXBFiYaj/Hc5xcq27Yh83Grr4JbNtUBV8W6zyK4hWg==", "dev": true, "dependencies": { - "@shikijs/core": "1.1.7" + "@shikijs/core": "1.2.1" } }, "node_modules/signal-exit": { @@ -11687,9 +11721,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -12477,9 +12511,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -13096,14 +13130,14 @@ } }, "node_modules/vite": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", - "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -13150,440 +13184,34 @@ } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, "node_modules/vitepress": { - "version": "1.0.0-rc.45", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.45.tgz", - "integrity": "sha512-/OiYsu5UKpQKA2c0BAZkfyywjfauDjvXyv6Mo4Ra57m5n4Bxg1HgUGoth1CLH2vwUbR/BHvDA9zOM0RDvgeSVQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.1.tgz", + "integrity": "sha512-eNr5pOBppYUUjEhv8S0S2t9Tv95LQ6mMeHj6ivaGwfHxpov70Vduuwl/QQMDRznKDSaP0WKV7a82Pb4JVOaqEw==", "dev": true, "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@shikijs/core": "^1.1.5", - "@shikijs/transformers": "^1.1.5", + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.2.0", + "@shikijs/transformers": "^1.2.0", "@types/markdown-it": "^13.0.7", "@vitejs/plugin-vue": "^5.0.4", - "@vue/devtools-api": "^7.0.14", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", + "@vue/devtools-api": "^7.0.16", + "@vueuse/core": "^10.9.0", + "@vueuse/integrations": "^10.9.0", "focus-trap": "^7.5.4", "mark.js": "8.11.1", "minisearch": "^6.3.0", - "shiki": "^1.1.5", - "vite": "^5.1.3", - "vue": "^3.4.19" + "shiki": "^1.2.0", + "vite": "^5.2.2", + "vue": "^3.4.21" }, "bin": { "vitepress": "bin/vitepress.js" }, "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.35" + "markdown-it-mathjax3": "^4", + "postcss": "^8" }, "peerDependenciesMeta": { "markdown-it-mathjax3": { diff --git a/package.json b/package.json index 81926157..e15b8539 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,13 @@ "eslint": "^8.57.0", "eslint-plugin-jsdoc": "^48.2.1", "lerna": "^8.1.2", - "mocha": "^10.3.0", + "mocha": "^10.4.0", "nodemon": "^3.1.0", "rimraf": "^5.0.5", "timezone-mock": "^1.3.6", - "typescript": "^5.3.3", - "vite": "^5.1.6", - "vitepress": "1.0.0-rc.45", + "typescript": "^5.4.3", + "vite": "^5.2.6", + "vitepress": "1.0.1", "yaml": "^2.4.1" }, "workspaces": [ diff --git a/packages/core/src/Coordinator.js b/packages/core/src/Coordinator.js index bef80c37..43a6129a 100644 --- a/packages/core/src/Coordinator.js +++ b/packages/core/src/Coordinator.js @@ -8,8 +8,7 @@ let _instance; /** * Set or retrieve the coordinator instance. - * - * @param {Coordinator} instance the coordinator instance to set + * @param {Coordinator} [instance] the coordinator instance to set * @returns {Coordinator} the coordinator instance */ export function coordinator(instance) { @@ -42,7 +41,15 @@ export class Coordinator { return this._logger; } - configure({ cache = true, consolidate = true, indexes = true }) { + /** + * Set configuration options for this coordinator. + * @param {object} [options] Configration options. + * @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching. + * @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation. + * @param {boolean|object} [options.indexes=true] Boolean flag to enable/disable + * automatic data cube indexes or an index options object. + */ + configure({ cache = true, consolidate = true, indexes = true } = {}) { this.manager.cache(cache); this.manager.consolidate(consolidate); this.indexes = indexes; @@ -116,7 +123,6 @@ export class Coordinator { /** * Connect a client to the coordinator. - * * @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect */ async connect(client) { diff --git a/packages/core/src/MosaicClient.js b/packages/core/src/MosaicClient.js index c90e4c32..a7a471f6 100644 --- a/packages/core/src/MosaicClient.js +++ b/packages/core/src/MosaicClient.js @@ -48,6 +48,7 @@ export class MosaicClient { /** * Return an array of fields queried by this client. + * @returns {object[]|null} The fields to retrieve info for. */ fields() { return null; @@ -55,21 +56,25 @@ export class MosaicClient { /** * Called by the coordinator to set the field info for this client. + * @param {*} info The field info result. * @returns {this} */ - fieldInfo() { + fieldInfo(info) { // eslint-disable-line no-unused-vars return this; } /** * Return a query specifying the data needed by this client. + * @param {*} [filter] The filtering criteria to apply in the query. + * @returns {*} The client query */ - query() { + query(filter) { // eslint-disable-line no-unused-vars return null; } /** * Called by the coordinator to inform the client that a query is pending. + * @returns {this} */ queryPending() { return this; @@ -77,16 +82,17 @@ export class MosaicClient { /** * Called by the coordinator to return a query result. - * - * @param {*} data the query result + * @param {*} data The query result. * @returns {this} */ - queryResult() { + queryResult(data) { // eslint-disable-line no-unused-vars return this; } /** * Called by the coordinator to report a query execution error. + * @param {*} error + * @returns {this} */ queryError(error) { console.error(error); @@ -116,6 +122,8 @@ export class MosaicClient { /** * Requests a client update. * For example to (re-)render an interface component. + * + * @returns {this | Promise} */ update() { return this; diff --git a/packages/core/src/Param.js b/packages/core/src/Param.js index d3b650c6..ecacabfa 100644 --- a/packages/core/src/Param.js +++ b/packages/core/src/Param.js @@ -4,7 +4,7 @@ import { distinct } from './util/distinct.js'; /** * Test if a value is a Param instance. * @param {*} x The value to test. - * @returns {boolean} True if the input is a Param, false otherwise. + * @returns {x is Param} True if the input is a Param, false otherwise. */ export function isParam(x) { return x instanceof Param; @@ -43,7 +43,9 @@ export class Param extends AsyncDispatch { static array(values) { if (values.some(v => isParam(v))) { const p = new Param(); - const update = () => p.update(values.map(v => isParam(v) ? v.value : v)); + const update = () => { + p.update(values.map(v => isParam(v) ? v.value : v)); + }; update(); values.forEach(v => isParam(v) ? v.addEventListener('value', update) : 0); return p; diff --git a/packages/core/src/QueryConsolidator.js b/packages/core/src/QueryConsolidator.js index f332ba93..39891f35 100644 --- a/packages/core/src/QueryConsolidator.js +++ b/packages/core/src/QueryConsolidator.js @@ -5,6 +5,7 @@ function wait(callback) { const method = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : typeof setImmediate !== 'undefined' ? setImmediate : setTimeout; + // @ts-ignore return method(callback); } @@ -82,7 +83,9 @@ function consolidationKey(query, cache) { const sql = `${query}`; if (query instanceof Query && !cache.get(sql)) { if ( + // @ts-ignore query.orderby().length || query.where().length || + // @ts-ignore query.qualify().length || query.having().length ) { // do not try to analyze if query includes clauses @@ -97,9 +100,12 @@ function consolidationKey(query, cache) { // queries may refer to *derived* columns as group by criteria // we resolve these against the true grouping expressions const groupby = query.groupby(); + // @ts-ignore if (groupby.length) { const map = {}; // expression map (as -> expr) + // @ts-ignore query.select().forEach(({ as, expr }) => map[as] = expr); + // @ts-ignore q.$groupby(groupby.map(e => (e instanceof Ref && map[e.column]) || e)); } diff --git a/packages/core/src/Selection.js b/packages/core/src/Selection.js index d1a4655d..b9702ddb 100644 --- a/packages/core/src/Selection.js +++ b/packages/core/src/Selection.js @@ -4,7 +4,7 @@ import { Param } from './Param.js'; /** * Test if a value is a Selection instance. * @param {*} x The value to test. - * @returns {boolean} True if the input is a Selection, false otherwise. + * @returns {x is Selection} True if the input is a Selection, false otherwise. */ export function isSelection(x) { return x instanceof Selection; @@ -76,7 +76,7 @@ export class Selection extends Param { /** * Create a cloned copy of this Selection instance. - * @returns {this} A clone of this selection. + * @returns {Selection} A clone of this selection. */ clone() { const s = new Selection(this._resolver); @@ -88,7 +88,7 @@ export class Selection extends Param { * Create a clone of this Selection with clauses corresponding * to the provided source removed. * @param {*} source The clause source to remove. - * @returns {this} A cloned and updated Selection. + * @returns {Selection} A cloned and updated Selection. */ remove(source) { const s = this.clone(); @@ -168,9 +168,9 @@ export class Selection extends Param { * Upon value-typed updates, returns a dispatch queue filter function. * The return value depends on the selection resolution strategy. * @param {string} type The event type. - * @param {*} value The input event value. - * @returns {*} For value-typed events, returns a dispatch queue filter - * function. Otherwise returns null. + * @param {*} value The new event value that will be enqueued. + * @returns {(value: *) => boolean|null} For value-typed events, + * returns a dispatch queue filter function. Otherwise returns null. */ emitQueueFilter(type, value) { return type === 'value' @@ -285,5 +285,6 @@ export class SelectionResolver { const source = value.active?.source; return clauses => clauses.active?.source !== source; } + return null; } } diff --git a/packages/core/src/connectors/wasm.js b/packages/core/src/connectors/wasm.js index d42fcdc6..be550fde 100644 --- a/packages/core/src/connectors/wasm.js +++ b/packages/core/src/connectors/wasm.js @@ -22,7 +22,7 @@ export function wasmConnector(options = {}) { /** * Get the backing DuckDB-WASM instance. * Will lazily initialize DuckDB-WASM if not already loaded. - * @returns {duckdb.AsyncDuckDB} The DuckDB-WASM instance. + * @returns {Promise} The DuckDB-WASM instance. */ async function getDuckDB() { if (!db) await load(); @@ -32,7 +32,7 @@ export function wasmConnector(options = {}) { /** * Get the backing DuckDB-WASM connection. * Will lazily initialize DuckDB-WASM if not already loaded. - * @returns {duckdb.AsyncDuckDBConnection} The DuckDB-WASM connection. + * @returns {Promise} The DuckDB-WASM connection. */ async function getConnection() { if (!con) await load(); diff --git a/packages/core/src/util/AsyncDispatch.js b/packages/core/src/util/AsyncDispatch.js index 1f789586..ad7b3d63 100644 --- a/packages/core/src/util/AsyncDispatch.js +++ b/packages/core/src/util/AsyncDispatch.js @@ -16,7 +16,7 @@ export class AsyncDispatch { /** * Add an event listener callback for the provided event type. * @param {string} type The event type. - * @param {(value: *) => Promise?} callback The event handler + * @param {(value: *) => void | Promise} callback The event handler * callback function to add. If the callback has already been * added for the event type, this method has no effect. */ @@ -35,7 +35,7 @@ export class AsyncDispatch { /** * Remove an event listener callback for the provided event type. * @param {string} type The event type. - * @param {(value: *) => Promise?} callback The event handler + * @param {(value: *) => void | Promise} callback The event handler * callback function to remove. */ removeEventListener(type, callback) { @@ -64,11 +64,12 @@ export class AsyncDispatch { * This default implementation simply returns null, indicating that * any other unemitted event values should be dropped (that is, all * queued events are filtered) + * @param {string} type The event type. * @param {*} value The new event value that will be enqueued. * @returns {(value: *) => boolean|null} A dispatch queue filter * function, or null if all unemitted event values should be filtered. */ - emitQueueFilter() { + emitQueueFilter(type, value) { // eslint-disable-line no-unused-vars // removes all pending items return null; } diff --git a/packages/core/src/util/convert-arrow.js b/packages/core/src/util/convert-arrow.js index f264d35a..3cf5aba2 100644 --- a/packages/core/src/util/convert-arrow.js +++ b/packages/core/src/util/convert-arrow.js @@ -5,7 +5,7 @@ import { DataType } from 'apache-arrow'; * As sometimes multiple Arrow versions may be used simultaneously, * we use a "duck typing" approach and check for a getChild function. * @param {*} values The value to test - * @returns true if the value duck types as Apache Arrow data + * @returns {values is import('apache-arrow').Table} true if the value duck types as Apache Arrow data */ export function isArrowTable(values) { return typeof values?.getChild === 'function'; @@ -28,7 +28,7 @@ export function convertArrowArrayType(type) { * Large integers (BigInt) are converted to Float64 numbers. * Fixed-point decimal values are convert to Float64 numbers. * Otherwise, the default Arrow values are used. - * @param {*} type an Apache Arrow column type + * @param {DataType} type an Apache Arrow column type * @returns a value conversion function */ export function convertArrowValue(type) { @@ -112,10 +112,10 @@ const BASE32 = Array.from( /** * Convert a fixed point decimal value to a double precision number. * Note: if the value is sufficiently large the conversion may be lossy! - * @param {Uint32Array} v a fixed decimal value + * @param {Uint32Array & { signed: boolean }} v a fixed decimal value * @param {number} scale a scale factor, corresponding to the * number of fractional decimal digits in the fixed point value - * @returns the resulting number + * @returns {number} the resulting number */ function decimalToNumber(v, scale) { const n = v.length; diff --git a/packages/core/src/util/query-result.js b/packages/core/src/util/query-result.js index cdccbccc..530c6422 100644 --- a/packages/core/src/util/query-result.js +++ b/packages/core/src/util/query-result.js @@ -2,7 +2,8 @@ export function queryResult() { let resolve; let reject; const p = new Promise((r, e) => { resolve = r; reject = e; }); - p.fulfill = value => (resolve(value), p); - p.reject = err => (reject(err), p); - return p; + return Object.assign(p, { + fulfill: value => (resolve(value), p), + reject: err => (reject(err), p) + }); } diff --git a/packages/inputs/src/Menu.js b/packages/inputs/src/Menu.js index a7ea0214..ed798bbf 100644 --- a/packages/inputs/src/Menu.js +++ b/packages/inputs/src/Menu.js @@ -9,6 +9,10 @@ const isObject = v => { export const menu = options => input(Menu, options); export class Menu extends MosaicClient { + /** + * Create a new Menu instance. + * @param {object} options Options object + */ constructor({ element, filterBy, diff --git a/packages/inputs/src/Search.js b/packages/inputs/src/Search.js index 9f7cef57..cbdfa751 100644 --- a/packages/inputs/src/Search.js +++ b/packages/inputs/src/Search.js @@ -10,6 +10,10 @@ let _id = 0; export const search = options => input(Search, options); export class Search extends MosaicClient { + /** + * Create a new Search instance. + * @param {object} options Options object + */ constructor({ element, filterBy, diff --git a/packages/inputs/src/Slider.js b/packages/inputs/src/Slider.js index dcb15cf2..2b3d88b5 100644 --- a/packages/inputs/src/Slider.js +++ b/packages/inputs/src/Slider.js @@ -7,6 +7,10 @@ let _id = 0; export const slider = options => input(Slider, options); export class Slider extends MosaicClient { + /** + * Create a new Slider instance. + * @param {object} options Options object + */ constructor({ element, filterBy, @@ -80,7 +84,7 @@ export class Slider extends MosaicClient { const { min, max } = Array.from(data)[0]; if (this.min == null) this.slider.setAttribute('min', min); if (this.max == null) this.slider.setAttribute('max', max); - if (this.step == null) this.slider.setAttribute('step', (max - min) / 500); + if (this.step == null) this.slider.setAttribute('step', String((max - min) / 500)); return this; } diff --git a/packages/inputs/src/Table.js b/packages/inputs/src/Table.js index 8af4f9b1..e66b28be 100644 --- a/packages/inputs/src/Table.js +++ b/packages/inputs/src/Table.js @@ -8,6 +8,10 @@ let _id = -1; export const table = options => input(Table, options); export class Table extends MosaicClient { + /** + * Create a new Table instance. + * @param {object} options Options object + */ constructor({ element, filterBy, diff --git a/packages/inputs/src/util/format.js b/packages/inputs/src/util/format.js index b5737a84..6a54ea65 100644 --- a/packages/inputs/src/util/format.js +++ b/packages/inputs/src/util/format.js @@ -42,6 +42,9 @@ export function formatDate(date) { // Memoize the last-returned locale. export function localize(f) { - let key = localize, value; - return (locale = 'en') => locale === key ? value : (value = f(key = locale)); + let key = null; + let value; + return (locale = 'en') => locale === key + ? value + : (value = f(key = locale)); } diff --git a/packages/plot/src/interactors/Interval1D.js b/packages/plot/src/interactors/Interval1D.js index 07e45996..a3702dcb 100644 --- a/packages/plot/src/interactors/Interval1D.js +++ b/packages/plot/src/interactors/Interval1D.js @@ -11,7 +11,7 @@ export class Interval1D { constructor(mark, { channel, selection, - field, + field = undefined, pixelSize = 1, peers = true, brush: style diff --git a/packages/plot/src/interactors/Toggle.js b/packages/plot/src/interactors/Toggle.js index 4b6d30c4..a95d8546 100644 --- a/packages/plot/src/interactors/Toggle.js +++ b/packages/plot/src/interactors/Toggle.js @@ -2,8 +2,8 @@ import { and, or, isNotDistinct, literal } from '@uwdata/mosaic-sql'; export class Toggle { /** - * @param {import('@uwdata/mosaic-plot').Mark} mark The mark. - * @param {*} options The options. + * @param {*} mark The mark to interact with. + * @param {*} options The interactor options. */ constructor(mark, { selection, @@ -11,7 +11,6 @@ export class Toggle { peers = true }) { this.value = null; - /** @type {import('@uwdata/mosaic-plot').Mark} */ this.mark = mark; this.selection = selection; this.peers = peers; @@ -56,7 +55,7 @@ export class Toggle { init(svg, selector, accessor) { const { mark, channels, selection } = this; - const { data: { columns } = {} } = mark; + const { data: { columns = {} } = {} } = mark; accessor ??= target => channels.map(c => columns[c.as][target.__data__]); selector ??= `[data-index="${mark.index}"]`; const groups = new Set(svg.querySelectorAll(selector)); diff --git a/packages/plot/src/interactors/util/patchScreenCTM.js b/packages/plot/src/interactors/util/patchScreenCTM.js index e455bc0a..631f74dd 100644 --- a/packages/plot/src/interactors/util/patchScreenCTM.js +++ b/packages/plot/src/interactors/util/patchScreenCTM.js @@ -4,6 +4,8 @@ * even after the node is removed from the DOM. */ export function patchScreenCTM() { + /** @type {SVGGraphicsElement} */ + // @ts-ignore const node = this; const getScreenCTM = node.getScreenCTM; let memo; diff --git a/packages/plot/src/legend.js b/packages/plot/src/legend.js index 1584b402..e1fad8eb 100644 --- a/packages/plot/src/legend.js +++ b/packages/plot/src/legend.js @@ -15,10 +15,11 @@ export class Legend { this.handler = null; this.selection = as; this.field = field; + this.legend = null; this.element = document.createElement('div'); this.element.setAttribute('class', 'legend'); - this.element.value = this; + Object.assign(this.element, { value: this }); } setPlot(plot) { diff --git a/packages/plot/src/marks/ConnectedMark.js b/packages/plot/src/marks/ConnectedMark.js index d409bb73..44247688 100644 --- a/packages/plot/src/marks/ConnectedMark.js +++ b/packages/plot/src/marks/ConnectedMark.js @@ -11,6 +11,11 @@ export class ConnectedMark extends Mark { this.dim = dim; } + /** + * Return a query specifying the data needed by this Mark client. + * @param {*} [filter] The filtering criteria to apply in the query. + * @returns {*} The client query + */ query(filter = []) { const { plot, dim, source } = this; const { optimize = true } = source.options || {}; @@ -28,6 +33,7 @@ export class ConnectedMark extends Mark { const [lo, hi] = filteredExtent(filter, field) || [min, max]; const [expr] = binExpr(this, dim, size, [lo, hi], 1, as); const cols = q.select() + // @ts-ignore .map(c => c.as) .filter(c => c !== as && c !== value); return m4(q, expr, as, value, cols); diff --git a/packages/plot/src/marks/ContourMark.js b/packages/plot/src/marks/ContourMark.js index 9649bc79..07188d59 100644 --- a/packages/plot/src/marks/ContourMark.js +++ b/packages/plot/src/marks/ContourMark.js @@ -13,8 +13,11 @@ export class ContourMark extends Grid2DMark { pixelSize: 2, ...channels }); - handleParam(this, 'thresholds', thresholds, () => { - return this.grids ? this.contours().update() : null + + /** @type {number|number[]} */ + this.thresholds = handleParam(thresholds, value => { + this.thresholds = value; + return this.grids ? this.contours().update() : null; }); } @@ -26,10 +29,13 @@ export class ContourMark extends Grid2DMark { const { bins, densityMap, grids, thresholds, plot } = this; const { numRows, columns } = grids; - let tz = thresholds; - if (!Array.isArray(tz)) { + let t = thresholds; + let tz; + if (Array.isArray(t)) { + tz = t; + } else { const [, hi] = gridDomainContinuous(columns.density); - tz = Array.from({length: tz - 1}, (_, i) => (hi * (i + 1)) / tz); + tz = Array.from({length: t - 1}, (_, i) => (hi * (i + 1)) / t); } if (densityMap.fill || densityMap.stroke) { @@ -52,7 +58,7 @@ export class ContourMark extends Grid2DMark { const contour = contours().size(bins); // generate contours - const data = this.data = Array(numRows * tz.length); + const data = this.contourData = Array(numRows * tz.length); const { density, ...groupby } = columns; const groups = Object.entries(groupby); for (let i = 0, k = 0; i < numRows; ++i) { @@ -72,7 +78,7 @@ export class ContourMark extends Grid2DMark { } plotSpecs() { - const { type, channels, densityMap, data } = this; + const { type, channels, densityMap, contourData: data } = this; const options = {}; for (const c of channels) { const { channel } = c; diff --git a/packages/plot/src/marks/DenseLineMark.js b/packages/plot/src/marks/DenseLineMark.js index 383219ff..bf8cc798 100644 --- a/packages/plot/src/marks/DenseLineMark.js +++ b/packages/plot/src/marks/DenseLineMark.js @@ -8,14 +8,18 @@ export class DenseLineMark extends RasterMark { constructor(source, options) { const { normalize = true, ...rest } = options; super(source, rest); - handleParam(this, 'normalize', normalize); + + /** @type {boolean} */ + this.normalize = handleParam(normalize, value => { + return (this.normalize = value, this.requestUpdate()); + }); } query(filter = []) { - const { channels, normalize, source, binPad } = this; - const [nx, ny] = this.bins = this.binDimensions(this); - const [x] = binExpr(this, 'x', nx, extentX(this, filter), binPad); - const [y] = binExpr(this, 'y', ny, extentY(this, filter), binPad); + const { channels, normalize, source, pad } = this; + const [nx, ny] = this.bins = this.binDimensions(); + const [x] = binExpr(this, 'x', nx, extentX(this, filter), pad); + const [y] = binExpr(this, 'y', ny, extentY(this, filter), pad); const q = Query .from(source.table) diff --git a/packages/plot/src/marks/Density1DMark.js b/packages/plot/src/marks/Density1DMark.js index 5949c4c8..fe3a9a04 100644 --- a/packages/plot/src/marks/Density1DMark.js +++ b/packages/plot/src/marks/Density1DMark.js @@ -16,9 +16,15 @@ export class Density1DMark extends Mark { super(type, source, channels, dim === 'x' ? xext : yext); this.dim = dim; - handleParam(this, 'bins', bins); - handleParam(this, 'bandwidth', bandwidth, () => { - return this.grid ? this.convolve().update() : null + /** @type {number} */ + this.bins = handleParam(bins, value => { + return (this.bins = value, this.requestUpdate()); + }); + + /** @type {number} */ + this.bandwidth = handleParam(bandwidth, value => { + this.bandwidth = value; + return this.grid ? this.convolve().update() : null; }); } diff --git a/packages/plot/src/marks/Grid2DMark.js b/packages/plot/src/marks/Grid2DMark.js index 1d2d7d3d..554f76c0 100644 --- a/packages/plot/src/marks/Grid2DMark.js +++ b/packages/plot/src/marks/Grid2DMark.js @@ -27,21 +27,47 @@ export class Grid2DMark extends Mark { super(type, source, channels, xyext); this.densityMap = densityMap; - handleParam(this, 'bandwidth', bandwidth, () => { + /** @type {number} */ + this.bandwidth = handleParam(bandwidth, value => { + this.bandwidth = value; return this.grids ? this.convolve().update() : null; }); - handleParam(this, 'pixelSize', pixelSize); - handleParam(this, 'interpolate', interpolate); - handleParam(this, 'pad', pad); - handleParam(this, 'width', width); - handleParam(this, 'height', height); + + /** @type {string} */ + this.interpolate = handleParam(interpolate, value => { + return (this.interpolate = value, this.requestUpdate()); + }); + + /** @type {number} */ + this.pixelSize = handleParam(pixelSize, value => { + return (this.pixelSize = value, this.requestUpdate()); + }); + + /** @type {number} */ + this.pad = handleParam(pad, value => { + return (this.pad = value, this.requestUpdate()); + }); + + /** @type {number|undefined} */ + this.width = handleParam(width, value => { + return (this.width = value, this.requestUpdate()); + }); + + /** @type {number|undefined} */ + this.height = handleParam(height, value => { + return (this.height = value, this.requestUpdate()); + }); } + /** + * @param {import('../plot.js').Plot} plot The plot. + * @param {number} index + */ setPlot(plot, index) { const update = () => { if (this.hasFieldInfo()) this.requestUpdate(); }; plot.addAttributeListener('xDomain', update); plot.addAttributeListener('yDomain', update); - return super.setPlot(plot, index); + super.setPlot(plot, index); } get filterIndexable() { @@ -54,7 +80,7 @@ export class Grid2DMark extends Mark { const { interpolate, pad, channels, densityMap, source } = this; const [x0, x1] = this.extentX = extentX(this, filter); const [y0, y1] = this.extentY = extentY(this, filter); - const [nx, ny] = this.bins = this.binDimensions(this); + const [nx, ny] = this.bins = this.binDimensions(); const [x, bx] = binExpr(this, 'x', nx, [x0, x1], pad); const [y, by] = binExpr(this, 'y', ny, [y0, y1], pad); @@ -114,6 +140,9 @@ export class Grid2DMark extends Mark { } } + /** + * @returns {[number, number]} The bin dimensions. + */ binDimensions() { const { plot, pixelSize, width, height } = this; return [ @@ -160,6 +189,7 @@ export class Grid2DMark extends Mark { numRows: grids0.numRows, columns: { ...grids0.columns, + // @ts-ignore [prop]: g.map(grid => dericheConv2d(configX, configY, grid, bins)) } }; @@ -167,10 +197,6 @@ export class Grid2DMark extends Mark { return this; } - - plotSpecs() { - throw new Error('Unimplemented. Use a Grid2D mark subclass.'); - } } /** diff --git a/packages/plot/src/marks/HexbinMark.js b/packages/plot/src/marks/HexbinMark.js index 47e60cb1..d7c9e087 100644 --- a/packages/plot/src/marks/HexbinMark.js +++ b/packages/plot/src/marks/HexbinMark.js @@ -2,12 +2,17 @@ import { Query, isNotNull, sql } from '@uwdata/mosaic-sql'; import { Transient } from '../symbols.js'; import { extentX, extentY, xyext } from './util/extent.js'; import { Mark } from './Mark.js'; +import { handleParam } from './util/handle-param.js'; export class HexbinMark extends Mark { constructor(source, options) { const { type = 'hexagon', binWidth = 20, ...channels } = options; super(type, source, { r: binWidth / 2, clip: true, ...channels }, xyext); - this.binWidth = binWidth; + + /** @type {number} */ + this.binWidth = handleParam(binWidth, value => { + return (this.binWidth = value, this.requestUpdate()); + }); } get filterIndexable() { @@ -39,9 +44,10 @@ export class HexbinMark extends Mark { let x, y; const aggr = new Set; const cols = {}; + let orderby; for (const c of channels) { if (c.channel === 'orderby') { - q.orderby(c.value); // TODO revisit once groupby is added + orderby = c.value; // TODO revisit once groupby is added } else if (c.channel === 'x') { x = c; } else if (c.channel === 'y') { @@ -63,6 +69,8 @@ export class HexbinMark extends Mark { ...cols }).groupby('x', 'y'); + if (orderby) q.orderby(orderby); + // Map x/y channels to screen space const xx = `${xr} * (${x.field} - ${x1}::DOUBLE)`; const yy = `${yr} * (${y2}::DOUBLE - ${y.field})`; diff --git a/packages/plot/src/marks/Mark.js b/packages/plot/src/marks/Mark.js index 91381459..f73c7ad7 100644 --- a/packages/plot/src/marks/Mark.js +++ b/packages/plot/src/marks/Mark.js @@ -64,12 +64,15 @@ export class Mark extends MosaicClient { } } else if (isParamLike(entry)) { if (Array.isArray(entry.columns)) { + // we currently duck-type to having a columns array + // as a check that this is SQLExpression-compatible channels.push(fieldEntry(channel, entry)); params.add(entry); } else { const c = valueEntry(channel, entry.value); channels.push(c); entry.addEventListener('value', value => { + // update immediately, the value is simply passed to Plot c.value = value; return this.update(); }); @@ -86,6 +89,10 @@ export class Mark extends MosaicClient { } } + /** + * @param {import('../plot.js').Plot} plot The plot. + * @param {number} index + */ setPlot(plot, index) { this.plot = plot; this.index = index; @@ -105,7 +112,7 @@ export class Mark extends MosaicClient { return this.channels.find(c => c.channel === channel); } - channelField(channel, { exact } = {}) { + channelField(channel, { exact = false } = {}) { const c = exact ? this.channel(channel) : this.channels.find(c => c.channel.startsWith(channel)); @@ -141,6 +148,11 @@ export class Mark extends MosaicClient { return this; } + /** + * Return a query specifying the data needed by this Mark client. + * @param {*} [filter] The filtering criteria to apply in the query. + * @returns {*} The client query + */ query(filter = []) { if (this.hasOwnData()) return null; const { channels, source: { table } } = this; @@ -154,8 +166,6 @@ export class Mark extends MosaicClient { /** * Provide query result data to the mark. - * @param {import('apache-arrow').Table} data The backing mark data. - * @returns {this} */ queryResult(data) { this.data = toDataColumns(data); @@ -166,8 +176,13 @@ export class Mark extends MosaicClient { return this.plot.update(this); } + /** + * Generate an array of Plot mark specifications. + * @returns {object[]} + */ plotSpecs() { const { type, detail, channels } = this; + // @ts-ignore const { numRows: length, values, columns } = this.data || {}; // populate plot specification options @@ -213,7 +228,7 @@ export function channelOption(c, columns) { * @param {*} table the table to query. * @param {*} skip an optional array of channels to skip. * Mark subclasses can skip channels that require special handling. - * @returns a Query instance + * @returns {Query} a Query instance */ export function markQuery(channels, table, skip = []) { const q = Query.from({ source: table }); diff --git a/packages/plot/src/marks/RasterMark.js b/packages/plot/src/marks/RasterMark.js index b9f3fb35..16b10bfa 100644 --- a/packages/plot/src/marks/RasterMark.js +++ b/packages/plot/src/marks/RasterMark.js @@ -20,6 +20,7 @@ import { Fixed, Transient } from '../symbols.js'; export class RasterMark extends Grid2DMark { constructor(source, options) { super('image', source, options); + this.image = null; } setPlot(plot, index) { diff --git a/packages/plot/src/marks/RasterTileMark.js b/packages/plot/src/marks/RasterTileMark.js index fe252019..16c36006 100644 --- a/packages/plot/src/marks/RasterTileMark.js +++ b/packages/plot/src/marks/RasterTileMark.js @@ -10,6 +10,7 @@ export class RasterTileMark extends Grid2DMark { constructor(source, options) { const { origin = [0, 0], dim = 'xy', ...markOptions } = options; super('image', source, markOptions); + this.image = null; // TODO: make part of data source instead of options? this.origin = origin; @@ -34,15 +35,15 @@ export class RasterTileMark extends Grid2DMark { } tileQuery(extent) { - const { binType, binPad, channels, densityMap, source } = this; + const { interpolate, pad, channels, densityMap, source } = this; const [[x0, x1], [y0, y1]] = extent; const [nx, ny] = this.bins; - const [x, bx] = binExpr(this, 'x', nx, [x0, x1], binPad); - const [y, by] = binExpr(this, 'y', ny, [y0, y1], binPad); + const [x, bx] = binExpr(this, 'x', nx, [x0, x1], pad); + const [y, by] = binExpr(this, 'y', ny, [y0, y1], pad); // with padded bins, include the entire domain extent // if the bins are flush, exclude the extent max - const bounds = binPad + const bounds = pad ? [isBetween(bx, [+x0, +x1]), isBetween(by, [+y0, +y1])] : [lte(+x0, bx), lt(bx, +x1), lte(+y0, by), lt(by, +y1)]; @@ -83,7 +84,7 @@ export class RasterTileMark extends Grid2DMark { } // generate grid binning query - if (binType === 'linear') { + if (interpolate === 'linear') { if (aggr.length > 1) { throw new Error('Linear binning not applicable to multiple aggregates.'); } @@ -102,14 +103,14 @@ export class RasterTileMark extends Grid2DMark { if (this.prefetch) mc.cancel(this.prefetch); // get view extent info - const { binPad, tileX, tileY, origin: [tx, ty] } = this; - const [m, n] = this.bins = this.binDimensions(this); + const { pad, tileX, tileY, origin: [tx, ty] } = this; + const [m, n] = this.bins = this.binDimensions(); const [x0, x1] = extentX(this, this._filter); const [y0, y1] = extentY(this, this._filter); const xspan = x1 - x0; const yspan = y1 - y0; - const xx = Math.floor((x0 - tx) * (m - binPad) / xspan); - const yy = Math.floor((y0 - ty) * (n - binPad) / yspan); + const xx = Math.floor((x0 - tx) * (m - pad) / xspan); + const yy = Math.floor((y0 - ty) * (n - pad) / yspan); const tileExtent = (i, j) => [ [tx + i * xspan, tx + (i + 1) * xspan], @@ -155,7 +156,11 @@ export class RasterTileMark extends Grid2DMark { // wait for tile queries to complete, then update const tiles = await Promise.all(queries); - this.grids = [{ density: processTiles(m, n, xx, yy, coords, tiles) }]; + const density = processTiles(m, n, xx, yy, coords, tiles); + this.grids0 = { + numRows: density.length, + columns: { density: [density] } + }; this.convolve().update(); } diff --git a/packages/plot/src/marks/RegressionMark.js b/packages/plot/src/marks/RegressionMark.js index 84bf91c1..e8bbdc3b 100644 --- a/packages/plot/src/marks/RegressionMark.js +++ b/packages/plot/src/marks/RegressionMark.js @@ -13,11 +13,18 @@ export class RegressionMark extends Mark { constructor(source, options) { const { ci = 0.95, precision = 4, ...channels } = options; super('line', source, channels); - const update = () => { - return this.modelFit ? this.confidenceBand().update() : null - }; - handleParam(this, 'ci', ci, update); - handleParam(this, 'precision', precision, update); + + const update = () => this.modelFit ? this.confidenceBand().update() : null; + + /** @type {number} */ + this.ci = handleParam(ci, value => { + return (this.ci = value, update()); + }); + + /** @type {number} */ + this.precision = handleParam(precision, value => { + return (this.precision = value, update()); + }); } query(filter = []) { diff --git a/packages/plot/src/marks/util/grid.js b/packages/plot/src/marks/util/grid.js index 9d623f8d..433994bc 100644 --- a/packages/plot/src/marks/util/grid.js +++ b/packages/plot/src/marks/util/grid.js @@ -1,22 +1,30 @@ import { InternSet, ascending } from 'd3'; +/** + * @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray + * | Int16Array | Uint16Array | Int32Array | Uint32Array + * | Float32Array | Float64Array + * } Arrayish - an Array or TypedArray + */ + /** * Generate a new array with designated size and type. * @param {number} size The size of the array - * @param {ArrayLike} [proto] A prototype object of the desired array type. + * @param {Arrayish} [proto] A prototype object of the desired array type. * This may be a typed array or standard array (the default). - * @returns {ArrayLike} The generated array. + * @returns {Arrayish} The generated array. */ export function array(size, proto = []) { + // @ts-ignore return new proto.constructor(size); } /** * Create a 1D grid for the given sample values * @param {number} size The grid size. - * @param {ArrayLike} index The grid indices for sample points. - * @param {ArrayLike} value The sample point values. - * @returns {ArrayLike} The generated value grid. + * @param {Arrayish} index The grid indices for sample points. + * @param {Arrayish} value The sample point values. + * @returns {Arrayish} The generated value grid. */ export function grid1d(size, index, value) { const G = array(size, value); @@ -32,14 +40,17 @@ export function grid1d(size, index, value) { * Can handle multiple grids and groupby values per output row. * @param {number} w The grid width. * @param {number} h The grid height. - * @param {ArrayLike} index The grid indices for sample points. + * @param {Arrayish} index The grid indices for sample points. * An index value is an integer of the form (y * w + x). - * @param {ArrayLike} columns Named column arrays with sample point values. + * @param {Record} columns Named column arrays with sample point values. * @param {string[]} aggregates The names of aggregate columns to grid. * @param {string[]} groupby The names of additional columns to group by. * @param {function} [interpolate] A grid interpolation function. * By default sample values are directly copied to output grid arrays. - * @returns {object} Named column arrays of generated grid values. + * @returns {{ + * numRows: number; + * columns: { [key:string]: Arrayish } + * }} Named column arrays of generated grid values. */ export function grid2d(w, h, index, columns, aggregates, groupby, interpolate) { const numRows = index.length; @@ -87,6 +98,7 @@ export function grid2d(w, h, index, columns, aggregates, groupby, interpolate) { }); } + // @ts-ignore return { numRows: cells.length, columns: result }; } diff --git a/packages/plot/src/marks/util/handle-param.js b/packages/plot/src/marks/util/handle-param.js index e00bb285..bf6ca91d 100644 --- a/packages/plot/src/marks/util/handle-param.js +++ b/packages/plot/src/marks/util/handle-param.js @@ -1,14 +1,13 @@ import { isParam } from '@uwdata/mosaic-core'; -export function handleParam(client, key, param, update) { - if (isParam(param)) { - update = update || (() => client.requestUpdate()); - param.addEventListener('value', value => { - client[key] = value; - return update(); - }); - client[key] = param.value; - } else { - client[key] = param; - } +/** + * Utility to check if a value is a Param, and if so, bind a listener. + * @param {*} value A potentially Param-typed value. + * @param {(value: *) => Promise|void} update Update callback + * @returns the input value or (if a Param) the current Param value. + */ +export function handleParam(value, update) { + return isParam(value) + ? (value.addEventListener('value', update), value.value) + : value; } diff --git a/packages/plot/src/marks/util/stats.js b/packages/plot/src/marks/util/stats.js index e4fee82f..779a5eab 100644 --- a/packages/plot/src/marks/util/stats.js +++ b/packages/plot/src/marks/util/stats.js @@ -20,6 +20,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/** + * ibetainv function + * @param {number} p + * @param {number} a + * @param {number} b + * @returns {number} + */ export function ibetainv(p, a, b) { var EPS = 1e-8; var a1 = a - 1; @@ -60,11 +67,18 @@ export function ibetainv(p, a, b) { return x; } +/** + * ibeta function + * @param {number} x + * @param {number} a + * @param {number} b + * @returns {number} + */ export function ibeta(x, a, b) { // Factors in front of the continued fraction. var bt = x === 0 || x === 1 ? 0 : Math.exp(gammaln(a + b) - gammaln(a) - gammaln(b) + a * Math.log(x) + b * Math.log(1 - x)); - if (x < 0 || x > 1) return false; + if (x < 0 || x > 1) return 0; if (x < (a + 1) / (a + b + 2)) // Use continued fraction directly. return (bt * betacf(x, a, b)) / a; @@ -72,6 +86,13 @@ export function ibeta(x, a, b) { return 1 - (bt * betacf(1 - x, b, a)) / b; } +/** + * betacf function + * @param {number} x + * @param {number} a + * @param {number} b + * @returns {number} + */ export function betacf(x, a, b) { var fpmin = 1e-30; var m = 1; @@ -112,6 +133,11 @@ export function betacf(x, a, b) { return h; } +/** + * gammaln function + * @param {number} x + * @returns {number} + */ export function gammaln(x) { var j = 0; var cof = [ @@ -126,6 +152,12 @@ export function gammaln(x) { return Math.log((2.506628274631 * ser) / xx) - tmp; } +/** + * qt function + * @param {number} p + * @param {number} dof + * @returns {number} + */ export function qt(p, dof) { var x = ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5); x = Math.sqrt((dof * (1 - x)) / x); diff --git a/packages/plot/src/marks/util/to-data-columns.js b/packages/plot/src/marks/util/to-data-columns.js index 050ad5b2..3758e07c 100644 --- a/packages/plot/src/marks/util/to-data-columns.js +++ b/packages/plot/src/marks/util/to-data-columns.js @@ -1,9 +1,23 @@ import { convertArrowColumn, isArrowTable } from '@uwdata/mosaic-core'; +/** + * @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray + * | Int16Array | Uint16Array | Int32Array | Uint32Array + * | Float32Array | Float64Array + * } Arrayish - an Array or TypedArray + */ + +/** + * @typedef { + * | { numRows: number, columns: Record } + * | { numRows: number, values: Arrayish; } + * } DataColumns +*/ + /** * Convert input data to a set of column arrays. * @param {any} data The input data. - * @returns An object of named column arrays. + * @returns {DataColumns} An object with named column arrays. */ export function toDataColumns(data) { return isArrowTable(data) @@ -14,7 +28,7 @@ export function toDataColumns(data) { /** * Convert an Arrow table to a set of column arrays. * @param {import('apache-arrow').Table} data An Apache Arrow Table. - * @returns An object of named column arrays. + * @returns {DataColumns} An object with named column arrays. */ function arrowToColumns(data) { const { numRows, numCols, schema: { fields } } = data; @@ -38,7 +52,7 @@ function arrowToColumns(data) { * We use the keys of the first object as the column names. * Otherwise, use a special "values" array. * @param {object[]} data An array of data objects. - * @returns An object of named column arrays. + * @returns {DataColumns} An object with named column arrays. */ function arrayToColumns(data) { const numRows = data.length; diff --git a/packages/plot/src/plot-attributes.js b/packages/plot/src/plot-attributes.js index 4ac581ba..9fcafc12 100644 --- a/packages/plot/src/plot-attributes.js +++ b/packages/plot/src/plot-attributes.js @@ -14,7 +14,6 @@ export const attributeMap = new Map([ ['grid', 'grid'], ['label', 'label'], ['padding', 'padding'], - ['round', 'round'], ['xScale', 'x.type'], ['xDomain', 'x.domain'], ['xRange', 'x.range'], diff --git a/packages/plot/src/plot.js b/packages/plot/src/plot.js index 1fbf3a9d..d2706faa 100644 --- a/packages/plot/src/plot.js +++ b/packages/plot/src/plot.js @@ -72,10 +72,20 @@ export class Plot { this.synch.resolve(); } + /** + * @param {string} name The attribute to return. + * @returns {*} The value of the attribute. + */ getAttribute(name) { return this.attributes[name]; } + /** + * @param {string} name The name of the attribute to set. + * @param {*} value The value to set. + * @param {{silent: boolean}} [options] Options for setting the attribute. + * @returns {boolean} whether the value changed. + */ setAttribute(name, value, options) { if (distinct(this.attributes[name], value)) { if (value === undefined) { @@ -91,6 +101,11 @@ export class Plot { return false; } + /** + * @param {string} name The attribute name. + * @param {*} callback The function to call when the attribute changes. + * @returns {this} + */ addAttributeListener(name, callback) { const map = this.listeners || (this.listeners = new Map); if (!map.has(name)) map.set(name, new Set); @@ -98,6 +113,11 @@ export class Plot { return this; } + /** + * @param {string} name The attribute name. + * @param {*} callback The function to call when the attribute changes. + * @returns {void} + */ removeAttributeListener(name, callback) { return this.listeners?.get(name)?.delete(callback); } diff --git a/packages/spec/jsconfig.json b/packages/spec/jsconfig.json new file mode 100644 index 00000000..22324029 --- /dev/null +++ b/packages/spec/jsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src/**/*", "../../specs/ts/*.ts"], + "compilerOptions": { + "checkJs": true, + "noEmit": true, + "noImplicitAny": false, + "module": "node16", + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/packages/spec/package.json b/packages/spec/package.json index f56100c0..1debe001 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -16,21 +16,22 @@ "module": "src/index.js", "jsdelivr": "dist/mosaic-spec.min.js", "unpkg": "dist/mosaic-spec.min.js", + "types": "dist/types/index.d.ts", "repository": { "type": "git", "url": "https://github.com/uwdata/mosaic.git" }, "scripts": { "prebuild": "rimraf dist && mkdir dist", - "build": "node ../../esbuild.js mosaic-spec", + "build": "tsc && node ../../esbuild.js mosaic-spec", "lint": "eslint src test --ext .js", - "test": "mocha 'test/**/*-test.js'", + "pretest": "tsc", + "test": "mocha 'test/**/*-test.js' && tsc -p jsconfig.json", "prepublishOnly": "npm run test && npm run lint && npm run build" }, "dependencies": { "@uwdata/mosaic-core": "^0.7.1", "@uwdata/mosaic-sql": "^0.7.0", - "@uwdata/vgplot": "^0.7.1", - "isoformat": "^0.2.1" + "@uwdata/vgplot": "^0.7.1" } } diff --git a/packages/spec/src/ast-to-dom.js b/packages/spec/src/ast-to-dom.js index 332cef2c..b1774eb0 100644 --- a/packages/spec/src/ast-to-dom.js +++ b/packages/spec/src/ast-to-dom.js @@ -1,15 +1,21 @@ +import { Param, Selection } from '@uwdata/mosaic-core'; import { createAPIContext, loadExtension } from '@uwdata/vgplot'; import { SpecNode } from './ast/SpecNode.js'; import { resolveExtensions } from './config/extensions.js'; import { error } from './util.js'; /** - * Generate running web application (DOM content) for a Mosaic spec AST. + * Generate a running web application (DOM content) for a Mosaic spec AST. * @param {SpecNode} ast Mosaic AST root node. * @param {object} [options] Instantiation options. * @param {string} [options.baseURL] The base URL for loading data files. - * @returns {object} An object with the resulting DOM element, and - * a map of named parameters (Param and Selection instances). + * @param {any[]} [options.plotDefaults] Array of default plot attributes. + * @param {Map} [options.params] A map of predefined Params/Selections. + * @returns {Promise<{ + * element: HTMLElement | SVGSVGElement; +* params: Map; +* }>} A Promise to an object with the resulting + * DOM element, and a map of named parameters (Param and Selection instances). */ export async function astToDOM(ast, options) { const { data, params, plotDefaults } = ast; diff --git a/packages/spec/src/ast-to-esm.js b/packages/spec/src/ast-to-esm.js index d91b293c..aab8de5b 100644 --- a/packages/spec/src/ast-to-esm.js +++ b/packages/spec/src/ast-to-esm.js @@ -12,9 +12,9 @@ import { error, isArray, isObject, isString, toArray, toParamRef } from './util. * If undefined, no connector code is generated. * @param {string} [options.namespace='vg'] The vgplot API namespace object. * @param {number} [options.depth=0] The starting indentation depth. - * @param {Map} [options.imports] A Map of ESM imports to - * include in generated code. Keys are packages (e.g., '@uwdata/vgplot') - * and values indicate what to import (e.g., '* as vg'). + * @param {Map} [options.imports] A Map of ESM + * imports to include in generated code. Keys are packages (e.g., + * '@uwdata/vgplot') and values indicate what to import (e.g., '* as vg'). * @param {string|string[]} [options.preamble] Code to include after imports. * @returns {string} Generated ESM code using the vgplot API. */ @@ -29,6 +29,7 @@ export function astToESM(ast, options = {}) { importsCode.push( isString(methods) ? `import ${methods} from "${pkg}";` + // @ts-ignore : `import { ${methods.join(', ')} } from "${pkg}";` ); } @@ -107,12 +108,26 @@ export function astToESM(ast, options = {}) { } export class CodegenContext { + /** + * Create a new code generator context. + * @param {object} [options] Code generation options. + * @param {*} [options.plotDefaults] Default attributes to apply to all plots. + * @param {string} [options.baseURL] The base URL for loading data files. + * @param {string} [options.connector] A database connector to initialize. + * Valid values are 'wasm', 'socket', and 'rest'. + * If undefined, no connector code is generated. + * @param {string} [options.namespace='vg'] The vgplot API namespace object. + * @param {number} [options.depth=0] The starting indentation depth. + * @param {Map} [options.imports] A Map of ESM + * imports to include in generated code. Keys are packages (e.g., + * '@uwdata/vgplot') and values indicate what to import (e.g., '* as vg'). + */ constructor({ - plotDefaults = null, + plotDefaults = undefined, namespace = 'vg', - connector = null, + connector = undefined, imports = new Map([['@uwdata/vgplot', '* as vg']]), - baseURL = null, + baseURL = undefined, depth = 0 } = {}) { this.plotDefaults = plotDefaults; @@ -125,9 +140,11 @@ export class CodegenContext { addImport(pkg, method) { if (!this.imports.has(pkg)) { - this.imports.set(pkg, []); + this.imports.set(pkg, [method]); + } else { + // @ts-ignore + this.imports.get(pkg).push(method); } - this.imports.get(pkg).push(method); } setImports(pkg, all) { diff --git a/packages/spec/src/ast/ASTNode.js b/packages/spec/src/ast/ASTNode.js index c8081be6..6161291e 100644 --- a/packages/spec/src/ast/ASTNode.js +++ b/packages/spec/src/ast/ASTNode.js @@ -1,18 +1,39 @@ +/** + * Abstract base class for Mosaic spec AST nodes. + */ export class ASTNode { constructor(type, children = null) { + /** @type {string} */ this.type = type; + /** @type {ASTNode[] | null} */ this.children = children; } - instantiate(/* ctx */) { + /** + * Instantiate this AST node to use in a live web application. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {*} The instantiated value of this node. + */ + instantiate(ctx) { // eslint-disable-line no-unused-vars + // @ts-ignore throw Error('instantiate not implemented'); } - codegen(/* ctx */) { + /** + * Generate ESM code for this AST node. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated ESM code for the node. + */ + codegen(ctx) { // eslint-disable-line no-unused-vars + // @ts-ignore return Error('codegen not implemented'); } + /** + * @returns {*} This AST node in JSON specification format. + */ toJSON() { + // @ts-ignore return Error('toJSON not implemented'); } } diff --git a/packages/spec/src/ast/DataNode.js b/packages/spec/src/ast/DataNode.js index abfef9b1..889b812b 100644 --- a/packages/spec/src/ast/DataNode.js +++ b/packages/spec/src/ast/DataNode.js @@ -10,6 +10,7 @@ export const CSV_DATA = 'csv'; export const JSON_DATA = 'json'; export const SPATIAL_DATA = 'spatial'; +// @ts-ignore const dataFormats = new Map([ [TABLE_DATA, parseTableData], [PARQUET_DATA, parseParquetData], @@ -18,16 +19,40 @@ const dataFormats = new Map([ [SPATIAL_DATA, parseSpatialData] ]); +/** + * Parse a data definition spec. + * @param {string} name The name of the dataset + * @param {import('../spec/Data.js').DataDefinition} spec The data definition spec. + * @param {import('../parse-spec.js').ParseContext} ctx The parser context. + * @returns {DataNode} a parsed data definition AST node + */ export function parseData(name, spec, ctx) { - spec = resolveDataSpec(spec); - if (dataFormats.has(spec.type)) { - const parse = dataFormats.get(spec.type); - return parse(name, spec, ctx); + const def = resolveDataSpec(spec); + if (dataFormats.has(def.type)) { + const parse = dataFormats.get(def.type); + return parse(name, def, ctx); } else { ctx.error(`Unrecognized data format type.`, spec); } } +function resolveDataSpec(spec) { + if (isArray(spec)) spec = { type: 'json', data: spec }; + if (isString(spec)) spec = { type: 'table', query: spec }; + return { ...spec, type: inferType(spec) }; +} + +function inferType(spec) { + return spec.type + || fileExtension(spec.file) + || 'table'; +} + +function fileExtension(file) { + const idx = file?.lastIndexOf('.'); + return idx > 0 ? file.slice(idx + 1) : null; +} + function parseTableData(name, spec, ctx) { // eslint-disable-next-line no-unused-vars const { query, type, ...options } = spec; @@ -61,23 +86,6 @@ function parseSpatialData(name, spec, ctx) { return new SpatialDataNode(name, file, parseOptions(options, ctx)); } -function resolveDataSpec(spec) { - if (isArray(spec)) spec = { type: 'json', data: spec }; - if (isString(spec)) spec = { type: 'table', query: spec }; - return { ...spec, type: inferType(spec) }; -} - -function inferType(spec) { - return spec.type - || fileExtension(spec.file) - || 'table'; -} - -function fileExtension(file) { - const idx = file?.lastIndexOf('.'); - return idx > 0 ? file.slice(idx + 1) : null; -} - function resolveFileURL(file, baseURL) { return baseURL ? new URL(file, baseURL).toString() : file; } @@ -100,19 +108,39 @@ export class QueryDataNode extends DataNode { super(name, format); } + /** + * Instantiate a table creation query. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {string|void} The instantiated query. + */ instantiateQuery(ctx) { ctx.error('instantiateQuery not implemented'); } + /** + * Code generate a table creation query. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated query code. + */ codegenQuery(ctx) { ctx.error('codegenQuery not implemented'); } + /** + * Instantiate this AST node to use in a live web application. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {*} The instantiated value of this node. + */ instantiate(ctx) { const query = this.instantiateQuery(ctx); if (query) return query; } + /** + * Generate ESM code for this AST node. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated ESM code for the node. + */ codegen(ctx) { const query = this.codegenQuery(ctx); if (query) return query; @@ -126,6 +154,11 @@ export class TableDataNode extends QueryDataNode { this.options = options; } + /** + * Instantiate a table creation query. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {string|void} The instantiated query. + */ instantiateQuery(ctx) { const { name, query, options } = this; if (query) { @@ -133,6 +166,11 @@ export class TableDataNode extends QueryDataNode { } } + /** + * Code generate a table creation query. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated query code. + */ codegenQuery(ctx) { const { name, query, options } = this; if (query) { @@ -154,6 +192,11 @@ export class FileDataNode extends QueryDataNode { this.options = options; } + /** + * Instantiate a table creation query. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {string|void} The instantiated query. + */ instantiateQuery(ctx) { const { name, method, file, options } = this; const url = resolveFileURL(file, ctx.baseURL); @@ -161,6 +204,11 @@ export class FileDataNode extends QueryDataNode { return ctx.api[method](name, url, opt); } + /** + * Code generate a table creation query. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated query code. + */ codegenQuery(ctx) { const { name, method, file, options } = this; const url = resolveFileURL(file, ctx.baseURL); @@ -205,11 +253,21 @@ export class LiteralJSONDataNode extends QueryDataNode { this.options = options; } + /** + * Instantiate a table creation query. + * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context. + * @returns {string|void} The instantiated query. + */ instantiateQuery(ctx) { const { name, data, options } = this; return ctx.api.loadObjects(name, data, options.instantiate(ctx)); } + /** + * Code generate a table creation query. + * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context. + * @returns {string|void} The generated query code. + */ codegenQuery(ctx) { const { name, data, options } = this; const opt = options ? ',' + options.codegen(ctx) : ''; diff --git a/packages/spec/src/ast/ParamNode.js b/packages/spec/src/ast/ParamNode.js index de077279..ffcb3eaa 100644 --- a/packages/spec/src/ast/ParamNode.js +++ b/packages/spec/src/ast/ParamNode.js @@ -1,5 +1,4 @@ -import { parse as isoparse } from 'isoformat'; -import { isArray, isObject } from '../util.js'; +import { isArray, isObject, isoparse } from '../util.js'; import { ASTNode } from './ASTNode.js'; import { CROSSFILTER, INTERSECT, PARAM, SINGLE, UNION, VALUE } from '../constants.js'; import { SelectionNode } from './SelectionNode.js'; diff --git a/packages/spec/src/config/inputs.js b/packages/spec/src/config/inputs.js index a606e269..de0995a0 100644 --- a/packages/spec/src/config/inputs.js +++ b/packages/spec/src/config/inputs.js @@ -1,5 +1,6 @@ /** * Set of input widget type names. + * @returns {Set} */ export function inputNames(overrides = []) { return new Set([ diff --git a/packages/spec/src/config/plots.js b/packages/spec/src/config/plots.js index 69abb9d9..4ea215c5 100644 --- a/packages/spec/src/config/plots.js +++ b/packages/spec/src/config/plots.js @@ -19,6 +19,7 @@ export function plotNames({ /** * Names of attribute directive functions. + * @returns {Set} */ export function plotAttributeNames(overrides = []) { return new Set([ @@ -29,6 +30,7 @@ export function plotAttributeNames(overrides = []) { /** * Names interactor directive functions. + * @returns {Set} */ export function plotInteractorNames(overrides = []) { return new Set([ @@ -39,6 +41,7 @@ export function plotInteractorNames(overrides = []) { /** * Names of legend directive functions. + * @returns {Set} */ export function plotLegendNames(overrides = []) { return new Set([ @@ -49,6 +52,7 @@ export function plotLegendNames(overrides = []) { /** * Names of mark directive functions. + * @returns {Set} */ export function plotMarkNames(overrides = []) { return new Set([ diff --git a/packages/spec/src/index.js b/packages/spec/src/index.js index 1fdb3e45..84fab64c 100644 --- a/packages/spec/src/index.js +++ b/packages/spec/src/index.js @@ -1,3 +1,7 @@ +/** + * @typedef {import('./spec/Spec.js').Spec} Spec A Mosaic declarative specification. + */ + export { astToDOM, InstantiateContext } from './ast-to-dom.js'; export { astToESM, CodegenContext } from './ast-to-esm.js'; export { parseSpec } from './parse-spec.js'; diff --git a/packages/spec/src/parse-spec.js b/packages/spec/src/parse-spec.js index 432dd571..d0e108cc 100644 --- a/packages/spec/src/parse-spec.js +++ b/packages/spec/src/parse-spec.js @@ -5,27 +5,52 @@ import { ParamRefNode } from './ast/ParamRefNode.js'; import { parseAttribute } from './ast/PlotAttributeNode.js'; import { SelectionNode } from './ast/SelectionNode.js'; import { SpecNode } from './ast/SpecNode.js'; - import { componentMap } from './config/components.js'; import { inputNames } from './config/inputs.js'; import { plotNames } from './config/plots.js'; import { transformNames } from './config/transforms.js'; +import { error, paramRef } from './util.js'; -import { error, isString, paramRef } from './util.js'; +/** + * @typedef {{ + * attributes: Set; + * interactors: Set; + * legends: Set; + * marks: Set; + * }} PlotNames names for supported plot elements + */ /** * Parse a Mosaic specification to an AST (abstract syntax tree). - * @param {object|string} spec The input specification as an object - * or JSON string. + * @param {import('./spec/Spec.js').Spec} spec The input specification. * @param {object} [options] Optional parse options object. + * @param {Map} [options.components] Map of component names to parse functions. + * @param {Set} [options.transforms] The names of allowed transform functions. + * @param {Set} [options.inputs] The names of supported input widgets. + * @param {PlotNames} [options.plot] The names of supported plot elements. + * @param {any[]} [options.params] An array of [name, node] pairs of pre-parsed + * Param or Selection AST nodes. + * @param {any[]} [options.datasets] An array of [name, node] pairs of pre-parsed + * dataset definition AST nodes. * @returns {SpecNode} The top-level AST spec node. */ export function parseSpec(spec, options) { - spec = isString(spec) ? JSON.parse(spec) : spec; return new ParseContext(options).parse(spec); } export class ParseContext { + /** + * Create a new parser context. + * @param {object} [options] + * @param {Map} [options.components] Map of component names to parse functions. + * @param {Set} [options.transforms] The names of allowed transform functions. + * @param {Set} [options.inputs] The names of supported input widgets. + * @param {PlotNames} [options.plot] The names of supported plot elements. + * @param {any[]} [options.params] An array of [name, node] pairs of pre-parsed + * Param or Selection AST nodes. + * @param {any[]} [options.datasets] An array of [name, node] pairs of pre-parsed + * dataset definition AST nodes. + */ constructor({ components = componentMap(), transforms = transformNames(), @@ -87,6 +112,14 @@ export class ParseContext { this.error(`Invalid specification.`, spec); } + /** + * Test if a value is param reference, if so, generate a paramter definition + * as needed and return a new ParamRefNode. Otherwise, return a LiteralNode. + * @param {*} value The value to test. + * @param {() => ParamNode | SelectionNode} [makeNode] A Param of Selection AST + * node constructor. + * @returns {ParamRefNode|LiteralNode} An AST node for the input value. + */ maybeParam(value, makeNode = () => new ParamNode) { const { params } = this; const name = paramRef(value); diff --git a/packages/spec/src/spec/Data.ts b/packages/spec/src/spec/Data.ts new file mode 100644 index 00000000..bce90fd8 --- /dev/null +++ b/packages/spec/src/spec/Data.ts @@ -0,0 +1,184 @@ +export interface DataBaseOptions { + /** + * A list of column names to extract upon load. + * Any other columns are omitted. + */ + select?: string[]; + /** + * A filter (WHERE clause) to apply upon load. + * Only rows that pass the filted are included. + */ + where?: string | string[]; + /** + * Flag (default `false`) to generate a view instead of a table. + */ + view?: boolean; + /** + * Flag (default `true`) to generate a temporary view or table. + */ + temp?: boolean; + /** + * Flag (default `true`) to replace an existing table of the same name. + * If `false`, creating a new table with an existing name raises an error. + */ + replace?: boolean; +} + +/** + * A SQL query defining a new temporary database table. + */ +export type DataQuery = string; + +/** + * An inline array of data objects to treat as JSON data. + */ +export type DataArray = object[]; + +/** + * A data definition that loads an external data file. + */ +export interface DataFile { + /** + * The data file to load. If no type option is provided, + * the file suffix must be one of `.csv`, `.json`, or `.parquet`. + */ + file: `${string}.parquet` | `${string}.csv` | `${string}.json`; +} + +/** + * A data definition that queries an existing table. + */ +export interface DataTable extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type: 'table'; + /** + * A SQL query string for the desired table data. + */ + query: string; +} + +/** + * A data definition that loads a parquet file. + */ +export interface DataParquet extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type: 'parquet'; + /** + * The file path for the dataset to load. + */ + file: string; +} + +/** + * A data definition that loads a csv file. + */ +export interface DataCSV extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type: 'csv'; + /** + * The file path for the dataset to load. + */ + file: string; + /** + * The column delimiter string. If not specified, DuckDB will try to infer + * the delimiter automatically. + */ + delimiter?: string, + /** + * The sample size, in table rows, to consult for type inference. + * Set to `-1` to process all rows in the dataset. + */ + sample_size?: number; +} + +/** + * A data definition that loads a supported spatial data file format. + */ +export interface DataSpatial extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type: 'spatial'; + /** + * The file path for the spatial dataset to load. See the DuckDB spatial + * documention [1] for more information on supported file types. + * + * [1] https://duckdb.org/docs/extensions/spatial.html#st_read--read-spatial-data-from-files + */ + file: string; + /** + * The named layer to load from the file. For example, in a TopoJSON file + * the layer is the named object to extract. For Excel spreadsheet files, + * the layer is the name of the worksheet to extract. + */ + layer?: string; +} + +export interface DataJSON extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type: 'json'; + /** + * The file path for the dataset to load. + */ + file: string; +} + +export interface DataJSONObjects extends DataBaseOptions { + /** + * The data source type. One of: + * - `"table"`: Define a new table based on a SQL query. + * - `"csv"`: Load a comma-separated values (CSV) file. + * - `"json"`: Load JavaScript Object Notation (json) data. + * - `"parquet"`: Load a Parquet file. + * - `"spatial"`: Load a spatial data file format via `ST_Read`. + */ + type?: 'json'; + /** + * An array of inline objects in JSON-style format. + */ + data: object[]; +} + +export type DataDefinition = + | DataQuery + | DataArray + | DataFile + | DataTable + | DataParquet + | DataCSV + | DataSpatial + | DataJSON + | DataJSONObjects; diff --git a/packages/spec/src/spec/Expression.ts b/packages/spec/src/spec/Expression.ts new file mode 100644 index 00000000..c2ad8e0a --- /dev/null +++ b/packages/spec/src/spec/Expression.ts @@ -0,0 +1,31 @@ +export type Expression = + | SQLExpression + | AggregateExpression; + +/** A custom SQL expression. */ +export interface SQLExpression { + /** + * A SQL expression string to derive a new column value. + * Embedded Param refrences, such as `$param + 1`, are supported. + * For expressions with aggregate functions, use *agg* instead. + */ + sql: string; + /** + * A label for this expression, for example to label a plot axis. + */ + label?: string; +} + +/** A custom SQL aggregate expression. */ +export interface AggregateExpression { + /** + * A SQL expression string to calculate an aggregate value. + * Embedded Param references, such as `SUM($param + 1)`, are supported. + * For expressions without aggregate functions, use *sql* instead. + */ + agg: string; + /** + * A label for this expression, for example to label a plot axis. + */ + label?: string; +} diff --git a/packages/spec/src/spec/HConcat.ts b/packages/spec/src/spec/HConcat.ts new file mode 100644 index 00000000..9819880d --- /dev/null +++ b/packages/spec/src/spec/HConcat.ts @@ -0,0 +1,9 @@ +import { Component } from './Spec.js'; + +/** An hconcat component. */ +export interface HConcat { + /** + * Horizontally concatenate components in a row layout. + */ + hconcat: Component[]; +} diff --git a/packages/spec/src/spec/HSpace.ts b/packages/spec/src/spec/HSpace.ts new file mode 100644 index 00000000..fe743256 --- /dev/null +++ b/packages/spec/src/spec/HSpace.ts @@ -0,0 +1,9 @@ +/** An hspace component. */ +export interface HSpace { + /** + * Horizontal space to place between components. + * Number values indicate screen pixels. + * String values may use CSS units (em, pt, px, etc). + */ + hspace: number | string; +} diff --git a/packages/spec/src/spec/Input.ts b/packages/spec/src/spec/Input.ts new file mode 100644 index 00000000..e082523e --- /dev/null +++ b/packages/spec/src/spec/Input.ts @@ -0,0 +1,182 @@ +import { ParamRef } from './Param.js'; + +/** A menu input component. */ +export interface Menu { + /** + * A menu input widget. + */ + input: 'menu'; + /** + * The output selection. A selection clause is added for the + * currently selected menu option. + */ + as?: ParamRef; + /** + * The name of a database table to use as a data source for this widget. + * Used in conjunction with the `column` property. + */ + from?: string; + /** + * The name of a database column from which to pull menu options. + * The unique column values are used as menu options. + * Used in conjunction with the `from` property. + */ + column?: string; + /** + * A selection to filter the database table indicated by the `from` property. + */ + filterBy?: ParamRef; + /** + * A text label for this input. + */ + label?: string; + /** + * An array of menu options, as literal values or option objects. + * Option objects have a `value` property and an optional `label` property. + * If no label is provided, the (string-coerced) value is used. + */ + options?: Array; + /** + * The initial selected menu option. + */ + value?: any; +} + +/** A search input component. */ +export interface Search { + /** + * A text search input widget. + */ + input: 'search'; + /** + * The output selection. A selection clause is added for the + * current text search query. + */ + as?: ParamRef; + /** + * The type of text search query to perform. One of: + * - `"contains"` (default): the query string may appear anywhere in the text + * - `"prefix"`: the query string must appear at the start of the text + * - `"suffix"`: the query string must appear at the end of the text + * - `"regexp"`: the query string is a regular expression the text must match + */ + type?: 'contains' | 'prefix' | 'suffix' | 'regexp'; + /** + * The name of a database table to use as an autocomplete data source + * for this widget. Used in conjunction with the `column` property. + */ + from?: string; + /** + * The name of a database column from which to pull valid search results. + * The unique column values are used as search autocomplete values. + * Used in conjunction with the `from` property. + */ + column?: string; + /** + * A selection to filter the database table indicated by the `from` property. + */ + filterBy?: ParamRef; + /** + * A text label for this input. + */ + label?: string; +} + +/** A slider input component. */ +export interface Slider { + /** + * A slider input widget. + */ + input: 'slider'; + /** + * The output selection. A selection clause is added for the + * currently selected slider option. + */ + as?: ParamRef; + /** + * The name of a database table to use as a data source for this widget. + * Used in conjunction with the `column` property. + * The minimum and maximum values of the column determine the slider range. + */ + from?: string; + /** + * The name of a database column whose values determine the slider range. + * Used in conjunction with the `from` property. + * The minimum and maximum values of the column determine the slider range. + */ + column?: string; + /** + * A selection to filter the database table indicated by the `from` property. + */ + filterBy?: ParamRef; + /** + * The minumum slider value. + */ + min?: number; + /** + * The maximum slider value. + */ + max?: number; + /** + * The slider step, the amount to increment between consecutive values. + */ + step?: number; + /** + * A text label for this input. + */ + label?: string; + /** + * The initial slider value. + */ + value?: number; + /** + * The width of the slider in screen pixels. + */ + width?: number; +} + +/** A table grid view component. */ +export interface Table { + /** + * A table grid widget. + */ + input: 'table'; + /** + * The name of a database table to use as a data source for this widget. + */ + from: string; + /** + * A list of column names to include in the table grid. + * If unspecified, all table columns are included. + */ + columns?: string[]; + /** + * A selection to filter the database table indicated by the `from` property. + */ + filterBy?: ParamRef; + /** + * An object of per-column alignment values. + * Column names should be object keys, which map to alignment values. + * Valid alignment values are: `"left"`, `"right"`, `"center"`, and `"justify"`. + * By default, numbers are right-aligned and other values are left-aligned. + */ + align?: { [column: string]: 'left' | 'right' | 'center' | 'justify' }; + /** + * If a number, sets the total width of the table widget, in pixels. + * If an object, provides per-column pixel width values. + * Column names should be object keys, mapped to numeric width values. + */ + width?: number | { [column : string]: number }; + /** + * The maximum width of the table widget, in pixels. + */ + maxWidth?: number; + /** + * The height of the table widget, in pixels. + */ + height?: number; + /** + * The number of rows load in a new batch upon table scroll. + */ + rowBatch?: number; +} diff --git a/packages/spec/src/spec/Param.ts b/packages/spec/src/spec/Param.ts new file mode 100644 index 00000000..7781e2c8 --- /dev/null +++ b/packages/spec/src/spec/Param.ts @@ -0,0 +1,68 @@ +export type ParamRef = `$${string}`; + +/** Base properties shared by Param definitions. */ +export interface ParamBase { + /** + * The type of reactive parameter. One of: + * - `"value"` (default) for a standard `Param` + * - `"intersect"` for a `Selection` that intersects clauses (logical "and") + * - `"union"` for a `Selection` that unions clauses (logical "or") + * - `"single"` for a `Selection` that retains a single clause only + * - `"crossfilter"` for a cross-filtered intersection `Selection` + */ + select?: 'value'; +} + +/** A Param definition. */ +export interface Param extends ParamBase { + /** + * The initial parameter value. + */ + value: ParamValue; +} + +/** A Date-valued Param definition. */ +export interface ParamDate extends ParamBase { + /** + * The initial parameter value as an ISO date/time + * string to be parsed to a Date object. + */ + date: string; +} + +/** Literal Param values. */ +export type ParamLiteral = + | string + | number + | boolean; + +/** Valid Param values. */ +export type ParamValue = + | ParamLiteral + | Array; + +/** A Selection definition. */ +export interface Selection { + /** + * The type of reactive parameter. One of: + * - `"value"` (default) for a standard `Param` + * - `"intersect"` for a `Selection` that intersects clauses (logical "and") + * - `"union"` for a `Selection` that unions clauses (logical "or") + * - `"single"` for a `Selection` that retains a single clause only + * - `"crossfilter"` for a cross-filtered intersection `Selection` + */ + select: 'crossfilter' | 'intersect' | 'single' | 'union'; + + /** + * A flag for cross-filtering, where selections made in a plot filter others + * but not oneself (default `false`, except for `crossfilter` selections). + */ + cross?: boolean; +} + +/** A Param or Selection definition. */ +export type ParamDefinition = + | ParamValue + | Param + | ParamDate + | Selection; diff --git a/packages/spec/src/spec/Plot.ts b/packages/spec/src/spec/Plot.ts new file mode 100644 index 00000000..c37e0684 --- /dev/null +++ b/packages/spec/src/spec/Plot.ts @@ -0,0 +1,15 @@ +import { PlotAttributes } from './PlotAttribute.js'; +import { PlotInteractor } from './PlotInteractor.js'; +import { PlotLegend } from './PlotLegend.js'; +import { PlotMark } from './PlotMark.js'; + +/** A plot component. */ +export interface Plot extends PlotAttributes { + /** + * An array of plot marks, interactors, or legends. + * Marks are graphical elements that make up plot layers. + * Unless otherwise configured, interactors will use the nearest + * previous mark as a basis for which data fields to select. + */ + plot: (PlotMark | PlotInteractor | PlotLegend)[]; +} diff --git a/packages/spec/src/spec/PlotAttribute.ts b/packages/spec/src/spec/PlotAttribute.ts new file mode 100644 index 00000000..c114de8b --- /dev/null +++ b/packages/spec/src/spec/PlotAttribute.ts @@ -0,0 +1,1721 @@ +import { ParamRef } from './Param.js'; +import { + ColorScaleType, ColorScheme, ContinuousScaleType, DiscreteScaleType, + Fixed, Interpolate, Interval, PositionScaleType, ProjectionName +} from './PlotTypes.js'; + +/** Plot attributes. */ +export interface PlotAttributes { + /** + * A unique name for the plot. The name is used by standalone legend + * components to to lookup the plot and access scale mappings. + */ + name?: string; + + /** + * The outer width of the plot in pixels, including margins. Defaults to 640. + * On Observable, this can be set to the built-in [width][1] for full-width + * responsive plots. Note: the default style has a max-width of 100%; the plot + * will automatically shrink to fit even when a fixed width is specified. + * + * [1]: https://github.com/observablehq/stdlib/blob/main/README.md#width + */ + width?: number | ParamRef; + + /** + * The outer height of the plot in pixels, including margins. The default + * depends on the plot’s scales, and the plot’s width if an aspectRatio is + * specified. For example, if the *y* scale is linear and there is no *fy* + * scale, it might be 396. + */ + height?: number | ParamRef; + + /** + * The desired aspect ratio of the *x* and *y* scales, affecting the default + * height. Given an aspect ratio of *dx* / *dy*, and assuming that the *x* and + * *y* scales represent equivalent units (say, degrees Celsius or meters), + * computes a default height such that *dx* pixels along *x* represents the + * same variation as *dy* pixels along *y*. Note: when faceting, set the *fx* + * and *fy* scales’ **round** option to false for an exact aspect ratio. + */ + aspectRatio?: number | boolean | null | ParamRef; + + /** + * Shorthand to set the same default for all four margins: **marginTop**, + * **marginRight**, **marginBottom**, and **marginLeft**. Otherwise, the + * default margins depend on the maximum margins of the plot’s marks. While + * most marks default to zero margins (because they are drawn inside the chart + * area), Plot’s axis marks have non-zero default margins. + */ + margin?: number | ParamRef; + + /** + * The top margin; the distance in pixels between the top edges of the inner + * and outer plot area. Defaults to the maximum top margin of the plot’s + * marks. + */ + marginTop?: number | ParamRef; + + /** + * The right margin; the distance in pixels between the right edges of the + * inner and outer plot area. Defaults to the maximum right margin of the + * plot’s marks. + */ + marginRight?: number | ParamRef; + + /** + * The bottom margin; the distance in pixels between the bottom edges of the + * inner and outer plot area. Defaults to the maximum bottom margin of the + * plot’s marks. + */ + marginBottom?: number | ParamRef; + + /** + * The left margin; the distance in pixels between the left edges of the inner + * and outer plot area. Defaults to the maximum left margin of the plot’s + * marks. + */ + marginLeft?: number | ParamRef; + + /** + * A shorthand object notation for setting multiple margin values. + * The object keys are margin names (top, right, etc). + */ + margins?: { + top?: number | ParamRef; + right?: number | ParamRef; + bottom?: number | ParamRef; + left?: number | ParamRef; + }; + + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + inset?: number | ParamRef; + + /** + * Custom styles to override Plot’s defaults. Styles may be specified either + * as a string of inline styles (*e.g.*, `"color: red;"`, in the same fashion + * as assigning [*element*.style][1]) or an object of properties (*e.g.*, + * `{color: "red"}`, in the same fashion as assigning [*element*.style + * properties][2]). Note that unitless numbers ([quirky lengths][3]) such as + * `{padding: 20}` may not supported by some browsers; you should instead + * specify a string with units such as `{padding: "20px"}`. By default, the + * returned plot has a max-width of 100%, and the system-ui font. Plot’s marks + * and axes default to [currentColor][4], meaning that they will inherit the + * surrounding content’s color. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style + * [2]: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration + * [3]: https://www.w3.org/TR/css-values-4/#deprecated-quirky-length + * [4]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword + */ + style?: string | Partial | null | ParamRef; + + /** + * How to distribute unused space in the **range** for *point* and *band* + * scales. A number in [0, 1], such as: + * + * - 0 - use the start of the range, putting unused space at the end + * - 0.5 (default) - use the middle, distributing unused space evenly + * - 1 use the end, putting unused space at the start + * + * For ordinal position scales only. + */ + align?: number | ParamRef; + + /** + * For *band* scales, how much of the **range** to reserve to separate + * adjacent bands; defaults to 0.1 (10%). For *point* scales, the amount of + * inset for the first and last value as a proportion of the bandwidth; + * defaults to 0.5 (50%). + * + * For ordinal position scales only. + */ + padding?: number | ParamRef; + + /** + * The side of the frame on which to place the implicit axis: *top* or + * *bottom* for *x* or *fx*, or *left* or *right* for *y* or *fy*. The default + * depends on the scale: + * + * - *x* - *bottom* + * - *y* - *left* + * - *fx* - *top* if there is a *bottom* *x* axis, and otherwise *bottom* + * - *fy* - *right* if there is a *left* *y* axis, and otherwise *right* + * + * If *both*, an implicit axis will be rendered on both sides of the plot + * (*top* and *bottom* for *x* or *fx*, or *left* and *right* for *y* or + * *fy*). If null, the implicit axis is suppressed. + * + * For position axes only. + */ + axis?: 'top' | 'right' | 'bottom' | 'left' | 'both' | boolean | null | ParamRef; + + /** + * Whether to show a grid aligned with the scale’s ticks. If true, show a grid + * with the currentColor stroke; if a string, show a grid with the specified + * stroke color; if an approximate number of ticks, an interval, or an array + * of tick values, show corresponding grid lines. See also the grid mark. + * + * For axes only. + */ + grid?: boolean | string | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + label?: string | null | ParamRef; + + + // x scale attributes + + /** + * The *x* scale type, affecting how the scale encodes abstract data, say by + * applying a mathematical transformation. If null, the scale is disabled. + * + * For quantitative data (numbers), defaults to *linear*; for temporal data + * (dates), defaults to *utc*; for ordinal data (strings or booleans), + * defaults to *point* for position scales, *categorical* for color scales, + * and otherwise *ordinal*. However, the radius scale defaults to *sqrt*, and + * the length and opacity scales default to *linear*; these scales are + * intended for quantitative data. The plot’s marks may also impose a scale + * type; for example, the barY mark requires that *x* is a *band* scale. + */ + xScale?: PositionScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + * + * Linear scales have a default domain of [0, 1]. Log scales have a default + * domain of [1, 10] and cannot include zero. Radius scales have a default + * domain from 0 to the median first quartile of associated channels. Length + * have a default domain from 0 to the median median of associated channels. + * Opacity scales have a default domain from 0 to the maximum value of + * associated channels. + */ + xDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and for position scales, the plot’s + * dimensions. For continuous data (numbers and dates), and for ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. + */ + xRange?: any[] | Fixed | ParamRef; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + xNice?: boolean | number | Interval | ParamRef; + + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + xInset?: number | ParamRef; + + /** + * Insets the right edge by the specified number of pixels. A positive value + * insets towards the left edge (reducing effective area), while a negative + * value insets away from the left edge (increasing it). + */ + xInsetRight?: number | ParamRef; + + /** + * Insets the left edge by the specified number of pixels. A positive value + * insets towards the right edge (reducing effective area), while a negative + * value insets away from the right edge (increasing it). + */ + xInsetLeft?: number | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + xClamp?: boolean | ParamRef; + + /** + * If true, round the output value to the nearest integer (pixel); useful for + * crisp edges when rendering. + * + * For position scales only. + */ + xRound?: boolean | ParamRef; + + /** + * How to distribute unused space in the **range** for *point* and *band* + * scales. A number in [0, 1], such as: + * + * - 0 - use the start of the range, putting unused space at the end + * - 0.5 (default) - use the middle, distributing unused space evenly + * - 1 use the end, putting unused space at the start + * + * For ordinal position scales only. + */ + xAlign?: number | ParamRef; + + /** + * For *band* scales, how much of the **range** to reserve to separate + * adjacent bands; defaults to 0.1 (10%). For *point* scales, the amount of + * inset for the first and last value as a proportion of the bandwidth; + * defaults to 0.5 (50%). + * + * For ordinal position scales only. + */ + xPadding?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to separate + * adjacent bands. + */ + xPaddingInner?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to inset first and + * last bands. + */ + xPaddingOuter?: number | ParamRef; + + /** + * The side of the frame on which to place the implicit axis: *top* or + * *bottom* for *x*. Defaults to *bottom* for an *x* scale. + * + * If *both*, an implicit axis will be rendered on both sides of the plot + * (*top* and *bottom* for *x*). If null, the implicit axis is suppressed. + */ + xAxis?: 'top' | 'bottom' | 'both' | boolean | null | ParamRef; + + /** + * The desired approximate number of axis ticks, or an explicit array of tick + * values, or an interval such as *day* or *month*. + */ + xTicks?: number | Interval | any[] | ParamRef; + + /** + * The length of axis tick marks in pixels; negative values extend in the + * opposite direction. Defaults to 6 for *x* and *y* axes and *color* and + * *opacity* *ramp* legends, and 0 for *fx* and *fy* axes. + */ + xTickSize?: number | ParamRef; + + /** + * The desired approximate spacing between adjacent axis ticks, affecting the + * default **ticks**; defaults to 80 pixels for *x* and *fx*, and 35 pixels + * for *y* and *fy*. + */ + xTickSpacing?: number | ParamRef; + + /** + * The distance between an axis tick mark and its associated text label (in + * pixels); often defaults to 3, but may be affected by **xTickSize** and + * **xTickRotate**. + */ + xTickPadding?: number | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + xTickFormat?: string | null | ParamRef; + + /** + * The rotation angle of axis tick labels in degrees clocksize; defaults to 0. + */ + xTickRotate?: number | ParamRef; + + /** + * Whether to show a grid aligned with the scale’s ticks. If true, show a grid + * with the currentColor stroke; if a string, show a grid with the specified + * stroke color; if an approximate number of ticks, an interval, or an array + * of tick values, show corresponding grid lines. See also the grid mark. + * + * For axes only. + */ + xGrid?: boolean | string | Interval | any[] | ParamRef; + + /** + * If true, draw a line along the axis; if false (default), do not. + */ + xLine?: boolean | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + xLabel?: string | null | ParamRef; + + /** + * Where to place the axis **label** relative to the plot’s frame. For + * vertical position scales (*y* and *fy*), may be *top*, *bottom*, or + * *center*; for horizontal position scales (*x* and *fx*), may be *left*, + * *right*, or *center*. Defaults to *center* for ordinal scales (including + * *fx* and *fy*), and otherwise *top* for *y*, and *right* for *x*. + */ + xLabelAnchor?: 'top' | 'right' | 'bottom' | 'left' | 'center' | ParamRef; + + /** + * The axis **label** position offset (in pixels); default depends on margins + * and orientation. + */ + xLabelOffset?: number | ParamRef; + + /** + * The font-variant attribute for axis ticks; defaults to *tabular-nums* for + * quantitative axes. + */ + xFontVariant?: string | ParamRef; + + /** + * A short label representing the axis in the accessibility tree. + */ + xAriaLabel?: string | ParamRef; + + /** + * A textual description for the axis in the accessibility tree. + */ + xAriaDescription?: string | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. + */ + xReverse?: boolean | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + xZero?: boolean | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* scales only. + */ + xExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* scales only. + */ + xBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* scales only. + */ + xConstant?: number | ParamRef; + + + // y scale attributes + + /** + * The *y* scale type, affecting how the scale encodes abstract data, say by + * applying a mathematical transformation. If null, the scale is disabled. + * + * For quantitative data (numbers), defaults to *linear*; for temporal data + * (dates), defaults to *utc*; for ordinal data (strings or booleans), + * defaults to *point* for position scales, The plot’s marks may also impose + * a scale type; for example, the barY mark requires that *x* is a *band* + * scale. + */ + yScale?: PositionScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + * + * Linear scales have a default domain of [0, 1]. Log scales have a default + * domain of [1, 10] and cannot include zero. + */ + yDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred + * from the scale’s **type** and **domain**, and for position scales, the + * plot’s dimensions. For continuous data (numbers and dates), and for + * ordinal position scales (*point* and *band*), it is typically [*min*, + * *max*]; it can be [*max*, *min*] to reverse the scale. + */ + yRange?: any[] | Fixed | ParamRef; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + yNice?: boolean | number | Interval | ParamRef; + + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + yInset?: number | ParamRef; + + /** + * Insets the top edge by the specified number of pixels. A positive value + * insets towards the bottom edge (reducing effective area), while a negative + * value insets away from the bottom edge (increasing it). + */ + yInsetTop?: number | ParamRef; + + /** + * Insets the bottom edge by the specified number of pixels. A positive value + * insets towards the top edge (reducing effective area), while a negative + * value insets away from the top edge (increasing it). + */ + yInsetBottom?: number | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + yClamp?: boolean | ParamRef; + + /** + * If true, round the output value to the nearest integer (pixel); useful for + * crisp edges when rendering. + * + * For position scales only. + */ + yRound?: boolean | ParamRef; + + /** + * How to distribute unused space in the **range** for *point* and *band* + * scales. A number in [0, 1], such as: + * + * - 0 - use the start of the range, putting unused space at the end + * - 0.5 (default) - use the middle, distributing unused space evenly + * - 1 use the end, putting unused space at the start + * + * For ordinal position scales only. + */ + yAlign?: number | ParamRef; + + /** + * For *band* scales, how much of the **range** to reserve to separate + * adjacent bands; defaults to 0.1 (10%). For *point* scales, the amount of + * inset for the first and last value as a proportion of the bandwidth; + * defaults to 0.5 (50%). + * + * For ordinal position scales only. + */ + yPadding?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to separate + * adjacent bands. + */ + yPaddingInner?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to inset first and + * last bands. + */ + yPaddingOuter?: number | ParamRef; + + /** + * The side of the frame on which to place the implicit axis: *left* or + * *right* for *y*. Defaults to *left* for a *y* scale. + * + * If *both*, an implicit axis will be rendered on both sides of the plot + * (*left* and *right* for *y*). If null, the implicit axis is suppressed. + */ + yAxis?: 'left' | 'right' | 'both' | boolean | null | ParamRef; + + /** + * The desired approximate number of axis ticks, or an explicit array of tick + * values, or an interval such as *day* or *month*. + */ + yTicks?: number | Interval | any[] | ParamRef; + + /** + * The length of axis tick marks in pixels; negative values extend in the + * opposite direction. Defaults to 6 for *x* and *y* axes and *color* and + * *opacity* *ramp* legends, and 0 for *fx* and *fy* axes. + */ + yTickSize?: number | ParamRef; + + /** + * The desired approximate spacing between adjacent axis ticks, affecting the + * default **ticks**; defaults to 80 pixels for *x* and *fx*, and 35 pixels + * for *y* and *fy*. + */ + yTickSpacing?: number | ParamRef; + + /** + * The distance between an axis tick mark and its associated text label (in + * pixels); often defaults to 3, but may be affected by **yTickSize** and + * **yTickRotate**. + */ + yTickPadding?: number | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + yTickFormat?: string | null | ParamRef; + + /** + * The rotation angle of axis tick labels in degrees clocksize; defaults to 0. + */ + yTickRotate?: number | ParamRef; + + /** + * Whether to show a grid aligned with the scale’s ticks. If true, show a grid + * with the currentColor stroke; if a string, show a grid with the specified + * stroke color; if an approximate number of ticks, an interval, or an array + * of tick values, show corresponding grid lines. See also the grid mark. + * + * For axes only. + */ + yGrid?: boolean | string | Interval | any[] | ParamRef; + + /** + * If true, draw a line along the axis; if false (default), do not. + */ + yLine?: boolean | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + yLabel?: string | null | ParamRef; + + /** + * Where to place the axis **label** relative to the plot’s frame. For + * vertical position scales (*y* and *fy*), may be *top*, *bottom*, or + * *center*; for horizontal position scales (*x* and *fx*), may be *left*, + * *right*, or *center*. Defaults to *center* for ordinal scales (including + * *fx* and *fy*), and otherwise *top* for *y*, and *right* for *x*. + */ + yLabelAnchor?: 'top' | 'right' | 'bottom' | 'left' | 'center' | ParamRef; + + /** + * The axis **label** position offset (in pixels); default depends on margins + * and orientation. + */ + yLabelOffset?: number | ParamRef; + + /** + * The font-variant attribute for axis ticks; defaults to *tabular-nums* for + * quantitative axes. + */ + yFontVariant?: string | ParamRef; + + /** + * A short label representing the axis in the accessibility tree. + */ + yAriaLabel?: string | ParamRef; + + /** + * A textual description for the axis in the accessibility tree. + */ + yAriaDescription?: string | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. Note that by default, when the *y* scale is + * continuous, the *max* value points to the top of the screen, whereas + * ordinal values are ranked from top to bottom. + */ + yReverse?: boolean | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + yZero?: boolean | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* scales only. + */ + yExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* scales only. + */ + yBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* scales only. + */ + yConstant?: number | ParamRef; + + + /** + * Set the *x* and *y* scale domains. + */ + xyDomain?: any[] | Fixed | ParamRef; + + + // facet attributes + + /** + * Shorthand to set the same default for all four facet margins: marginTop, + * marginRight, marginBottom, and marginLeft. + */ + facetMargin?: number | ParamRef; + + /** + * The top facet margin; the (minimum) distance in pixels between the top + * edges of the inner and outer plot area. + */ + facetMarginTop?: number | ParamRef; + + /** + * The right facet margin; the (minimum) distance in pixels between the right + * edges of the inner and outer plot area. + */ + facetMarginBottom?: number | ParamRef; + + /** + * The bottom facet margin; the (minimum) distance in pixels between the + * bottom edges of the inner and outer plot area. + */ + facetMarginLeft?: number | ParamRef; + + /** + * The left facet margin; the (minimum) distance in pixels between the left + * edges of the inner and outer plot area. + */ + facetMarginRight?: number | ParamRef; + + /** + * Default axis grid for fx and fy scales; typically set to true to enable. + */ + facetGrid?: boolean | string | Interval | any[] | ParamRef; + + /** + * Default axis label for fx and fy scales; typically set to null to disable. + */ + facetLabel?: string | null | ParamRef; + + + // fx scale attributes + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For ordinal data (strings or booleans), it is an + * array (or iterable) of values is the desired order, defaulting to natural + * ascending order. + */ + fxDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and the plot’s dimensions. For ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. + */ + fxRange?: any[] | Fixed | ParamRef; + + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + fxInset?: number | ParamRef; + + /** + * Insets the right edge by the specified number of pixels. A positive value + * insets towards the left edge (reducing effective area), while a negative + * value insets away from the left edge (increasing it). + */ + fxInsetRight?: number | ParamRef; + + /** + * Insets the left edge by the specified number of pixels. A positive value + * insets towards the right edge (reducing effective area), while a negative + * value insets away from the right edge (increasing it). + */ + fxInsetLeft?: number | ParamRef; + + /** + * If true, round the output value to the nearest integer (pixel); useful for + * crisp edges when rendering. + * + * For position scales only. + */ + fxRound?: boolean | ParamRef; + + /** + * How to distribute unused space in the **range** for *point* and *band* + * scales. A number in [0, 1], such as: + * + * - 0 - use the start of the range, putting unused space at the end + * - 0.5 (default) - use the middle, distributing unused space evenly + * - 1 use the end, putting unused space at the start + * + * For ordinal position scales only. + */ + fxAlign?: number | ParamRef; + + /** + * For *band* scales, how much of the **range** to reserve to separate + * adjacent bands; defaults to 0.1 (10%). For *point* scales, the amount of + * inset for the first and last value as a proportion of the bandwidth; + * defaults to 0.5 (50%). + * + * For ordinal position scales only. + */ + fxPadding?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to separate + * adjacent bands. + */ + fxPaddingInner?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to inset first and + * last bands. + */ + fxPaddingOuter?: number | ParamRef; + + /** + * The side of the frame on which to place the implicit axis: *top* or + * *bottom* for *fx*. Defaults to *top* if there is a *bottom* *x* axis, + * and otherwise *bottom*. + * + * If *both*, an implicit axis will be rendered on both sides of the plot + * (*top* and *bottom* for *fx*). If null, the implicit axis is suppressed. + */ + fxAxis?: 'top' | 'bottom' | 'both' | boolean | null | ParamRef; + + /** + * The desired approximate number of axis ticks, or an explicit array of tick + * values, or an interval such as *day* or *month*. + */ + fxTicks?: number | Interval | any[] | ParamRef; + + /** + * The length of axis tick marks in pixels; negative values extend in the + * opposite direction. Defaults to 6 for *x* and *y* axes and *color* and + * *opacity* *ramp* legends, and 0 for *fx* and *fy* axes. + */ + fxTickSize?: number | ParamRef; + + /** + * The desired approximate spacing between adjacent axis ticks, affecting the + * default **ticks**; defaults to 80 pixels for *x* and *fx*, and 35 pixels + * for *y* and *fy*. + */ + fxTickSpacing?: number | ParamRef; + + /** + * The distance between an axis tick mark and its associated text label (in + * pixels); often defaults to 3, but may be affected by **fxTickSize** and + * **fxTickRotate**. + */ + fxTickPadding?: number | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + fxTickFormat?: string | null | ParamRef; + + /** + * The rotation angle of axis tick labels in degrees clocksize; defaults to 0. + */ + fxTickRotate?: number | ParamRef; + + /** + * Whether to show a grid aligned with the scale’s ticks. If true, show a grid + * with the currentColor stroke; if a string, show a grid with the specified + * stroke color; if an approximate number of ticks, an interval, or an array + * of tick values, show corresponding grid lines. See also the grid mark. + * + * For axes only. + */ + fxGrid?: boolean | string | Interval | any[] | ParamRef; + + /** + * If true, draw a line along the axis; if false (default), do not. + */ + fxLine?: boolean | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + fxLabel?: string | null | ParamRef; + + /** + * Where to place the axis **label** relative to the plot’s frame. For + * vertical position scales (*y* and *fy*), may be *top*, *bottom*, or + * *center*; for horizontal position scales (*x* and *fx*), may be *left*, + * *right*, or *center*. Defaults to *center* for ordinal scales (including + * *fx* and *fy*), and otherwise *top* for *y*, and *right* for *x*. + */ + fxLabelAnchor?: 'top' | 'right' | 'bottom' | 'left' | 'center' | ParamRef; + + /** + * The axis **label** position offset (in pixels); default depends on margins + * and orientation. + */ + fxLabelOffset?: number | ParamRef; + + /** + * The font-variant attribute for axis ticks; defaults to *tabular-nums* for + * quantitative axes. + */ + fxFontVariant?: string | ParamRef; + + /** + * A short label representing the axis in the accessibility tree. + */ + fxAriaLabel?: string | ParamRef; + + /** + * A textual description for the axis in the accessibility tree. + */ + fxAriaDescription?: string | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. + */ + fxReverse?: boolean | ParamRef; + + + // fy scale attributes + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For ordinal data (strings or booleans), it is an + * array (or iterable) of values is the desired order, defaulting to natural + * ascending order. + */ + fyDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and the plot’s dimensions. For ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. + */ + fyRange?: any[] | Fixed | ParamRef; + + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + fyInset?: number | ParamRef; + + /** + * Insets the top edge by the specified number of pixels. A positive value + * insets towards the bottom edge (reducing effective area), while a negative + * value insets away from the bottom edge (increasing it). + */ + fyInsetTop?: number | ParamRef; + + /** + * Insets the bottom edge by the specified number of pixels. A positive value + * insets towards the top edge (reducing effective area), while a negative + * value insets away from the top edge (increasing it). + */ + fyInsetBottom?: number | ParamRef; + + /** + * If true, round the output value to the nearest integer (pixel); useful for + * crisp edges when rendering. + * + * For position scales only. + */ + fyRound?: boolean | ParamRef; + + /** + * How to distribute unused space in the **range** for *point* and *band* + * scales. A number in [0, 1], such as: + * + * - 0 - use the start of the range, putting unused space at the end + * - 0.5 (default) - use the middle, distributing unused space evenly + * - 1 use the end, putting unused space at the start + * + * For ordinal position scales only. + */ + fyAlign?: number | ParamRef; + + /** + * For *band* scales, how much of the **range** to reserve to separate + * adjacent bands; defaults to 0.1 (10%). For *point* scales, the amount of + * inset for the first and last value as a proportion of the bandwidth; + * defaults to 0.5 (50%). + * + * For ordinal position scales only. + */ + fyPadding?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to separate + * adjacent bands. + */ + fyPaddingInner?: number | ParamRef; + + /** + * For a *band* scale, how much of the range to reserve to inset first and + * last bands. + */ + fyPaddingOuter?: number | ParamRef; + + /** + * The side of the frame on which to place the implicit axis: *left* or + * *right* for *fy*. Defaults to *left* for an *fy* scale. + * + * If *both*, an implicit axis will be rendered on both sides of the plot + * (*left* and *right* for *fy*). If null, the implicit axis is suppressed. + */ + fyAxis?: 'left' | 'right' | 'both' | boolean | null | ParamRef; + + /** + * The desired approximate number of axis ticks, or an explicit array of tick + * values, or an interval such as *day* or *month*. + */ + fyTicks?: number | Interval | any[] | ParamRef; + + /** + * The length of axis tick marks in pixels; negative values extend in the + * opposite direction. Defaults to 6 for *x* and *y* axes and *color* and + * *opacity* *ramp* legends, and 0 for *fx* and *fy* axes. + */ + fyTickSize?: number | ParamRef; + + /** + * The desired approximate spacing between adjacent axis ticks, affecting the + * default **ticks**; defaults to 80 pixels for *x* and *fx*, and 35 pixels + * for *y* and *fy*. + */ + fyTickSpacing?: number | ParamRef; + + /** + * The distance between an axis tick mark and its associated text label (in + * pixels); often defaults to 3, but may be affected by **fyTickSize** and + * **fyTickRotate**. + */ + fyTickPadding?: number | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + fyTickFormat?: string | null | ParamRef; + + /** + * The rotation angle of axis tick labels in degrees clocksize; defaults to 0. + */ + fyTickRotate?: number | ParamRef; + + /** + * Whether to show a grid aligned with the scale’s ticks. If true, show a grid + * with the currentColor stroke; if a string, show a grid with the specified + * stroke color; if an approximate number of ticks, an interval, or an array + * of tick values, show corresponding grid lines. See also the grid mark. + * + * For axes only. + */ + fyGrid?: boolean | string | Interval | any[] | ParamRef; + + /** + * If true, draw a line along the axis; if false (default), do not. + */ + fyLine?: boolean | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + fyLabel?: string | null | ParamRef; + + /** + * Where to place the axis **label** relative to the plot’s frame. For + * vertical position scales (*y* and *fy*), may be *top*, *bottom*, or + * *center*; for horizontal position scales (*x* and *fx*), may be *left*, + * *right*, or *center*. Defaults to *center* for ordinal scales (including + * *fx* and *fy*), and otherwise *top* for *y*, and *right* for *x*. + */ + fyLabelAnchor?: 'top' | 'right' | 'bottom' | 'left' | 'center' | ParamRef; + + /** + * The axis **label** position offset (in pixels); default depends on margins + * and orientation. + */ + fyLabelOffset?: number | ParamRef; + + /** + * The font-variant attribute for axis ticks; defaults to *tabular-nums* for + * quantitative axes. + */ + fyFontVariant?: string | ParamRef; + + /** + * A short label representing the axis in the accessibility tree. + */ + fyAriaLabel?: string | ParamRef; + + /** + * A textual description for the axis in the accessibility tree. + */ + fyAriaDescription?: string | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. + */ + fyReverse?: boolean | ParamRef; + + + // color scale attributes + + /** + * The *color* scale type, affecting how the scale encodes abstract data, say + * by applying a mathematical transformation. If null, the scale is disabled. + * + * For quantitative data (numbers), defaults to *linear*; for temporal data + * (dates), defaults to *utc*; for ordinal data (strings or booleans), + * defaults to *point* for position scales, *categorical* for color scales, + * and otherwise *ordinal*. + */ + colorScale?: ColorScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + */ + colorDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**. For other ordinal data, it is an array + * (or iterable) of output values in the same order as the **domain**. + */ + colorRange?: any[] | Fixed | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + colorClamp?: boolean | ParamRef; + + /** + * For a *quantile* scale, the number of quantiles (creates *n* - 1 + * thresholds); for a *quantize* scale, the approximate number of thresholds; + * defaults to 5. + */ + colorN?: number | ParamRef; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + colorNice?: boolean | number | Interval | ParamRef; + + /** + * If specified, shorthand for setting the **colorRange** + * or **colorInterpolate** option of a *color* scale. + */ + colorScheme?: ColorScheme | ParamRef; + + /** + * How to interpolate color range values. For quantitative scales only. + * This attribute can be used to specify a color space for interpolating + * colors specified in the **colorRange**. + */ + colorInterpolate?: Interpolate | ParamRef; + + /** + * For a diverging color scale, the input value (abstract value) that divides + * the domain into two parts; defaults to 0 for *diverging* scales, dividing + * the domain into negative and positive parts; defaults to 1 for + * *diverging-log* scales. By default, diverging scales are symmetric around + * the pivot; see the **symmetric** option. + */ + colorPivot?: any | ParamRef; + + /** + * For a diverging color scale, if true (the default), extend the domain to + * ensure that the lower part of the domain (below the **pivot**) is + * commensurate with the upper part of the domain (above the **pivot**). + * + * A symmetric diverging color scale may not use all of its output **range**; + * this reduces contrast but ensures that deviations both below and above the + * **pivot** are represented proportionally. Otherwise if false, the full + * output **range** will be used; this increases contrast but values on + * opposite sides of the **pivot** may not be meaningfully compared. + */ + colorSymmetric?: boolean | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + colorLabel?: string | null | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. + */ + colorReverse?: boolean | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + colorZero?: boolean | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + colorTickFormat?: string | null | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* and *diverging-pow* scales only. + */ + colorExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* and *diverging-log* scales only. + */ + colorBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* and *diverging-symlog* + * scales only. + */ + colorConstant?: number | ParamRef; + + + // opacity scale attributes + + /** + * The *opacity* scale type, affecting how the scale encodes abstract data, + * say by applying a mathematical transformation. If null, the scale is + * disabled. The opacity scale defaults to *linear*; this scales is intended + * for quantitative data. + */ + opacityScale?: ContinuousScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + * + * Opacity scales have a default domain from 0 to the maximum value of + * associated channels. + */ + opacityDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). + * + * Opacity scales have a default range of [0, 1]. + */ + opacityRange?: any[] | Fixed | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + opacityClamp?: boolean | ParamRef; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + opacityNice?: boolean | number| Interval | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + opacityLabel?: string | null | ParamRef; + + /** + * Whether to reverse the scale’s encoding; equivalent to reversing either the + * **domain** or **range**. + */ + opacityReverse?: boolean | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + opacityZero?: boolean | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + opacityTickFormat?: string | null | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* scales only. + */ + opacityExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* scales only. + */ + opacityBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* scales only. + */ + opacityConstant?: number | ParamRef; + + + // symbol scale attributes + + /** + * The *symbol* scale type, affecting how the scale encodes abstract data, + * say by applying a mathematical transformation. If null, the scale is + * disabled. Defaults to an *ordinal* scale type. + */ + symbolScale?: DiscreteScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. As symbol scales are discrete, the domain is an array + * (or iterable) of values is the desired order, defaulting to natural + * ascending order. + */ + symbolDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and for position scales, the plot’s + * dimensions. For continuous data (numbers and dates), and for ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. For other ordinal data, such as + * for a *color* scale, it is an array (or iterable) of output values in the + * same order as the **domain**. + * + * Symbol scales have a default range of categorical symbols; the choice of + * symbols depends on whether the associated dot mark is filled or stroked. + */ + symbolRange?: any[] | Fixed | ParamRef; + + + // r scale attributes + + /** + * The *r* (radius) scale type, affecting how the scale encodes abstract + * data, say by applying a mathematical transformation. If null, the scale + * is disabled. The radius scale defaults to *sqrt*; this scale is intended + * for quantitative data. + */ + rScale?: ContinuousScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + * + * Radius scales have a default domain from 0 to the median first quartile + * of associated channels. + */ + rDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and for position scales, the plot’s + * dimensions. For continuous data (numbers and dates), and for ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. For other ordinal data, such as + * for a *color* scale, it is an array (or iterable) of output values in the + * same order as the **domain**. + * + * Radius scales have a default range of [0, 3]. + */ + rRange?: any[] | Fixed | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + rClamp?: any; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + rNice?: boolean | number| Interval | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + rZero?: boolean | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* scales only. + */ + rExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* scales only. + */ + rBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* scales only. + */ + rConstant?: number | ParamRef; + + + // length scale attributes + + /** + * The *length* scale type, affecting how the scale encodes abstract data, + * say by applying a mathematical transformation. If null, the scale is + * disabled. The length scale defaults to *linear*, as this scale is intended + * for quantitative data. + */ + lengthScale?: ContinuousScaleType | null | ParamRef; + + /** + * The extent of the scale’s inputs (abstract values). By default inferred + * from channel values. For continuous data (numbers and dates), it is + * typically [*min*, *max*]; it can be [*max*, *min*] to reverse the scale. + * For ordinal data (strings or booleans), it is an array (or iterable) of + * values is the desired order, defaulting to natural ascending order. + * + * Linear scales have a default domain of [0, 1]. Log scales have a default + * domain of [1, 10] and cannot include zero. Radius scales have a default + * domain from 0 to the median first quartile of associated channels. Length + * have a default domain from 0 to the median median of associated channels. + * Opacity scales have a default domain from 0 to the maximum value of + * associated channels. + */ + lengthDomain?: any[] | Fixed | ParamRef; + + /** + * The extent of the scale’s outputs (visual values). By default inferred from + * the scale’s **type** and **domain**, and for position scales, the plot’s + * dimensions. For continuous data (numbers and dates), and for ordinal + * position scales (*point* and *band*), it is typically [*min*, *max*]; it + * can be [*max*, *min*] to reverse the scale. For other ordinal data, such as + * for a *color* scale, it is an array (or iterable) of output values in the + * same order as the **domain**. + * + * Length scales have a default range of [0, 12]. + */ + lengthRange?: any[] | Fixed | ParamRef; + + /** + * If true, values below the domain minimum are treated as the domain minimum, + * and values above the domain maximum are treated as the domain maximum. + * + * Clamping is useful for focusing on a subset of the data while ensuring that + * extreme values remain visible, but use caution: clamped values may need an + * annotation to avoid misinterpretation. Clamping typically requires setting + * an explicit **domain** since if the domain is inferred, no values will be + * outside the domain. + * + * For continuous scales only. + */ + lengthClamp?: any; + + /** + * If true, or a tick count or interval, extend the domain to nice round + * values. Defaults to 1, 2 or 5 times a power of 10 for *linear* scales, and + * nice time intervals for *utc* and *time* scales. Pass an interval such as + * *minute*, *wednesday* or *month* to specify what constitutes a nice + * interval. + * + * For continuous scales only. + */ + lengthNice?: boolean | number| Interval | ParamRef; + + /** + * Whether the **domain** must include zero. If the domain minimum is + * positive, it will be set to zero; otherwise if the domain maximum is + * negative, it will be set to zero. + * + * For quantitative scales only. + */ + lengthZero?: boolean | ParamRef; + + /** + * A power scale’s exponent (*e.g.*, 0.5 for sqrt); defaults to 1 for a + * linear scale. For *pow* scales only. + */ + lengthExponent?: number | ParamRef; + + /** + * A log scale’s base; defaults to 10. Does not affect the scale’s encoding, + * but rather the default ticks. For *log* scales only. + */ + lengthBase?: number | ParamRef; + + /** + * A symlog scale’s constant, expressing the magnitude of the linear region + * around the origin; defaults to 1. For *symlog* scales only. + */ + lengthConstant?: number | ParamRef; + + + // projection attributes + + /** + * The desired projection; one of: + * + * - a named built-in projection such as *albers-usa* + * - null, for no projection + * + * Named projections are scaled and translated to fit + * the **domain** to the plot’s frame (minus insets). + */ + projectionType?: ProjectionName | null | ParamRef; + + /** + * A GeoJSON object to fit to the plot’s frame (minus insets); defaults to a + * Sphere for spherical projections (outline of the the whole globe). + */ + projectionDomain?: object | ParamRef; + + /** + * A rotation of the sphere before projection; defaults to [0, 0, 0]. + * Specified as Euler angles λ (yaw, or reference longitude), φ (pitch, or + * reference latitude), and optionally γ (roll), in degrees. + */ + projectionRotate?: + | [x: number | ParamRef, y: number | ParamRef, z?: number | ParamRef] + | ParamRef; + + /** + * The [standard parallels][1]. For conic projections only. + * + * [1]: https://d3js.org/d3-geo/conic#conic_parallels + */ + projectionParallels?: [y1: number | ParamRef, y2: number | ParamRef] | ParamRef; + + /** + * The projection’s [sampling threshold][1]. + * + * [1]: https://d3js.org/d3-geo/projection#projection_precision + */ + projectionPrecision?: number | ParamRef; + + /** + * The projection’s clipping method; one of: + * + * - *frame* or true (default) - clip to the plot’s frame (including margins but not insets) + * - a number - clip to a circle of the given radius in degrees centered around the origin + * - null or false - do not clip + * + * Some projections (such as [*armadillo*][1] and [*berghaus*][2]) require + * spherical clipping: in that case set the marks’ **clip** option to + * *sphere*. + * + * [1]: https://observablehq.com/@d3/armadillo + * [2]: https://observablehq.com/@d3/berghaus-star + */ + projectionClip?: boolean | number | 'frame' | null | ParamRef; + + /** + * Shorthand to set the same default for all four projection insets. + * All insets typically default to zero, though not always. A positive + * inset reduces effective area, while a negative inset increases it. + */ + projectionInset?: number | ParamRef; + + /** + * Insets the top edge of the projection by the specified number of pixels. + * A positive value insets towards the bottom edge (reducing effective area), + * while a negative value insets away from the bottom edge (increasing it). + */ + projectionInsetTop?: number | ParamRef; + + /** + * Insets the right edge of the projection by the specified number of pixels. + * A positive value insets towards the left edge (reducing effective area), + * while a negative value insets away from the left edge (increasing it). + */ + projectionInsetRight?: number | ParamRef; + + /** + * Insets the bottom edge of the projection by the specified number of pixels. + * A positive value insets towards the top edge (reducing effective area), + * while a negative value insets away from the top edge (increasing it). + */ + projectionInsetBottom?: number | ParamRef; + + /** + * Insets the left edge of the projection by the specified number of pixels. + * A positive value insets towards the right edge (reducing effective area), + * while a negative value insets away from the right edge (increasing it). + */ + projectionInsetLeft?: number | ParamRef; +} diff --git a/packages/spec/src/spec/PlotFrom.ts b/packages/spec/src/spec/PlotFrom.ts new file mode 100644 index 00000000..208622f1 --- /dev/null +++ b/packages/spec/src/spec/PlotFrom.ts @@ -0,0 +1,23 @@ +import { ParamRef } from './Param.js'; + +/** + * An array of inline data values to visualize. As this data does not come + * from a database, it can not be filtered by interactive selections. + */ +export type PlotDataInline = any[]; + +/** Input data specification for a plot mark. */ +export interface PlotFrom { + /** The name of the backing data table. */ + from: string; + /** A selection that filters the mark data. */ + filterBy?: ParamRef; + /** + * A flag (default `true`) to enable any mark-specific query optimizations. + * If `false`, optimizations are disabled to aid testing and debugging. + */ + optimize?: boolean; +} + +/** Input data for a marks */ +export type PlotMarkData = PlotDataInline | PlotFrom; diff --git a/packages/spec/src/spec/PlotInteractor.ts b/packages/spec/src/spec/PlotInteractor.ts new file mode 100644 index 00000000..bc60e472 --- /dev/null +++ b/packages/spec/src/spec/PlotInteractor.ts @@ -0,0 +1,25 @@ +import { Highlight } from './interactors/Highlight.js'; +import { IntervalX, IntervalY } from './interactors/Interval1D.js'; +import { IntervalXY } from './interactors/Interval2D.js'; +import { NearestX, NearestY } from './interactors/Nearest.js'; +import { Pan, PanX, PanY, PanZoom, PanZoomX, PanZoomY } from './interactors/PanZoom.js'; +import { Toggle, ToggleColor, ToggleX, ToggleY } from './interactors/Toggle.js'; + +/** A plot interactor entry. */ +export type PlotInteractor = + | Highlight + | IntervalX + | IntervalY + | IntervalXY + | NearestX + | NearestY + | Toggle + | ToggleX + | ToggleY + | ToggleColor + | Pan + | PanX + | PanY + | PanZoom + | PanZoomX + | PanZoomY; diff --git a/packages/spec/src/spec/PlotLegend.ts b/packages/spec/src/spec/PlotLegend.ts new file mode 100644 index 00000000..e960ebea --- /dev/null +++ b/packages/spec/src/spec/PlotLegend.ts @@ -0,0 +1,70 @@ +import { ParamRef } from './Param.js'; + +/** + * A legend defined as an entry within a plot. + */ +export interface PlotLegend { + /** + * A legend of the given type. + * The valid types are `"color"`, `"opacity"`, and `"symbol"`. + */ + legend: 'color' | 'opacity' | 'symbol'; + /** + * The output selection. If specified, the legend is interactive, using a + * `toggle` interaction for discrete legends or an `intervalX` interaction + * for continuous legends. + */ + as?: ParamRef; + /** + * The data field over which to generate output selection clauses. If + * unspecified, a matching field is retrieved from existing plot marks. + */ + field?: string; + /** + * The legend label. + */ + label?: string; + /** + * The size of legend ticks in a continuous legend, in pixels. + */ + tickSize?: number; + /** + * The top margin of the legend component, in pixels. + */ + marginTop?: number; + /** + * The right margin of the legend component, in pixels. + */ + marginRight?: number; + /** + * The bottom margin of the legend component, in pixels. + */ + marginBottom?: number; + /** + * The left margin of the legend component, in pixels. + */ + marginLeft?: number; + /** + * The width of a continuous legend, in pixels. + */ + width?: number; + /** + * The height of a continuous legend, in pixels. + */ + height?: number; + /** + * The number of columns to use to layout a discrete legend. + */ + columns?: number; +} + +/** + * A legend defined as a top-level spec component. + */ +export interface Legend extends PlotLegend { + /** + * The name of the plot this legend applies to. + * A plot must include a `name` attribute to be referenced. + */ + for: string; +} diff --git a/packages/spec/src/spec/PlotMark.ts b/packages/spec/src/spec/PlotMark.ts new file mode 100644 index 00000000..bdc86a83 --- /dev/null +++ b/packages/spec/src/spec/PlotMark.ts @@ -0,0 +1,51 @@ +import { Area, AreaX, AreaY } from './marks/Area.js'; +import { Arrow } from './marks/Arrow.js'; +import { AxisFx, AxisFy, AxisX, AxisY, GridFx, GridFy, GridX, GridY } from './marks/Axis.js'; +import { BarX, BarY } from './marks/Bar.js'; +import { Cell, CellX, CellY } from './marks/Cell.js'; +import { Contour } from './marks/Contour.js'; +import { DelaunayLink, DelaunayMesh, Hull, Voronoi, VoronoiMesh } from './marks/Delaunay.js'; +import { DenseLine } from './marks/DenseLine.js'; +import { Density, DensityX, DensityY } from './marks/Density.js'; +import { Circle, Dot, DotX, DotY, Hexagon } from './marks/Dot.js'; +import { Frame } from './marks/Frame.js'; +import { Geo, Graticule, Sphere } from './marks/Geo.js'; +import { Hexbin } from './marks/Hexbin.js'; +import { Hexgrid } from './marks/Hexgrid.js'; +import { Image } from './marks/Image.js'; +import { Line, LineX, LineY } from './marks/Line.js'; +import { Link } from './marks/Link.js'; +import { Heatmap, Raster, RasterTile } from './marks/Raster.js'; +import { Rect, RectX, RectY } from './marks/Rect.js'; +import { RegressionY } from './marks/Regression.js'; +import { RuleX, RuleY } from './marks/Rule.js'; +import { Text,TextX, TextY } from './marks/Text.js'; +import { TickX, TickY } from './marks/Tick.js'; +import { Spike, Vector, VectorX, VectorY } from './marks/Vector.js'; + +/** A plot mark entry. */ +export type PlotMark = + | Area | AreaX | AreaY + | Arrow + | AxisX | AxisY | AxisFx | AxisFy | GridX | GridY | GridFx | GridFy + | BarX | BarY + | Cell | CellX | CellY + | Contour + | DelaunayLink | DelaunayMesh | Hull | Voronoi | VoronoiMesh + | DenseLine + | Density | DensityX | DensityY + | Dot | DotX | DotY | Circle | Hexagon + | Frame + | Geo | Graticule | Sphere + | Hexbin + | Hexgrid + | Image + | Line | LineX | LineY + | Link + | Raster | Heatmap | RasterTile + | Rect | RectX | RectY + | RegressionY + | RuleX | RuleY + | Text | TextX | TextY + | TickX | TickY + | Vector | VectorX | VectorY | Spike; diff --git a/packages/spec/src/spec/PlotTypes.ts b/packages/spec/src/spec/PlotTypes.ts new file mode 100644 index 00000000..27342705 --- /dev/null +++ b/packages/spec/src/spec/PlotTypes.ts @@ -0,0 +1,519 @@ +/** + * A symbol indicating a fixed scale domain. A fixed domain is initially + * determined from data as usual, but subsequently "fixed" so that it does not + * change over subsequent interactive filtering, ensring stable comparisons. + */ +export type Fixed = 'Fixed'; + +// For internal use. +export type LiteralTimeInterval = + | '3 months' + | '10 years' + | TimeIntervalName + | (`${TimeIntervalName}s` & Record) + | (`${number} ${TimeIntervalName}` & Record) + | (`${number} ${TimeIntervalName}s` & Record); + +/** + * The built-in time intervals; UTC or local time, depending on context. The + * *week* interval is an alias for *sunday*. The *quarter* interval is every + * three months, and the *half* interval is every six months, aligned at the + * start of the year. + */ +export type TimeIntervalName = + | 'second' + | 'minute' + | 'hour' + | 'day' + | 'week' + | 'month' + | 'quarter' // 3 months + | 'half' // 6 months + | 'year' + | 'monday' + | 'tuesday' + | 'wednesday' + | 'thursday' + | 'friday' + | 'saturday' + | 'sunday'; + +/** + * How to partition a continuous range into discrete intervals; one of: + * + * - a named time interval such as *day* (for date intervals) + * - a number (for number intervals), defining intervals at integer multiples of *n* + */ +export type Interval = T extends Date ? LiteralTimeInterval + : T extends number ? number + : never; + +/** + * The built-in scale names; one of: + * + * - *x* - horizontal position + * - *y* - vertical position + * - *fx* - horizontal facet position + * - *fy* - vertical facet position + * - *r* - radius (for dots and point geos) + * - *color* - color + * - *opacity* - opacity + * - *symbol* - categorical symbol (for dots) + * - *length* - length (for vectors) + * + * Position scales may have associated axes. Color, opacity, and symbol scales + * may have an associated legend. + */ +export type ScaleName = 'x' | 'y' | 'fx' | 'fy' | 'r' | 'color' | 'opacity' | 'symbol' | 'length'; + +/** + * The supported scale types for *x* and *y* position encodings. + * + * For quantitative data, one of: + * + * - *linear* (default) - linear transform (translate and scale) + * - *pow* - power (exponential) transform + * - *sqrt* - square-root transform; *pow* with *exponent* = 0.5 + * - *log* - logarithmic transform + * - *symlog* - bi-symmetric logarithmic transform per Webber et al. + * + * For temporal data, one of: + * + * - *utc* (default, recommended) - UTC time + * - *time* - local time + * + * For ordinal data, one of: + * + * - *point* (for position only) - divide a continuous range into discrete points + * - *band* (for position only) - divide a continuous range into discrete points + * + * Other scale types: + * + * - *identity* - do not transform values when encoding + */ +export type PositionScaleType = + | 'linear' + | 'pow' + | 'sqrt' + | 'log' + | 'symlog' + | 'utc' + | 'time' + | 'point' + | 'band' + | 'threshold' + | 'quantile' + | 'quantize' + | 'identity'; + +/** + * The supported scale types for *color* encodings. + * + * For quantitative data, one of: + * + * - *linear* (default) - linear transform (translate and scale) + * - *pow* - power (exponential) transform + * - *sqrt* - square-root transform; *pow* with *exponent* = 0.5 + * - *log* - logarithmic transform + * - *symlog* - bi-symmetric logarithmic transform per Webber et al. + * + * For temporal data, one of: + * + * - *utc* (default, recommended) - UTC time + * - *time* - local time + * + * For ordinal data, one of: + * + * - *ordinal* - from discrete inputs to discrete outputs + * + * For color, one of: + * + * - *categorical* - equivalent to *ordinal*; defaults to *observable10* + * - *sequential* - equivalent to *linear*; defaults to *turbo* + * - *cyclical* - equivalent to *linear*; defaults to *rainbow* + * - *threshold* - encodes using discrete thresholds; defaults to *rdylbu* + * - *quantile* - encodes using quantile thresholds; defaults to *rdylbu* + * - *quantize* - uniformly quantizes a continuous domain; defaults to *rdylbu* + * - *diverging* - *linear*, but with a pivot; defaults to *rdbu* + * - *diverging-log* - *log*, but with a pivot; defaults to *rdbu* + * - *diverging-pow* - *pow*, but with a pivot; defaults to *rdbu* + * - *diverging-sqrt* - *sqrt*, but with a pivot; defaults to *rdbu* + * - *diverging-symlog* - *symlog*, but with a pivot; defaults to *rdbu* + * + * Other scale types: + * + * - *identity* - do not transform values when encoding + */ +export type ColorScaleType = + | 'linear' + | 'pow' + | 'sqrt' + | 'log' + | 'symlog' + | 'utc' + | 'time' + | 'point' + | 'band' + | 'ordinal' + | 'sequential' + | 'cyclical' + | 'diverging' + | 'diverging-log' + | 'diverging-pow' + | 'diverging-sqrt' + | 'diverging-symlog' + | 'categorical' + | 'threshold' + | 'quantile' + | 'quantize' + | 'identity'; + +/** + * The supported scale types for continuous encoding channels. + * + * For quantitative data, one of: + * + * - *linear* (default) - linear transform (translate and scale) + * - *pow* - power (exponential) transform + * - *sqrt* - square-root transform; *pow* with *exponent* = 0.5 + * - *log* - logarithmic transform + * - *symlog* - bi-symmetric logarithmic transform per Webber et al. + * + * For temporal data, one of: + * + * - *utc* (default, recommended) - UTC time + * - *time* - local time + * + * Other scale types: + * + * - *identity* - do not transform values when encoding + */ +export type ContinuousScaleType = + | 'linear' + | 'pow' + | 'sqrt' + | 'log' + | 'symlog' + | 'utc' + | 'time' + | 'identity'; + +/** + * The supported scale types for discrete encoding channels. One of: + * + * - *ordinal* - from discrete inputs to discrete outputs + * - *identity* - do not transform values when encoding + */ +export type DiscreteScaleType = + | 'ordinal' + | 'identity'; + +/** + * The built-in projection implementations; one of: + * + * - *albers-usa* - a U.S.-centric composite projection with insets for Alaska and Hawaii + * - *albers* - a U.S.-centric *conic-equal-area* projection + * - *azimuthal-equal-area* - the azimuthal equal-area projection + * - *azimuthal-equidistant* - the azimuthal equidistant projection + * - *conic-conformal* - the conic conformal projection + * - *conic-equal-area* - the conic equal-area projection + * - *conic-equidistant* - the conic equidistant projection + * - *equal-earth* - the Equal Earth projection Šavrič et al., 2018 + * - *equirectangular* - the equirectangular (plate carrée) projection + * - *gnomonic* - the gnomonic projection + * - *identity* - the identity projection + * - *reflect-y* - the identity projection, but flipping *y* + * - *mercator* - the spherical Mercator projection + * - *orthographic* - the orthographic projection + * - *stereographic* - the stereographic projection + * - *transverse-mercator* - the transverse spherical Mercator projection + */ +export type ProjectionName = + | 'albers-usa' + | 'albers' + | 'azimuthal-equal-area' + | 'azimuthal-equidistant' + | 'conic-conformal' + | 'conic-equal-area' + | 'conic-equidistant' + | 'equal-earth' + | 'equirectangular' + | 'gnomonic' + | 'identity' + | 'reflect-y' + | 'mercator' + | 'orthographic' + | 'stereographic' + | 'transverse-mercator'; + +/** + * How to interpolate range (output) values for continuous scales; one of: + * + * - *number* - linear numeric interpolation + * - *rgb* - red, green, blue (sRGB) + * - *hsl* - hue, saturation, lightness (HSL; cylindrical sRGB) + * - *hcl* - hue, chroma, perceptual lightness (CIELCh_ab; cylindrical CIELAB) + * - *lab* - perceptual lightness and opponent colors (L\*a\*b\*, CIELAB) + */ +export type Interpolate = + | 'number' + | 'rgb' + | 'hsl' + | 'hcl' + | 'lab'; + +/** The built-in color schemes, cased. */ +type ColorSchemeCase = + | 'Accent' + | 'Category10' + | 'Dark2' + | 'Observable10' + | 'Paired' + | 'Pastel1' + | 'Pastel2' + | 'Set1' + | 'Set2' + | 'Set3' + | 'Tableau10' + | 'BrBG' + | 'PRGn' + | 'PiYG' + | 'PuOr' + | 'RdBu' + | 'RdGy' + | 'RdYlBu' + | 'RdYlGn' + | 'Spectral' + | 'BuRd' + | 'BuYlRd' + | 'Blues' + | 'Greens' + | 'Greys' + | 'Oranges' + | 'Purples' + | 'Reds' + | 'Turbo' + | 'Viridis' + | 'Magma' + | 'Inferno' + | 'Plasma' + | 'Cividis' + | 'Cubehelix' + | 'Warm' + | 'Cool' + | 'BuGn' + | 'BuPu' + | 'GnBu' + | 'OrRd' + | 'PuBu' + | 'PuBuGn' + | 'PuRd' + | 'RdPu' + | 'YlGn' + | 'YlGnBu' + | 'YlOrBr' + | 'YlOrRd' + | 'Rainbow' + | 'Sinebow'; + +/** + * The built-in color schemes. For categorical data, one of: + * + * - *Accent* - eight colors + * - *Category10* - ten colors + * - *Dark2* - eight colors + * - *Observable10* (default) - ten colors + * - *Paired* - twelve paired colors + * - *Pastel1* - nine colors + * - *Pastel2* - eight colors + * - *Set1* - nine colors + * - *Set2* - eight colors + * - *Set3* - twelve colors + * - *Tableau10* - ten colors + * + * For diverging data, one of: + * + * - *BrBG* - from brown to white to blue-green + * - *PRGn* - from purple to white to green + * - *PiYG* - from pink to white to yellow-green + * - *PuOr* - from purple to white to orange + * - *RdBu* (default) - from red to white to blue + * - *RdGy* - from red to white to gray + * - *RdYlBu* - from red to yellow to blue + * - *RdYlGn* - from red to yellow to green + * - *Spectral* - from red to blue, through the spectrum + * - *BuRd* - from blue to white to red + * - *BuYlRd* - from blue to yellow to red + * + * For sequential data, one of: + * + * - *Blues* - from white to blue + * - *Greens* - from white to green + * - *Greys* - from white to gray + * - *Oranges* - from white to orange + * - *Purples* - from white to purple + * - *Reds* - from white to red + * - *Turbo* (default) - from blue to red, through the spectrum + * - *Viridis* - from blue to green to yellow + * - *Magma* - from purple to orange to yellow + * - *Inferno* - from purple to orange to yellow + * - *Plasma* - from purple to orange to yellow + * - *Cividis* - from blue to yellow + * - *Cubehelix* - from black to white, rotating hue + * - *Warm* - from purple to green, through warm hues + * - *Cool* - from green to to purple, through cool hues + * - *BuGn* - from light blue to dark green + * - *BuPu* - from light blue to dark purple + * - *GnBu* - from light green to dark blue + * - *OrRd* - from light orange to dark red + * - *PuBu* - from light purple to dark blue + * - *PuBuGn* - from light purple to blue to dark green + * - *PuRd* - from light purple to dark red + * - *RdPu* - from light red to dark purple + * - *YlGn* - from light yellow to dark green + * - *YlGnBu* - from light yellow to green to dark blue + * - *YlOrBr* - from light yellow to orange to dark brown + * - *YlOrRd* - from light yellow to orange to dark red + * + * For cyclical data, one of: + * + * - *Rainbow* (default) - the less-angry rainbow color scheme + * - *Sinebow* - Bumgardner and Loyd’s “sinebow” scheme + */ +export type ColorScheme = ColorSchemeCase | (Lowercase & Record); + +/** + * The built-in symbol implementations. For fill, one of: + * + * - *circle* - a circle + * - *cross* - a Greek cross with arms of equal length + * - *diamond* - a rhombus + * - *square* - a square + * - *star* - a pentagonal star (pentagram) + * - *triangle* - an up-pointing triangle + * - *wye* - a Y with arms of equal length + * + * For stroke (based on [Heman Robinson’s research][1]), one of: + * + * - *circle* - a circle + * - *plus* - a plus sign + * - *times* - an X with arms of equal length + * - *triangle2* - an (alternate) up-pointing triangle + * - *asterisk* - an asterisk + * - *square2* - a (alternate) square + * - *diamond2* - a rotated square + * + * The *hexagon* symbol is also supported. + * + * [1]: https://www.tandfonline.com/doi/abs/10.1080/10618600.2019.1637746 + */ +export type SymbolType = + | 'asterisk' + | 'circle' + | 'cross' + | 'diamond' + | 'diamond2' + | 'hexagon' + | 'plus' + | 'square' + | 'square2' + | 'star' + | 'times' + | 'triangle' + | 'triangle2' + | 'wye'; + +/** + * How to anchor a mark relative to the plot’s frame; one of: + * + * - *middle* - centered in the middle + * - in the middle of one of the edges: *top*, *right*, *bottom*, *left* + * - in one of the corners: *top-left*, *top-right*, *bottom-right*, *bottom-left* + */ +export type FrameAnchor = + | 'middle' + | 'top-left' + | 'top' + | 'top-right' + | 'right' + | 'bottom-right' + | 'bottom' + | 'bottom-left' + | 'left'; + +// from https://github.com/observablehq/plot/blob/main/src/reducer.d.ts +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + +// For internal use. +export type ReducerPercentile = + | (`p${Digit}${Digit}` & Record) // see https://github.com/microsoft/TypeScript/issues/29729 + | 'p25' + | 'p50' + | 'p75'; + +/** + * How to reduce aggregated (binned or grouped) values; one of: + * + * - *first* - the first value, in input order + * - *last* - the last value, in input order + * - *count* - the number of elements (frequency) + * - *distinct* - the number of distinct values + * - *sum* - the sum of values + * - *proportion* - the sum proportional to the overall total (weighted frequency) + * - *proportion-facet* - the sum proportional to the facet total + * - *deviation* - the standard deviation + * - *min* - the minimum value + * - *min-index* - the zero-based index of the minimum value + * - *max* - the maximum value + * - *max-index* - the zero-based index of the maximum value + * - *mean* - the mean value (average) + * - *median* - the median value + * - *variance* - the variance per [Welford’s algorithm][1] + * - *mode* - the value with the most occurrences + * - *pXX* - the percentile value, where XX is a number in [00,99] + * - *identity* - the array of values + * + * [1]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + */ +export type Reducer = + | 'first' + | 'last' + | 'identity' + | 'count' + | 'distinct' + | 'sum' + | 'proportion' + | 'proportion-facet' + | 'deviation' + | 'min' + | 'min-index' + | 'max' + | 'max-index' + | 'mean' + | 'median' + | 'variance' + | 'mode' + | ReducerPercentile; + +/** The built-in curve implementations. */ +export type CurveName = + | 'basis' + | 'basis-closed' + | 'basis-open' + | 'bundle' + | 'bump-x' + | 'bump-y' + | 'cardinal' + | 'cardinal-closed' + | 'cardinal-open' + | 'catmull-rom' + | 'catmull-rom-closed' + | 'catmull-rom-open' + | 'linear' + | 'linear-closed' + | 'monotone-x' + | 'monotone-y' + | 'natural' + | 'step' + | 'step-after' + | 'step-before'; diff --git a/packages/spec/src/spec/Spec.ts b/packages/spec/src/spec/Spec.ts new file mode 100644 index 00000000..ed59c322 --- /dev/null +++ b/packages/spec/src/spec/Spec.ts @@ -0,0 +1,63 @@ +import { DataDefinition } from './Data.js'; +import { ParamDefinition } from './Param.js'; +import { HConcat } from './HConcat.js'; +import { VConcat } from './VConcat.js'; +import { HSpace } from './HSpace.js'; +import { VSpace } from './VSpace.js'; +import { Menu, Search, Slider, Table } from './Input.js'; +import { Plot } from './Plot.js'; +import { PlotMark } from './PlotMark.js'; +import { Legend } from './PlotLegend.js'; +import { PlotAttributes } from './PlotAttribute.js'; + +/** Specification metadata. */ +export interface Meta extends Record { + /** The specification title. */ + title?: string; + /** A description of the specification content. */ + description?: string; + /** Credits or other acknowledgements. */ + credit?: string; +} + +/** Configuration options. */ +export interface Config extends Record { + extensions?: string | string[]; +} + +/** Top-level dataset definitions. */ +export type Data = Record; + +/** Top-level Param and Selection definitions. */ +export type Params = Record; + +/** Top-level specification properties. */ +export interface SpecHead { + /** Specification metadata. */ + meta?: Meta; + /** Configuration options. */ + config?: Config; + /** Dataset definitions. */ + data?: Data; + /** Param and Selection definitions. */ + params?: Params; + /** A default set of attributes to apply to all plot components. */ + plotDefaults?: PlotAttributes; +} + +/** A specifcation component such as a plot, input widget, or layout. */ +export type Component = + | HConcat + | VConcat + | HSpace + | VSpace + | Menu + | Search + | Slider + | Table + | Plot + | PlotMark + | Legend; + +/** A declarative Mosaic specification. */ +export type Spec = SpecHead & Component; diff --git a/packages/spec/src/spec/Transform.ts b/packages/spec/src/spec/Transform.ts new file mode 100644 index 00000000..79b14d16 --- /dev/null +++ b/packages/spec/src/spec/Transform.ts @@ -0,0 +1,394 @@ +import { ParamRef } from './Param.js'; + +/** A field argument to a data transform. */ +export type TransformField = string | ParamRef; + +/** Window transform options. */ +export interface WindowOptions { + orderby?: TransformField | TransformField[]; + partitionby?: TransformField | TransformField[]; + rows?: (number | null)[] | ParamRef; + range?: (number | null)[] | ParamRef; +} + +/** Aggregate transform options. */ +export interface AggregateOptions { + distinct?: boolean; +} + +/** A transform argument. */ +type Arg = string | number | boolean; + +/** A zero argument transform signature. */ +type Arg0 = null | []; + +/** A single argument transform signature. */ +type Arg1 = Arg | [Arg]; + +/** + * A two argument transform signature; both arguments are required. + */ +type Arg2 = [Arg, Arg]; + +/** + * A two argument transform signature; the second argument is optional. + */ +type Arg2Opt = Arg | [Arg, Arg?]; + +/** + * A three argument transform signature; the + * second and third arguments are optional. + */ +type Arg3Opt = Arg | [Arg, Arg?, Arg?]; + +/** Bin transform options. */ +export interface BinOptions { + /** + * The target number of binning steps to use. To accommodate human-friendly + * bin boundaries, the actual number of bins may diverge from this exact number. + */ + steps?: number; + /** + * The minimum allowed bin step size (default `0`). + * For example, a setting of `1` will prevent step sizes less than 1. + */ + minstep?: number; + /** + * A flag requesting "nice" human-friendly step sizes (default `true`). + */ + nice?: true; + /** + * Offset for computed bins (default `0`). For example, a value of `1` will + * result in using the next consecutive bin boundary. + */ + offset?: number; +} + +/* A bin transform. */ +export interface Bin { + /** + * Bin a continuous variable into discrete intervals. This transform accepts + * a data column to bin over as well as an optional bin options object. + */ + bin: Arg | [Arg] | [Arg, BinOptions]; +} + +/* A dateMonth transform. */ +export interface DateMonth { + /** + * Transform a Date value to a month boundary for cyclic comparison. + * Year values are collapsed to enable comparison over months only. + */ + dateMonth: Arg1; +} + +/* A dateMonthDay transform. */ +export interface DateMonthDay { + /** + * Transform a Date value to a month and day boundary for cyclic comparison. + * Year values are collapsed to enable comparison over months and days only. + */ + dateMonthDay: Arg1; +} + +/* A dateDay transform. */ +export interface DateDay { + /** + * Transform a Date value to a day of the month for cyclic comparison. + * Year and month values are collapsed to enable comparison over days only. + */ + dateDay: Arg1; +} + +/* A centroid transform. */ +export interface Centroid { + /** + * Compute the 2D centroid of geometry-typed data. + * This transform requires the DuckDB `spatial` extension. + */ + centroid: Arg1; +} + +/* A centroidX transform. */ +export interface CentroidX { + /** + * Compute the centroid x-coordinate of geometry-typed data. + * This transform requires the DuckDB `spatial` extension. + */ + centroidX: Arg1; +} + +/* A centroidY transform. */ +export interface CentroidY { + /** + * Compute the centroid y-coordinate of geometry-typed data. + * This transform requires the DuckDB `spatial` extension. + */ + centroidY: Arg1; +} + +/* A geojson transform. */ +export interface GeoJSON { + /** + * Compute a GeoJSON-formatted string from geometry-typed data. + * This transform requires the DuckDB `spatial` extension. + */ + geojson: Arg1; +} + +/* An argmax aggregate transform. */ +export interface Argmax extends AggregateOptions, WindowOptions { + /** + * Find a value of the first column that maximizes the second column. + */ + argmax: Arg2; +} + +/* An argmin aggregate transform. */ +export interface Argmin extends AggregateOptions, WindowOptions { + /** + * Find a value of the first column that minimizes the second column. + */ + argmin: Arg2; +} + +/* An avg (average, or mean) aggregate transform. */ +export interface Avg extends AggregateOptions, WindowOptions { + /** + * Compute the average (mean) value of the given column. + */ + avg: Arg1; +} + +/* A count aggregate transform. */ +export interface Count extends AggregateOptions, WindowOptions { + /** + * Compute the count of records in an aggregation group. + */ + count: Arg0 | Arg1; +} + +/* A first aggregate transform. */ +export interface First extends AggregateOptions, WindowOptions { + /** + * Return the first column value found in an aggregation group. + */ + first: Arg1; +} + +/* A last aggregate transform. */ +export interface Last extends AggregateOptions, WindowOptions { + /** + * Return the last column value found in an aggregation group. + */ + last: Arg1; +} + +/* A max aggregate transform. */ +export interface Max extends AggregateOptions, WindowOptions { + /** + * Compute the maximum value of the given column. + */ + max: Arg1; +} + +/* A min aggregate transform. */ +export interface Min extends AggregateOptions, WindowOptions { + /** + * Compute the minimum value of the given column. + */ + min: Arg1; +} + +/* A median aggregate transform. */ +export interface Median extends AggregateOptions, WindowOptions { + /** + * Compute the median value of the given column. + */ + median: Arg1; +} + +/* A mode aggregate transform. */ +export interface Mode extends AggregateOptions, WindowOptions { + /** + * Compute the mode value of the given column. + */ + mode: Arg1; +} + +/* A product aggregate transform. */ +export interface Product extends AggregateOptions, WindowOptions { + /** + * Compute the product of the given column. + */ + product: Arg1; +} + +/* A quantile aggregate transform. */ +export interface Quantile extends AggregateOptions, WindowOptions { + /** + * Compute the quantile value of the given column at the provided + * probability threshold. For example, 0.5 is the median. + */ + quantile: Arg2; +} + +/* A sum aggregate transform. */ +export interface Sum extends AggregateOptions, WindowOptions { + /** + * Compute the sum of the given column. + */ + sum: Arg1; +} + +/* A row_number window transform. */ +export interface RowNumber extends WindowOptions { + /** + * Compute the 1-based row number over an ordered window partition. + */ + row_number: Arg0; +} + +/* A rank window transform. */ +export interface Rank extends WindowOptions { + /** + * Compute the row rank over an ordered window partition. + * Sorting ties result in gaps in the rank numbers ([1, 1, 3, ...]). + */ + rank: Arg0; +} + +/* A dense_rank window transform. */ +export interface DenseRank extends WindowOptions { + /** + * Compute the dense row rank (no gaps) over an ordered window partition. + * Sorting ties do not result in gaps in the rank numbers ( [1, 1, 2, ...]). + */ + dense_rank: Arg0; +} + +/* A percent_rank window transform. */ +export interface PercentRank extends WindowOptions { + /** + * Compute the percetange rank over an ordered window partition. + */ + percent_rank: Arg0; +} + +/* A cume_dist window transform. */ +export interface CumeDist extends WindowOptions { + /** + * Compute the cumulative distribution value over an ordered window + * partition. Equals the number of partition rows preceding or peer with + * the current row, divided by the total number of partition rows. + */ + cume_dist: Arg0; +} + +/* An ntile window transform. */ +export interface NTile extends WindowOptions { + /** + * Compute an n-tile integer ranging from 1 to the provided argument + * (num_buckets), dividing the partition as equally as possible. + */ + ntile: Arg1; +} + +/* A lag window transform. */ +export interface Lag extends WindowOptions { + /** + * Compute lagging values in a column. Returns the value at the row that is + * `offset` (second argument, default `1`) rows before the current row within + * the window frame. If there is no such row, instead return `default` (third + * argument, default `null`). Both offset and default are evaluated with + * respect to the current row. + */ + lag: Arg3Opt; +} + +/* A lead window transform. */ +export interface Lead extends WindowOptions { + /** + * Compute leading values in a column. Returns the value at the row that is + * `offset` (second argument, default `1`) rows after the current row within + * the window frame. If there is no such row, instead return `default` (third + * argument, default `null`). Both offset and default are evaluated with + * respect to the current row. + */ + lag: Arg3Opt; +} + +/* A first_value window transform. */ +export interface FirstValue extends WindowOptions { + /** + * Get the first value of the given column in the current window frame. + */ + first_value: Arg1; +} + +/* A last_value window transform. */ +export interface LastValue extends WindowOptions { + /** + * Get the last value of the given column in the current window frame. + */ + last_value: Arg1; +} + +/* An nth_value window transform. */ +export interface NthValue extends WindowOptions { + /** + * Get the nth value of the given column in the current window frame, + * counting from one. The second argument is the offset for the nth row. + */ + nth_value: Arg2Opt; +} + +/** A data transform that maps one column value to another. */ +export type ColumnTransform = + | Bin + | DateMonth + | DateMonthDay + | DateDay + | Centroid + | CentroidX + | CentroidY + | GeoJSON; + +/** An aggregate transform that combines multiple values. */ +export type AggregateTransform = + | Argmax + | Argmin + | Avg + | Count + | Max + | Min + | First + | Last + | Max + | Min + | Median + | Mode + | Product + | Quantile + | Sum; + +/* A window transform that operates over a sorted domain. */ +export type WindowTransform = + | RowNumber + | Rank + | DenseRank + | PercentRank + | CumeDist + | NTile + | Rank + | Lag + | Lead + | FirstValue + | LastValue + | NthValue; + +/** A data transform. */ +export type Transform = + | ColumnTransform + | AggregateTransform + | WindowTransform; diff --git a/packages/spec/src/spec/VConcat.ts b/packages/spec/src/spec/VConcat.ts new file mode 100644 index 00000000..80a4c2bd --- /dev/null +++ b/packages/spec/src/spec/VConcat.ts @@ -0,0 +1,9 @@ +import { Component } from './Spec.js'; + +/** A vconcat component. */ +export interface VConcat { + /** + * Vertically concatenate components in a column layout. + */ + vconcat: Component[]; +} diff --git a/packages/spec/src/spec/VSpace.ts b/packages/spec/src/spec/VSpace.ts new file mode 100644 index 00000000..32db577d --- /dev/null +++ b/packages/spec/src/spec/VSpace.ts @@ -0,0 +1,9 @@ +/** A vspace component. */ +export interface VSpace { + /** + * Vertical space to place between components. + * Number values indicate screen pixels. + * String values may use CSS units (em, pt, px, etc). + */ + vspace: number | string; +} diff --git a/packages/spec/src/spec/interactors/Highlight.ts b/packages/spec/src/spec/interactors/Highlight.ts new file mode 100644 index 00000000..95eac6f5 --- /dev/null +++ b/packages/spec/src/spec/interactors/Highlight.ts @@ -0,0 +1,38 @@ +import { ParamRef } from '../Param.js'; + +/** A highlight interactor. */ +export interface Highlight { + /** + * Highlight selected marks by deemphasizing the others. + */ + select: 'highlight'; + /** + * The input selection. Unselected marks are deemphasized. + */ + by: ParamRef; + /** + * The overall opacity of deemphasized marks. + * By default the opacity is set to 0.2. + */ + opacity?: number; + /** + * The fill opacity of deemphasized marks. + * By default the fill opacity is unchanged. + */ + fillOpacity?: number; + /** + * The stroke opacity of deemphasized marks. + * By default the stroke opacity is unchanged. + */ + strokeOpacity?: number; + /** + * The fill color of deemphasized marks. + * By default the fill is unchanged. + */ + fill?: string; + /** + * The stroke color of deemphasized marks. + * By default the stroke is unchanged. + */ + stroke?: string; +} diff --git a/packages/spec/src/spec/interactors/Interval1D.ts b/packages/spec/src/spec/interactors/Interval1D.ts new file mode 100644 index 00000000..f164463f --- /dev/null +++ b/packages/spec/src/spec/interactors/Interval1D.ts @@ -0,0 +1,67 @@ +import { ParamRef } from '../Param.js'; + +/** Styles for rectangular selection brushes. */ +export interface BrushStyles { + /** + * The overall opacity of the brush rectangle. + */ + opacity?: number; + /** + * The fill opacity of the brush rectangle. + */ + fillOpacity?: number; + /** + * The stroke opacity of the brush rectangle. + */ + strokeOpacity?: number; + /** + * The fill color of the brush rectangle. + */ + fill?: string; + /** + * The stroke color of the brush rectangle. + */ + stroke?: string; +} + +/** Options for 1D interval interactors. */ +export interface Interval1DOptions { + /** + * The output selection. A clause of the form `field BETWEEN lo AND hi` + * is added for the currently selected interval [lo, hi]. + */ + as: ParamRef; + /** + * The name of the field (database column) over which the interval + * selection should be defined. If unspecified, the channel field of the + * first valid prior mark definition is used. + */ + field?: string; + /** + * The size of an interative pixel (default `1`). Larger pixel sizes reduce + * the brush resolution, which can reduce the size of data cube indexes. + */ + pixelSize?: number; + /** + * A flag indicating if peer (sibling) marks are when cross-filtering + * (default `true`). If set, peer marks will not be filtered by this + * interactor's selection in cross-filtering setups. + */ + peers?: boolean; + /** + * CSS styles for the brush (SVG `rect`) element. + */ + brush?: BrushStyles; +} + +/** An intervalX interactor. */ +export interface IntervalX extends Interval1DOptions { + /** Select a continuous 1D interval selection over the `x` scale domain. */ + select: 'intervalX'; +} + +/** An intervalY interactor. */ +export interface IntervalY extends Interval1DOptions { + /** Select a continuous 1D interval selection over the `y` scale domain. */ + select: 'intervalY'; +} diff --git a/packages/spec/src/spec/interactors/Interval2D.ts b/packages/spec/src/spec/interactors/Interval2D.ts new file mode 100644 index 00000000..d4844520 --- /dev/null +++ b/packages/spec/src/spec/interactors/Interval2D.ts @@ -0,0 +1,48 @@ +import { ParamRef } from '../Param.js'; +import { BrushStyles } from './Interval1D.js'; + +/** Options for 2D interval interactors. */ +export interface Interval2DOptions { + /** + * The output selection. A clause of the form + * `(xfield BETWEEN x1 AND x2) AND (yfield BETWEEN y1 AND y2)` + * is added for the currently selected intervals. + */ + as: ParamRef; + /** + * The name of the field (database column) over which the `x`-component + * of the interval selection should be defined. If unspecified, the `x` + * channel field of the first valid prior mark definition is used. + */ + xfield?: string; + /** + * The name of the field (database column) over which the `y`-component + * of the interval selection should be defined. If unspecified, the `y` + * channel field of the first valid prior mark definition is used. + */ + yfield?: string; + /** + * The size of an interative pixel (default `1`). Larger pixel sizes reduce + * the brush resolution, which can reduce the size of data cube indexes. + */ + pixelSize?: number; + /** + * A flag indicating if peer (sibling) marks are when cross-filtering + * (default `true`). If set, peer marks will not be filtered by this + * interactor's selection in cross-filtering setups. + */ + peers?: boolean; + /** + * CSS styles for the brush (SVG `rect`) element. + */ + brush?: BrushStyles; +} + +/** An intervalXY interactor. */ +export interface IntervalXY extends Interval2DOptions { + /** + * Select a continuous 2D interval selection + * over the `x` and `y` scale domains. + */ + select: 'intervalXY'; +} diff --git a/packages/spec/src/spec/interactors/Nearest.ts b/packages/spec/src/spec/interactors/Nearest.ts new file mode 100644 index 00000000..83a1a12b --- /dev/null +++ b/packages/spec/src/spec/interactors/Nearest.ts @@ -0,0 +1,28 @@ +import { ParamRef } from '../Param.js'; + +/** Options for nearest interactors. */ +export interface NearestOptions { + /** + * The output selection. A clause of the form `field = value` + * is added for the currently nearest value. + */ + as: ParamRef; + /** + * The name of the field (database column) over which the nearest + * selection should be defined. If unspecified, the channel field of the + * first valid prior mark definition is used. + */ + field?: string; +} + +/** A nearestX interactor. */ +export interface NearestX extends NearestOptions { + /** Select the **x** domain value of the mark closest to the pointer. */ + select: 'nearestX'; +} + +/** A nearestY interactor. */ +export interface NearestY extends NearestOptions { + /** Select the **y** domain value of the mark closest to the pointer. */ + select: 'nearestY'; +} diff --git a/packages/spec/src/spec/interactors/PanZoom.ts b/packages/spec/src/spec/interactors/PanZoom.ts new file mode 100644 index 00000000..3257253c --- /dev/null +++ b/packages/spec/src/spec/interactors/PanZoom.ts @@ -0,0 +1,65 @@ +import { ParamRef } from '../Param.js'; + +/** Options for pan/zoom interactors. */ +export interface PanZoomOptions { + /** + * The output selection for the `x` domain. + * A clause of the form `field BETWEEN x1 AND x2` is added for the + * current pan/zom interval [x1, x2]. + */ + x?: ParamRef; + /** + * The output selection for the `y` domain. + * A clause of the form `field BETWEEN y1 AND y2` is added for the + * current pan/zom interval [y1, y2]. + */ + y?: ParamRef; + /** + * The name of the field (database column) over which the `x`-component + * of the pan/zoom interval should be defined. If unspecified, the `x` + * channel field of the first valid prior mark definition is used. + */ + xfield?: string; + /** + * The name of the field (database column) over which the `y`-component + * of the pan/zoom interval should be defined. If unspecified, the `y` + * channel field of the first valid prior mark definition is used. + */ + yfield?: string; +} + +/** A pan interactor. */ +export interface Pan extends PanZoomOptions { + /** Pan a plot along both the `x` and `y` scales. */ + select: 'pan'; +} + +/** A panX interactor. */ +export interface PanX extends PanZoomOptions { + /** Pan a plot along the `x` scale only. */ + select: 'panX'; +} + +/** A panY interactor. */ +export interface PanY extends PanZoomOptions { + /** Pan a plot along the `y` scale only. */ + select: 'panY'; +} + +/** A panZoom interactor. */ +export interface PanZoom extends PanZoomOptions { + /** Pan and zoom a plot along both the `x` and `y` scales. */ + select: 'panZoom'; +} + +/** A panZoomX interactor. */ +export interface PanZoomX extends PanZoomOptions { + /** Pan and zoom a plot along the `x` scale only. */ + select: 'panZoomX'; +} + +/** A panZoomY interactor. */ +export interface PanZoomY extends PanZoomOptions { + /** Pan and zoom a plot along the `y` scale only. */ + select: 'panZoomY'; +} diff --git a/packages/spec/src/spec/interactors/Toggle.ts b/packages/spec/src/spec/interactors/Toggle.ts new file mode 100644 index 00000000..470ff915 --- /dev/null +++ b/packages/spec/src/spec/interactors/Toggle.ts @@ -0,0 +1,56 @@ +import { ParamRef } from '../Param.js'; + +/** Options for toggle interactors. */ +export interface ToggleOptions { + /** + * The output selection. A clause of the form + * `(field = value1) OR (field = value2) ...` + * is added for the currently selected values. + */ + as: ParamRef; + /** + * A flag indicating if peer (sibling) marks are when cross-filtering + * (default `true`). If set, peer marks will not be filtered by this + * interactor's selection in cross-filtering setups. + */ + peers?: boolean; +} + +/** A toggle interactor. */ +export interface Toggle extends ToggleOptions { + /** Select individal values. */ + select: 'toggle'; + /** + * The encoding channels over which to select values. + * For a selected mark, selection clauses will cover + * the backing data fields for each channel. + */ + channels: string[]; +} + +/** A toggleX interactor. */ +export interface ToggleX extends ToggleOptions { + /** + * Select individal values in the `x` scale domain. + * Clicking or touching a mark toggles its selection status. + */ + select: 'toggleX'; +} + +/** A toggleY interactor. */ +export interface ToggleY extends ToggleOptions { + /** + * Select individal values in the `y` scale domain. + * Clicking or touching a mark toggles its selection status. + */ + select: 'toggleY'; +} + +/** A toggleColor interactor. */ +export interface ToggleColor extends ToggleOptions { + /** + * Select individal values in the `color` scale domain. + * Clicking or touching a mark toggles its selection status. + */ + select: 'toggleColor'; +} diff --git a/packages/spec/src/spec/marks/Area.ts b/packages/spec/src/spec/marks/Area.ts new file mode 100644 index 00000000..6b1ec0c5 --- /dev/null +++ b/packages/spec/src/spec/marks/Area.ts @@ -0,0 +1,154 @@ +import { + ChannelValue, ChannelValueSpec, CurveOptions, + MarkData, MarkOptions, StackOptions +} from './Marks.js'; + +/** Options for the area, areaX, and areaY marks. */ +export interface AreaOptions extends MarkOptions, StackOptions, CurveOptions { + /** + * The required primary (starting, often left) horizontal position channel, + * representing the area’s baseline, typically bound to the *x* scale. For + * areaX, setting this option disables the implicit stackX transform. + */ + x1?: ChannelValueSpec; + + /** + * The optional secondary (ending, often right) horizontal position channel, + * representing the area’s topline, typically bound to the *x* scale; if not + * specified, **x1** is used. For areaX, setting this option disables the + * implicit stackX transform. + */ + x2?: ChannelValueSpec; + + /** + * The required primary (starting, often bottom) vertical position channel, + * representing the area’s baseline, typically bound to the *y* scale. For + * areaY, setting this option disables the implicit stackY transform. + */ + y1?: ChannelValueSpec; + + /** + * The optional secondary (ending, often top) vertical position channel, + * representing the area’s topline, typically bound to the *y* scale; if not + * specified, **y1** is used. For areaY, setting this option disables the + * implicit stackY transform. + */ + y2?: ChannelValueSpec; + + /** + * An optional ordinal channel for grouping data into (possibly stacked) + * series to be drawn as separate areas; defaults to **fill** if a channel, or + * **stroke** if a channel. + */ + z?: ChannelValue; +} + +/** Options for the areaX mark. */ +export interface AreaXOptions extends Omit { + /** + * The horizontal position (or length) channel, typically bound to the *x* + * scale. + * + * If neither **x1** nor **x2** is specified, an implicit stackX transform is + * applied and **x** defaults to the identity function, assuming that *data* = + * [*x₀*, *x₁*, *x₂*, …]. Otherwise, if only one of **x1** or **x2** is + * specified, the other defaults to **x**, which defaults to zero. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel, typically bound to the *y* scale; defaults + * to the zero-based index of the data [0, 1, 2, …]. + */ + y?: ChannelValueSpec; +} + +/** Options for the areaY mark. */ +export interface AreaYOptions extends Omit { + /** + * The horizontal position channel, typically bound to the *x* scale; defaults + * to the zero-based index of the data [0, 1, 2, …]. + */ + x?: ChannelValueSpec; + + /** + * The vertical position (or length) channel, typically bound to the *y* + * scale. + * + * If neither **y1** nor **y2** is specified, an implicit stackY transform is + * applied and **y** defaults to the identity function, assuming that *data* = + * [*y₀*, *y₁*, *y₂*, …]. Otherwise, if only one of **y1** or **y2** is + * specified, the other defaults to **y**, which defaults to zero. + */ + y?: ChannelValueSpec; +} + +/** The area mark. */ +export interface Area extends MarkData, AreaOptions { + /** + * An area mark. The area mark is rarely used directly; it is only needed + * when the baseline and topline have neither *x* nor *y* values in common. + * Use areaY for a horizontal orientation where the baseline and topline + * share *x* values, or areaX for a vertical orientation where the baseline + * and topline share *y* values. + */ + mark: 'area'; +} + +/** The areaX mark. */ +export interface AreaX extends MarkData, AreaXOptions { + /** + * A vertically-oriented area mark, where the baseline and topline share + * **y** values, as in a time-series area chart where time goes up↑. + * + * If neither **x1** nor **x2** is specified, an implicit stackX transform is + * applied and **x** defaults to the identity function, assuming that *data* = + * [*x₀*, *x₁*, *x₂*, …]. Otherwise, if only one of **x1** or **x2** is + * specified, the other defaults to **x**, which defaults to zero. + * + * If an **interval** is specified, **y** values are binned accordingly, + * allowing zeroes for empty bins instead of interpolating across gaps. This is + * recommended to “regularize” sampled data; for example, if your data + * represents timestamped observations and you expect one observation per day, + * use *day* as the **interval**. + * + * Variable aesthetic channels are supported: if the **fill** is defined as a + * channel, the area will be broken into contiguous overlapping sections when + * the fill color changes; the fill color will apply to the interval spanning + * the current data point and the following data point. This behavior also + * applies to the **fillOpacity**, **stroke**, **strokeOpacity**, + * **strokeWidth**, **opacity**, **href**, **title**, and **ariaLabel** + * channels. When any of these channels are used, setting an explicit **z** + * channel (possibly to null) is strongly recommended. + */ + mark: 'areaX'; +} + +/** The areaY mark. */ +export interface AreaY extends MarkData, AreaYOptions { + /** + * A horizontally-oriented area mark, where the baseline and topline share + * **x** values, as in a time-series area chart where time goes right→. + * + * If neither **y1** nor **y2** is specified, an implicit stackY transform is + * applied and **y** defaults to the identity function, assuming that *data* = + * [*y₀*, *y₁*, *y₂*, …]. Otherwise, if only one of **y1** or **y2** is + * specified, the other defaults to **y**, which defaults to zero. + * + * If an **interval** is specified, **x** values are binned accordingly, + * allowing zeroes for empty bins instead of interpolating across gaps. This is + * recommended to “regularize” sampled data; for example, if your data + * represents timestamped observations and you expect one observation per day, + * use *day* as the **interval**. + * + * Variable aesthetic channels are supported: if the **fill** is defined as a + * channel, the area will be broken into contiguous overlapping sections when + * the fill color changes; the fill color will apply to the interval spanning + * the current data point and the following data point. This behavior also + * applies to the **fillOpacity**, **stroke**, **strokeOpacity**, + * **strokeWidth**, **opacity**, **href**, **title**, and **ariaLabel** + * channels. When any of these channels are used, setting an explicit **z** + * channel (possibly to null) is strongly recommended. + */ + mark: 'areaY'; +} diff --git a/packages/spec/src/spec/marks/Arrow.ts b/packages/spec/src/spec/marks/Arrow.ts new file mode 100644 index 00000000..1120c11b --- /dev/null +++ b/packages/spec/src/spec/marks/Arrow.ts @@ -0,0 +1,108 @@ +import { ParamRef } from '../Param.js'; +import { ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** Options for the arrow mark. */ +export interface ArrowOptions extends MarkOptions { + /** + * The horizontal position, for vertical arrows; typically bound to the *x* + * scale; shorthand for setting defaults for both **x1** and **x2**. + */ + x?: ChannelValueSpec; + + /** + * The vertical position, for horizontal arrows; typically bound to the *y* + * scale; shorthand for setting defaults for both **y1** and **y2**. + */ + y?: ChannelValueSpec; + + /** + * The starting horizontal position; typically bound to the *x* scale; also + * sets a default for **x2**. + */ + x1?: ChannelValueSpec; + + /** + * The starting vertical position; typically bound to the *y* scale; also + * sets a default for **y2**. + */ + y1?: ChannelValueSpec; + + /** + * The ending horizontal position; typically bound to the *x* scale; also + * sets a default for **x1**. + */ + x2?: ChannelValueSpec; + + /** + * The ending vertical position; typically bound to the *y* scale; also sets + * a default for **y1**. + */ + y2?: ChannelValueSpec; + + /** + * The angle, a constant in degrees, between the straight line intersecting + * the arrow’s two control points and the outgoing tangent direction of the + * arrow from the start point. The angle must be within ±90°; a positive + * angle will produce a clockwise curve, while a negative angle will produce + * a counterclockwise curve; zero (the default) will produce a straight line. + * Use true for 22.5°. + */ + bend?: number | boolean | ParamRef; + + /** + * How pointy the arrowhead is, in degrees; a constant typically between 0° + * and 180°, and defaults to 60°. + */ + headAngle?: number | ParamRef; + + /** + * The size of the arrowhead relative to the **strokeWidth**; a constant. + * Assuming the default of stroke width 1.5px, this is the length of the + * arrowhead’s side in pixels. + */ + headLength?: number | ParamRef; + + /** + * Shorthand to set the same default for **insetStart** and **insetEnd**. + */ + inset?: number | ParamRef; + + /** + * The starting inset, a constant in pixels; defaults to 0. A positive inset + * shortens the arrow by moving the starting point towards the endpoint + * point, while a negative inset extends it by moving the starting point in + * the opposite direction. A positive starting inset may be useful if the + * arrow emerges from a dot. + */ + insetStart?: number | ParamRef; + + /** + * The ending inset, a constant in pixels; defaults to 0. A positive inset + * shortens the arrow by moving the ending point towards the starting point, + * while a negative inset extends it by moving the ending point in the + * opposite direction. A positive ending inset may be useful if the arrow + * points to a dot. + */ + insetEnd?: number | ParamRef; + + /** + * The sweep order; defaults to 1 indicating a positive (clockwise) bend + * angle; -1 indicates a negative (anticlockwise) bend angle; 0 effectively + * clears the bend angle. If set to *-x*, the bend angle is flipped when the + * ending point is to the left of the starting point — ensuring all arrows + * bulge up (down if bend is negative); if set to *-y*, the bend angle is + * flipped when the ending point is above the starting point — ensuring all + * arrows bulge right (left if bend is negative); the sign is negated for + * *+x* and *+y*. + */ + sweep?: number | '+x' | '-x' | '+y' | '-y' | ParamRef; +} + +/** The arrow mark. */ +export interface Arrow extends MarkData, ArrowOptions { + /** + * An arrow mark, drawing (possibly swoopy) arrows connecting pairs of + * points. + */ + mark: 'arrow'; +} diff --git a/packages/spec/src/spec/marks/Axis.ts b/packages/spec/src/spec/marks/Axis.ts new file mode 100644 index 00000000..cf09f93a --- /dev/null +++ b/packages/spec/src/spec/marks/Axis.ts @@ -0,0 +1,305 @@ +import { ParamRef } from '../Param.js'; +import { Interval } from '../PlotTypes.js'; +import { MarkOptions } from './Marks.js'; +import { RuleXOptions, RuleYOptions } from './Rule.js'; +import { TextOptions } from './Text.js'; +import { TickXOptions, TickYOptions } from './Tick.js'; + +/** The scale options used by axis and grid marks. */ +interface ScaleOptions { + /** + * Enforces uniformity for data at regular intervals, such as integer values + * or daily samples. The interval may be one of: + * + * - a named time interval such as *day* (for date intervals) + * - a number (for number intervals), defining intervals at integer multiples of *n* + * + * This option sets the internal transform to the given interval’s + * *interval*.floor function. In addition, the default **domain** will align + * with interval boundaries. + */ + interval?: Interval | ParamRef; + + /** + * The desired approximate number of axis ticks, or an explicit array of tick + * values, or an interval such as *day* or *month*. + */ + ticks?: number | Interval | any[] | ParamRef; + + /** + * The length of axis tick marks in pixels; negative values extend in the + * opposite direction. Defaults to 6 for *x* and *y* axes and *color* and + * *opacity* *ramp* legends, and 0 for *fx* and *fy* axes. + */ + tickSize?: number | ParamRef; + + /** + * The desired approximate spacing between adjacent axis ticks, affecting the + * default **ticks**; defaults to 80 pixels for *x* and *fx*, and 35 pixels + * for *y* and *fy*. + */ + tickSpacing?: number | ParamRef; + + /** + * The distance between an axis tick mark and its associated text label (in + * pixels); often defaults to 3, but may be affected by **xTickSize** and + * **xTickRotate**. + */ + tickPadding?: number | ParamRef; + + /** + * How to format inputs (abstract values) for axis tick labels; one of: + * + * - a [d3-format][1] string for numeric scales + * - a [d3-time-format][2] string for temporal scales + * + * [1]: https://d3js.org/d3-time + * [2]: https://d3js.org/d3-time-format + */ + tickFormat?: string | null | ParamRef; + + /** + * The rotation angle of axis tick labels in degrees clocksize; defaults to 0. + */ + tickRotate?: number | ParamRef; + + /** + * A textual label to show on the axis or legend; if null, show no label. By + * default the scale label is inferred from channel definitions, possibly with + * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value. + * + * For axes and legends only. + */ + label?: string | null | ParamRef; + + /** + * Where to place the axis **label** relative to the plot’s frame. For + * vertical position scales (*y* and *fy*), may be *top*, *bottom*, or + * *center*; for horizontal position scales (*x* and *fx*), may be *left*, + * *right*, or *center*. Defaults to *center* for ordinal scales (including + * *fx* and *fy*), and otherwise *top* for *y*, and *right* for *x*. + */ + labelAnchor?: 'top' | 'right' | 'bottom' | 'left' | 'center' | ParamRef; + + /** + * The axis **label** position offset (in pixels); default depends on margins + * and orientation. + */ + labelOffset?: number | ParamRef; + + /** + * Whether to apply a directional arrow such as → or ↑ to the scale label. If + * *auto* (the default), the presence of the arrow depends on whether the + * scale is ordinal. + */ + labelArrow?: 'auto' | 'up' | 'right' | 'down' | 'left' | 'none' | true | false | null | ParamRef; +} + +/** The subset of scale options for grids. */ +type GridScaleOptions = Pick; + +/** The subset of scale options for axes. */ +type AxisScaleOptions = Pick; + +/** Options for the grid marks. */ +export interface GridOptions extends GridScaleOptions { + /** + * The side of the frame on which to place the axis: *top* or *bottom* for + * horizontal axes (axisX and axisFx) and their associated vertical grids + * (gridX and gridFx), or *left* or *right* for vertical axes (axisY and + * axisFY) and their associated horizontal grids (gridY and gridFy). + * + * The default **anchor** depends on the associated scale: + * + * - *x* - *bottom* + * - *y* - *left* + * - *fx* - *top* if there is a *bottom* *x* axis, and otherwise *bottom* + * - *fy* - *right* if there is a *left* *y* axis, and otherwise *right* + * + * For grids, the **anchor** also affects the extent of grid lines when the + * opposite dimension is specified (**x** for gridY and **y** for gridX). + */ + anchor?: 'top' | 'right' | 'bottom' | 'left' | ParamRef; + + /** + * A shorthand for setting both **fill** and **stroke**; affects the stroke of + * tick vectors and grid rules, and the fill of tick texts and axis label + * texts; defaults to *currentColor*. + */ + color?: MarkOptions['stroke']; + + /** + * A shorthand for setting both **fillOpacity** and **strokeOpacity**; affects + * the stroke opacity of tick vectors and grid rules, and the fill opacity of + * tick texts and axis label texts; defaults to 1 for axes and 0.1 for grids. + */ + opacity?: MarkOptions['opacity']; +} + +/** Options for the axis marks. */ +export interface AxisOptions extends GridOptions, MarkOptions, TextOptions, AxisScaleOptions { + /** The tick text **stroke**, say for a *white* outline to improve legibility; defaults to null. */ + textStroke?: MarkOptions['stroke']; + /** The tick text **strokeOpacity**; defaults to 1; has no effect unless **textStroke** is set. */ + textStrokeOpacity?: MarkOptions['strokeOpacity']; + /** The tick text **strokeWidth**; defaults to 4; has no effect unless **textStroke** is set. */ + textStrokeWidth?: MarkOptions['strokeWidth']; +} + +/** Options for the axisX and axisFx marks. */ +export interface AxisXOptions extends AxisOptions, Omit {} + +/** Options for the axisY and axisFy marks. */ +export interface AxisYOptions extends AxisOptions, Omit {} + +/** Options for the gridX and gridFx marks. */ +export interface GridXOptions extends GridOptions, Omit {} + +/** Options for the gridY and gridFy marks. */ +export interface GridYOptions extends GridOptions, Omit {} + +/** The axisX mark. */ +export interface AxisX extends AxisXOptions { + /** + * An axis mark to document the visual encoding of the horizontal position + * *x* scale, comprised of (up to) three marks: a vector for ticks, a text + * for tick labels, and another text for an axis label. The data defaults to + * tick values sampled from the *x* scale’s domain; if desired, use one of + * the **ticks**, **tickSpacing**, or **interval** options. + * + * The **facetAnchor** option defaults to *bottom-empty* if **anchor** is + * *bottom*, and *top-empty* if **anchor** is *top*. The default margins + * likewise depend on **anchor** as follows; in order of **marginTop**, + * **marginRight**, **marginBottom**, and **marginLeft**, in pixels: + * + * - *top* - 30, 20, 0, 20 + * - *bottom* - 0, 20, 30, 20 + * + * For simplicity, and for consistent layout across plots, default axis margins + * are not affected by tick labels. If tick labels are too long, either increase + * the margin or shorten the labels: use the *k* SI-prefix tick format; use the + * **transform** *y*-scale option to show thousands or millions; or use the + * **textOverflow** and **lineWidth** options to clip. + */ + mark: 'axisX'; +} + +/** The axisFx mark. */ +export interface AxisFx extends AxisXOptions { + /** + * An axis mark to document the visual encoding of the horizontal facet + * position *fx* scale, comprised of (up to) three marks: a vector for ticks, + * a text for tick labels, and another text for an axis label. The data + * defaults to the *fx* scale’s domain; if desired, use one of the **ticks**, + * **tickSpacing**, or **interval** options. + * + * The **facetAnchor** and **frameAnchor** options defaults to **anchor**. The + * default margins likewise depend on **anchor** as follows; in order of + * **marginTop**, **marginRight**, **marginBottom**, and **marginLeft**, in + * pixels: + * + * - *top* - 30, 20, 0, 20 + * - *bottom* - 0, 20, 30, 20 + * + * For simplicity, and for consistent layout across plots, default axis margins + * are not affected by tick labels. If tick labels are too long, either increase + * the margin or shorten the labels: use the *k* SI-prefix tick format; use the + * **transform** *y*-scale option to show thousands or millions; or use the + * **textOverflow** and **lineWidth** options to clip. + */ + mark: 'axisFx'; +} + +/** The axisY mark. */ +export interface AxisY extends AxisYOptions { + /** + * An axis mark to document the visual encoding of the vertical position *y* + * scale, comprised of (up to) three marks: a vector for ticks, a text for + * tick labels, and another text for an axis label. The data defaults to tick + * values sampled from the *y* scale’s domain; if desired, use one of the + * **ticks**, **tickSpacing**, or **interval** options. + * + * The **facetAnchor** option defaults to *right-empty* if **anchor** is + * *right*, and *left-empty* if **anchor** is *left*. The default margins + * likewise depend on **anchor** as follows; in order of **marginTop**, + * **marginRight**, **marginBottom**, and **marginLeft**, in pixels: + * + * - *right* - 20, 40, 20, 0 + * - *left* - 20, 0, 20, 40 + * + * For simplicity, and for consistent layout across plots, default axis + * margins are not affected by tick labels. If tick labels are too long, + * either increase the margin or shorten the labels: use the *k* SI-prefix + * tick format; or use the **textOverflow** and **lineWidth** options to + * clip. + */ + mark: 'axisY'; +} + +/** The axisFy mark. */ +export interface AxisFy extends AxisYOptions { + /** + * An axis mark to document the visual encoding of the vertical facet + * position *fy* scale, comprised of (up to) three marks: a vector for ticks, + * a text for tick labels, and another text for an axis label. The data + * defaults to the *fy* scale’s domain; if desired, use one of the **ticks**, + * **tickSpacing**, or **interval** options. + * + * The **facetAnchor** option defaults to *right-empty* if **anchor** is + * *right*, and *left-empty* if **anchor** is *left*. The default margins + * likewise depend on **anchor** as follows; in order of **marginTop**, + * **marginRight**, **marginBottom**, and **marginLeft**, in pixels: + * + * - *right* - 20, 40, 20, 0 + * - *left* - 20, 0, 20, 40 + * + * For simplicity, and for consistent layout across plots, default axis + * margins are not affected by tick labels. If tick labels are too long, + * either increase the margin or shorten the labels: use the *k* SI-prefix + * tick format; or use the **textOverflow** and **lineWidth** options to + * clip. + */ + mark: 'axisFy'; +} + +/** The gridX mark. */ +export interface GridX extends GridXOptions { + /** + * A horizontally-positioned ruleX mark (a vertical line, |) that renders a + * grid for the *x* scale. The data defaults to tick values sampled from the + * *x* scale’s domain; if desired, use one of the **ticks**, **tickSpacing**, + * or **interval** options. + */ + mark: 'gridX'; +} + +/** The gridFx mark. */ +export interface GridFx extends GridXOptions { + /** + * A horizontally-positioned ruleX mark (a vertical line, |) that renders a + * grid for the *fx* scale. The data defaults to the *fx* scale’s domain; + * if desired, use the **ticks** option. + */ + mark: 'gridFx'; +} + +/** The gridY mark. */ +export interface GridY extends GridYOptions { + /** + * A vertically-positioned ruleY mark (a horizontal line, —) that renders a + * grid for the *y* scale. The data defaults to tick values sampled from the + * *y* scale’s domain; if desired, use one of the **ticks**, **tickSpacing**, + * or **interval** options. + */ + mark: 'gridY'; +} + +/** The gridFy mark. */ +export interface GridFy extends GridYOptions { + /** + * A vertically-positioned ruleY mark (a horizontal line, —) that renders a + * grid for the *fy* scale. The data defaults to the *fy* scale’s domain; + * if desired, use the **ticks** option. + */ + mark: 'gridFy'; +} diff --git a/packages/spec/src/spec/marks/Bar.ts b/packages/spec/src/spec/marks/Bar.ts new file mode 100644 index 00000000..3bc839e7 --- /dev/null +++ b/packages/spec/src/spec/marks/Bar.ts @@ -0,0 +1,160 @@ +import { ParamRef } from '../Param.js'; +import { Interval } from '../PlotTypes.js'; +import { ChannelValueIntervalSpec, ChannelValueSpec, InsetOptions, MarkData, MarkOptions, StackOptions } from './Marks.js'; +import { RectCornerOptions } from './Rect.js'; + +/** Options for the barX and barY marks. */ +interface BarOptions extends MarkOptions, InsetOptions, RectCornerOptions, StackOptions { + /** + * How to convert a continuous value (**x** for barX, or **y** for barY) into + * an interval (**x1** and **x2** for barX, or **y1** and **y2** for barY); + * one of: + * + * - a named time interval such as *day* (for date intervals) + * - a number (for number intervals), defining intervals at integer multiples of *n* + * + * Setting this option disables the implicit stack transform (stackX for barX, + * or stackY for barY). + */ + interval?: Interval | ParamRef; +} + +/** Options for the barX mark. */ +export interface BarXOptions extends BarOptions { + /** + * The horizontal position (or length/width) channel, typically bound to the + * *x* scale. + * + * If neither **x1** nor **x2** nor **interval** is specified, an implicit + * stackX transform is applied and **x** defaults to the identity function, + * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]. Otherwise if an **interval** + * is specified, then **x1** and **x2** are derived from **x**, representing + * the lower and upper bound of the containing interval, respectively. + * Otherwise, if only one of **x1** or **x2** is specified, the other + * defaults to **x**, which defaults to zero. + */ + x?: ChannelValueIntervalSpec; + + /** + * The required primary (starting, often left) horizontal position channel, + * typically bound to the *x* scale. Setting this option disables the + * implicit stackX transform. + * + * If *x* represents ordinal values, use a cell mark instead. + */ + x1?: ChannelValueSpec; + + /** + * The required secondary (ending, often right) horizontal position channel, + * typically bound to the *x* scale. Setting this option disables the + * implicit stackX transform. + * + * If *x* represents ordinal values, use a cell mark instead. + */ + x2?: ChannelValueSpec; + + /** + * The optional vertical position of the bar; a ordinal channel typically + * bound to the *y* scale. If not specified, the bar spans the vertical + * extent of the frame; otherwise the *y* scale must be a *band* scale. + * + * If *y* represents quantitative or temporal values, use a rectX mark + * instead. + */ + y?: ChannelValueSpec; +} + +/** Options for the barY mark. */ +export interface BarYOptions extends BarOptions { + /** + * The vertical position (or length/height) channel, typically bound to the + * *y* scale. + * + * If neither **y1** nor **y2** nor **interval** is specified, an implicit + * stackY transform is applied and **y** defaults to the identity function, + * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]. Otherwise if an **interval** + * is specified, then **y1** and **y2** are derived from **y**, representing + * the lower and upper bound of the containing interval, respectively. + * Otherwise, if only one of **y1** or **y2** is specified, the other + * defaults to **y**, which defaults to zero. + */ + y?: ChannelValueIntervalSpec; + + /** + * The required primary (starting, often bottom) vertical position channel, + * typically bound to the *y* scale. Setting this option disables the + * implicit stackY transform. + * + * If *y* represents ordinal values, use a cell mark instead. + */ + y1?: ChannelValueSpec; + + /** + * The required secondary (ending, often top) horizontal position channel, + * typically bound to the *y* scale. Setting this option disables the + * implicit stackY transform. + * + * If *y* represents ordinal values, use a cell mark instead. + */ + y2?: ChannelValueSpec; + + /** + * The optional horizontal position of the bar; a ordinal channel typically + * bound to the *x* scale. If not specified, the bar spans the horizontal + * extent of the frame; otherwise the *x* scale must be a *band* scale. + * + * If *x* represents quantitative or temporal values, use a rectY mark + * instead. + */ + x?: ChannelValueSpec; +} + +/** The barX mark. */ +export interface BarX extends MarkData, BarXOptions { + /** + * A horizontal bar mark. The required *x* values should be quantitative or + * temporal, and the optional *y* values should be ordinal. + * + * If neither **x1** nor **x2** nor **interval** is specified, an implicit + * stackX transform is applied and **x** defaults to the identity function, + * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]. Otherwise if an **interval** + * is specified, then **x1** and **x2** are derived from **x**, representing + * the lower and upper bound of the containing interval, respectively. + * Otherwise, if only one of **x1** or **x2** is specified, the other + * defaults to **x**, which defaults to zero. + * + * The optional **y** ordinal channel specifies the vertical position; it is + * typically bound to the *y* scale, which must be a *band* scale. If the + * **y** channel is not specified, the bar will span the vertical extent of + * the plot’s frame. + * + * If *y* is quantitative, use the rectX mark instead. + * If *x* is ordinal, use the cell mark instead. + */ + mark: 'barX'; +} + +/** The barY mark. */ +export interface BarY extends MarkData, BarYOptions { + /** + * A vertical bar mark. The required *y* values should be quantitative or + * temporal, and the optional *x* values should be ordinal. + * + * If neither **y1** nor **y2** nor **interval** is specified, an implicit + * stackY transform is applied and **y** defaults to the identity function, + * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]. Otherwise if an **interval** + * is specified, then **y1** and **y2** are derived from **y**, representing + * the lower and upper bound of the containing interval, respectively. + * Otherwise, if only one of **y1** or **y2** is specified, the other + * defaults to **y**, which defaults to zero. + * + * The optional **x** ordinal channel specifies the horizontal position; it + * is typically bound to the *x* scale, which must be a *band* scale. If the + * **x** channel is not specified, the bar will span the horizontal extent of + * the plot’s frame. + * + * If *x* is quantitative, use the rectY mark instead. + * If *y* is ordinal, use the cell mark instead. + */ + mark: 'barY'; +} diff --git a/packages/spec/src/spec/marks/Cell.ts b/packages/spec/src/spec/marks/Cell.ts new file mode 100644 index 00000000..20d7a84f --- /dev/null +++ b/packages/spec/src/spec/marks/Cell.ts @@ -0,0 +1,62 @@ +import { ChannelValueSpec, InsetOptions, MarkData, MarkOptions } from './Marks.js'; +import { RectCornerOptions } from './Rect.js'; + +/** Options for the cell mark. */ +export interface CellOptions extends MarkOptions, InsetOptions, RectCornerOptions { + /** + * The horizontal position of the cell; an optional ordinal channel typically + * bound to the *x* scale. If not specified, the cell spans the horizontal + * extent of the frame; otherwise the *x* scale must be a *band* scale. + * + * If *x* represents quantitative or temporal values, use a barX mark instead; + * if *y* is also quantitative or temporal, use a rect mark. + */ + x?: ChannelValueSpec; + + /** + * The vertical position of the cell; an optional ordinal channel typically + * bound to the *y* scale. If not specified, the cell spans the vertical + * extent of the frame; otherwise the *y* scale must be a *band* scale. + * + * If *y* represents quantitative or temporal values, use a barY mark instead; + * if *x* is also quantitative or temporal, use a rect mark. + */ + y?: ChannelValueSpec; +} + +/** The cell mark. */ +export interface Cell extends MarkData, CellOptions { + /** + * A rectangular cell mark. Along with **x** and/or **y**, a **fill** channel + * is typically specified to encode value as color. + * + * If neither **x** nor **y** are specified, *data* is assumed to be an array of + * pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, + * *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. + * + * Both **x** and **y** should be ordinal; if only **x** is quantitative (or + * temporal), use a barX mark; if only **y** is quantitative, use a barY mark; + * if both are quantitative, use a rect mark. + */ + mark: 'cell'; +} + +/** The cellX mark. */ +export interface CellX extends MarkData, CellOptions { + /** + * Like cell, but **x** defaults to the zero-based index [0, 1, 2, …], and if + * **stroke** is not a channel, **fill** defaults to the identity function, + * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]. + */ + mark: 'cellX'; +} + +/** The cellY mark. */ +export interface CellY extends MarkData, CellOptions { + /** + * Like cell, but **y** defaults to the zero-based index [0, 1, 2, …], and if + * **stroke** is not a channel, **fill** defaults to the identity function, + * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]. + */ + mark: 'cellY'; +} diff --git a/packages/spec/src/spec/marks/Contour.ts b/packages/spec/src/spec/marks/Contour.ts new file mode 100644 index 00000000..e65a1668 --- /dev/null +++ b/packages/spec/src/spec/marks/Contour.ts @@ -0,0 +1,25 @@ +import { ParamRef } from '../Param.js'; +import { MarkData, MarkOptions } from './Marks.js'; +import { Grid2DOptions } from './Raster.js'; + +export interface ContourOptions extends MarkOptions, Grid2DOptions { + /** + * The number of contour thresholds to subdivide the domain into discrete + * level sets; defaults to 10. One of: + * + * - a count representing the desired number of bins + * - an array of *n* threshold values for *n* - 1 bins + */ + thresholds?: number | number[] | ParamRef; +} + +/** The contour mark. */ +export interface Contour extends MarkData, ContourOptions { + /** + * A contour mark that draws isolines to delineate regions above and below a + * particular continuous value. It is often used to convey densities as a + * height field. The special column name "density" can be used to map density + * values to the fill or stroke options. + */ + mark: 'contour'; +} diff --git a/packages/spec/src/spec/marks/Delaunay.ts b/packages/spec/src/spec/marks/Delaunay.ts new file mode 100644 index 00000000..9a02e51d --- /dev/null +++ b/packages/spec/src/spec/marks/Delaunay.ts @@ -0,0 +1,95 @@ +import { + ChannelValue, ChannelValueSpec, CurveOptions, + MarkData, MarkOptions, MarkerOptions +} from './Marks.js'; + +/** Options for the Delaunay marks. */ +export interface DelaunayOptions extends MarkOptions, MarkerOptions, CurveOptions { + /** The horizontal position channel, typically bound to the *x* scale. */ + x?: ChannelValueSpec; + /** The vertical position channel, typically bound to the *y* scale. */ + y?: ChannelValueSpec; + /** + * An optional ordinal channel for grouping to produce multiple + * (possibly overlapping) triangulations. + */ + z?: ChannelValue; +} + +/** The delaunayLink mark. */ +export interface DelaunayLink extends MarkData, DelaunayOptions { + /** + * A mark that draws links for each edge of the Delaunay triangulation + * of points given by the **x** and **y** channels. Like the link mark, + * except that **x1**, **y1**, **x2**, and **y2** are derived automatically + * from **x** and **y**. When an aesthetic channel is specified (such as + * **stroke** or **strokeWidth**), the link inherits the corresponding + * channel value from one of its two endpoints arbitrarily. + * + * If **z** is specified, the input points are grouped by *z*, producing a + * separate Delaunay triangulation for each group. + */ + mark: 'delaunayLink'; +} + +/** The delaunayMesh mark. */ +export interface DelaunayMesh extends MarkData, DelaunayOptions { + /** + * A mark that draws a mesh of the Delaunay triangulation of the points + * given by the **x** and **y** channels. The **stroke** option defaults to + * _currentColor_, and the **strokeOpacity** defaults to 0.2; the **fill** + * option is not supported. When an aesthetic channel is specified (such as + * **stroke** or **strokeWidth**), the mesh inherits the corresponding + * channel value from one of its constituent points arbitrarily. + * + * If **z** is specified, the input points are grouped by *z*, producing a + * separate Delaunay triangulation for each group. + */ + mark: 'delaunayMesh'; +} + +/** The hull mark. */ +export interface Hull extends MarkData, DelaunayOptions { + /** + * A mark that draws a convex hull around the points given by the **x** and + * **y** channels. The **stroke** option defaults to _currentColor_ and the + * **fill** option defaults to _none_. When an aesthetic channel is specified + * (such as **stroke** or **strokeWidth**), the hull inherits the + * corresponding channel value from one of its constituent points + * arbitrarily. + * + * If **z** is specified, the input points are grouped by *z*, producing a + * separate hull for each group. If **z** is not specified, it defaults to + * the **fill** channel, if any, or the **stroke** channel, if any. + */ + mark: 'hull'; +} + +/** The voronoi mark. */ +export interface Voronoi extends MarkData, DelaunayOptions { + /** + * A mark that draws polygons for each cell of the Voronoi tesselation + * of the points given by the **x** and **y** channels. + * + * If **z** is specified, the input points are grouped by *z*, producing a + * separate Voronoi tesselation for each group. + */ + mark: 'voronoi'; +} + +/** The voronoiMesh mark. */ +export interface VoronoiMesh extends MarkData, DelaunayOptions { + /** + * A mark that draws a mesh for the cell boundaries of the Voronoi + * tesselation of the points given by the **x** and **y** channels. The + * **stroke** option defaults to _currentColor_, and the **strokeOpacity** + * defaults to 0.2. The **fill** option is not supported. When an aesthetic + * channel is specified (such as **stroke** or **strokeWidth**), the mesh + * inherits the corresponding channel value from one of its constituent + * points arbitrarily. + * + * If **z** is specified, the input points are grouped by *z*, producing a + * separate Voronoi tesselation for each group. + */ + mark: 'voronoiMesh'; +} diff --git a/packages/spec/src/spec/marks/DenseLine.ts b/packages/spec/src/spec/marks/DenseLine.ts new file mode 100644 index 00000000..ad9d78fb --- /dev/null +++ b/packages/spec/src/spec/marks/DenseLine.ts @@ -0,0 +1,30 @@ +import { ParamRef } from '../Param.js'; +import { ChannelValue, MarkData } from './Marks.js'; +import { RasterOptions } from './Raster.js'; + +export interface DenseLineOptions extends RasterOptions { + /** + * Flag to perform approximate arc length normalization of line segments + * to prevent artifacts due to overcounting steep lines. Defaults to `true`. + */ + normalize?: boolean | ParamRef; + + /** + * A ordinal channel for grouping data into series to be drawn as separate + * lines. + */ + z?: ChannelValue; +} + +/** The denseLine mark. */ +export interface DenseLine extends MarkData, DenseLineOptions { + /** + * A denseLine mark that plots line densities rather than point densities. + * The mark forms a binned raster grid and "draws" straight lines into it. + * To avoid over-weighting steep lines, by default each drawn series is + * normalized on a per-column basis to approximate arc length normalization. + * The values for each series are aggregated to form the line density, which + * is then drawn as an image similar to the raster mark. + */ + mark: 'denseLine'; +} diff --git a/packages/spec/src/spec/marks/Density.ts b/packages/spec/src/spec/marks/Density.ts new file mode 100644 index 00000000..845ec2ed --- /dev/null +++ b/packages/spec/src/spec/marks/Density.ts @@ -0,0 +1,145 @@ +import { ParamRef } from "../Param.js"; +import { AreaXOptions, AreaYOptions } from "./Area.js"; +import { DotOptions } from "./Dot.js"; +import { LineXOptions, LineYOptions } from "./Line.js"; +import { MarkData, MarkOptions, TextStyles } from "./Marks.js"; +import { Grid2DOptions } from "./Raster.js"; +import { TextOptions } from "./Text.js"; + +// Density2D + +export interface Density2DOptions extends MarkOptions, Omit, TextStyles, Grid2DOptions { + /** + * The basic mark type to use to render 2D density values. + * Defaults to a dot mark; cell and text marks are also supported. + */ + type?: 'dot' | 'circle' | 'hexagon' | 'cell' | 'text' | ParamRef; +} + +/** The density mark for 2D densities. */ +export interface Density extends MarkData, Density2DOptions { + /** + * A 2D density mark that shows smoothed point cloud densities along two + * dimensions. The mark bins the data, counts the number of records that fall + * into each bin, and smooths the resulting counts, then plots the smoothed + * distribution, by default using a circular dot mark. The density mark + * calculates density values that can be mapped to encoding channels such as + * fill or r using the special field name "density". + * + * Set the *type* property to use a different base mark type. + */ + mark: 'density'; +} + +// Density1D + +export interface Density1DOptions { + /** + * The kernel density bandwidth for smoothing, in pixels. Defaults to 20. + */ + bandwidth?: number | ParamRef; + + /** + * The number of bins over which to discretize the data prior to smoothing. + * Defaults to 1024. + */ + bins?: number | ParamRef; +} + +export interface DensityAreaXOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaX mark; lineX, dotX, and textX marks are also supported. + */ + type: 'areaX'; +} + +export interface DensityAreaYOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaY mark; lineY, dot, and text marks are also supported. + */ + type?: 'areaY'; +} + +export interface DensityLineXOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaX mark; lineX, dotX, and textX marks are also supported. + */ + type: 'lineX'; +} + +export interface DensityLineYOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaY mark; lineY, dot, and text marks are also supported. + */ + type: 'lineY'; +} + +export interface DensityDotXOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaX mark; lineX, dotX, and textX marks are also supported. + */ + type: 'dotX'; +} + +export interface DensityDotYOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaY mark; lineY, dot, and text marks are also supported. + */ + type: 'dot' | 'dotY' | 'circle' | 'hexagon'; +} + +export interface DensityTextXOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaX mark; lineX, dotX, and textX marks are also supported. + */ + type: 'textX'; +} + +export interface DensityTextYOptions extends Omit { + /** + * The basic mark type to use to render 1D density values. Defaults to an + * areaY mark; lineY, dot, and text marks are also supported. + */ + type: 'text' | 'textY'; +} + +export interface DensityXBase extends MarkData, Density1DOptions { + /** + * A densityX mark that visualizes smoothed point cloud densities along the + * **x** dimension. The mark bins the data, counts the number of records that + * fall into each bin, smooths the resulting counts, and then plots the + * smoothed distribution, by default using an areaX mark. + * + * Set the *type* property to use a different base mark type. + */ + mark: 'densityX'; +} + +export interface DensityYBase extends MarkData, Density1DOptions { + /** + * A densityY mark that visualizes smoothed point cloud densities along the + * **y** dimension. The mark bins the data, counts the number of records that + * fall into each bin, smooths the resulting counts, and then plots the + * smoothed distribution, by default using an areaY mark. + * + * Set the *type* property to use a different base mark type. + */ + mark: 'densityY'; +} + +/** The densityX mark. */ +export type DensityX = DensityXBase & ( + DensityAreaXOptions | DensityLineXOptions | DensityDotXOptions | DensityTextXOptions +); + +/** The densityY mark. */ +export type DensityY = DensityYBase & ( + DensityAreaYOptions | DensityLineYOptions | DensityDotYOptions | DensityTextYOptions +); diff --git a/packages/spec/src/spec/marks/Dot.ts b/packages/spec/src/spec/marks/Dot.ts new file mode 100644 index 00000000..2c76e9ef --- /dev/null +++ b/packages/spec/src/spec/marks/Dot.ts @@ -0,0 +1,147 @@ +import { ParamRef } from '../Param.js'; +import { FrameAnchor, Interval, SymbolType } from '../PlotTypes.js'; +import { + ChannelValue, ChannelValueIntervalSpec, + ChannelValueSpec, MarkData, MarkOptions +} from './Marks.js'; + +/** Options for the dot mark. */ +export interface DotOptions extends MarkOptions { + /** + * The horizontal position channel specifying the dot’s center, typically + * bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel specifying the dot’s center, typically bound + * to the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * The radius of dots; either a channel or constant. When a number, it is + * interpreted as a constant radius in pixels. Otherwise it is interpreted as + * a channel, typically bound to the *r* channel, which defaults to the *sqrt* + * type for proportional symbols. The radius defaults to 4.5 pixels when using + * the **symbol** channel, and otherwise 3 pixels. Dots with a nonpositive + * radius are not drawn. + */ + r?: ChannelValueSpec | number | ParamRef; + + /** + * The rotation angle of dots in degrees clockwise; either a channel or a + * constant. When a number, it is interpreted as a constant; otherwise it is + * interpreted as a channel. Defaults to 0°, pointing up. + */ + rotate?: ChannelValue | number | ParamRef; + + /** + * The categorical symbol; either a channel or a constant. A constant symbol + * can be specified by a valid symbol name such as *star*, or a symbol object + * (implementing the draw method); otherwise it is interpreted as a channel. + * Defaults to *circle* for the **dot** mark, and *hexagon* for the + * **hexagon** mark. + * + * If the **symbol** channel’s values are all symbols, symbol names, or + * nullish, the channel is unscaled (values are interpreted literally); + * otherwise, the channel is bound to the *symbol* scale. + */ + symbol?: ChannelValueSpec | SymbolType | ParamRef; + + /** + * The frame anchor specifies defaults for **x** and **y** based on the plot’s + * frame; it may be one of the four sides (*top*, *right*, *bottom*, *left*), + * one of the four corners (*top-left*, *top-right*, *bottom-right*, + * *bottom-left*), or the *middle* of the frame. For example, for dots + * distributed horizontally at the top of the frame: + * + * ```js + * Plot.dot(data, {x: "date", frameAnchor: "top"}) + * ``` + */ + frameAnchor?: FrameAnchor | ParamRef; +} + +/** Options for the dotX mark. */ +export interface DotXOptions extends Omit { + /** + * The vertical position of the dot’s center, typically bound to the *y* + * scale. + */ + y?: ChannelValueIntervalSpec; + + /** + * An interval (such as *day* or a number), to transform **y** values to the + * middle of the interval. + */ + interval?: Interval | ParamRef; +} + +/** Options for the dotY mark. */ +export interface DotYOptions extends Omit { + /** + * The horizontal position of the dot’s center, typically bound to the *x* + * scale. + */ + x?: ChannelValueIntervalSpec; + + /** + * An interval (such as *day* or a number), to transform **x** values to the + * middle of the interval. + */ + interval?: Interval | ParamRef; +} + +/** The dot mark. */ +export interface Dot extends MarkData, DotOptions { + /** + * A dot mark that draws circles, or other symbols, as in a scatterplot. + * + * If either **x** or **y** is not specified, the default is determined by the + * **frameAnchor** option. If none of **x**, **y**, and **frameAnchor** are + * specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, + * *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = + * [*y₀*, *y₁*, *y₂*, …]. + * + * Dots are sorted by descending radius **r** by default to mitigate + * overplotting; set the **sort** option to null to draw them in input order. + */ + mark: 'dot'; +} + +/** The dotX mark. */ +export interface DotX extends MarkData, DotXOptions { + /** + * Like dot, except that **x** defaults to the identity function, assuming that + * *data* = [*x₀*, *x₁*, *x₂*, …]. + * + * If an **interval** is specified, such as *day*, **y** is transformed to the + * middle of the interval. + */ + mark: 'dotX'; +} + +/** The dotY mark. */ +export interface DotY extends MarkData, DotYOptions { + /** + * Like dot, except that **y** defaults to the identity function, assuming that + * *data* = [*y₀*, *y₁*, *y₂*, …]. + * + * If an **interval** is specified, such as *day*, **x** is transformed to the + * middle of the interval. + */ + mark: 'dotY'; +} + +/** The circle mark. */ +export interface Circle extends MarkData, Exclude { + /** Like dot, except that the **symbol** option is set to *circle*. */ + mark: 'circle'; +} + +/** The hexagon mark. */ +export interface Hexagon extends MarkData, Exclude { + /** Like dot, except that the **symbol** option is set to *hexagon*. */ + mark: 'hexagon'; +} diff --git a/packages/spec/src/spec/marks/Frame.ts b/packages/spec/src/spec/marks/Frame.ts new file mode 100644 index 00000000..2e570086 --- /dev/null +++ b/packages/spec/src/spec/marks/Frame.ts @@ -0,0 +1,23 @@ +import { ParamRef } from '../Param.js'; +import { InsetOptions, MarkOptions } from './Marks.js'; +import { RectCornerOptions } from './Rect.js'; + +/** Options for the frame decoration mark. */ +export interface FrameOptions extends MarkOptions, InsetOptions, RectCornerOptions { + /** + * If null (default), the rectangular outline of the frame is drawn; + * otherwise the frame is drawn as a line only on the given side, and the + * **rx**, **ry**, **fill**, and **fillOpacity** options are ignored. + */ + anchor?: 'top' | 'right' | 'bottom' | 'left' | null | ParamRef; +} + +/** The frame mark. */ +export interface Frame extends FrameOptions { + /** + * Draws a rectangle around the plot’s frame, or if an **anchor** is given, + * a line on the given side. Useful for visual separation of facets, or in + * conjunction with axes and grids to fill the frame’s background. + */ + mark: 'frame'; +} diff --git a/packages/spec/src/spec/marks/Geo.ts b/packages/spec/src/spec/marks/Geo.ts new file mode 100644 index 00000000..33226a72 --- /dev/null +++ b/packages/spec/src/spec/marks/Geo.ts @@ -0,0 +1,58 @@ +import { ParamRef } from '../Param.js'; +import { ChannelValue, ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** Options for the geo mark. */ +export interface GeoOptions extends MarkOptions { + /** + * A required channel for the geometry to render; defaults to identity, + * assuming *data* is a GeoJSON object or an iterable of GeoJSON objects. + */ + geometry?: ChannelValue; + + /** + * The size of Point and MultiPoint geometries, defaulting to a constant 3 + * pixels. If **r** is a number, it is interpreted as a constant radius in + * pixels; otherwise it is interpreted as a channel and the effective radius + * is controlled by the *r* scale, which defaults to a *sqrt* scale such that + * the visual area of a point is proportional to its associated value. + * + * If **r** is a channel, geometries will be sorted by descending radius by + * default, to limit occlusion; use the **sort** transform to control render + * order. Geometries with a nonpositive radius are not drawn. + */ + r?: ChannelValueSpec | ParamRef; +} + +/** The geo mark. */ +export interface Geo extends MarkData, GeoOptions { + /** + * A geo mark. The **geometry** channel, which defaults to the identity + * function assuming that *data* is a GeoJSON object or an iterable of + * GeoJSON objects, is projected to the plane using the plot’s top-level + * **projection**. + * + * If *data* is a GeoJSON feature collection, then the mark’s data is + * *data*.features; if *data* is a GeoJSON geometry collection, then the + * mark’s data is *data*.geometries; if *data* is some other GeoJSON + * object, then the mark’s data is the single-element array [*data*]. + */ + mark: 'geo'; +} + +/** The sphere mark. */ +export interface Sphere extends MarkOptions { + /** + * A geo mark whose *data* is the outline of the sphere on the + * projection’s plane. (For use with a spherical **projection** only.) + */ + mark: 'sphere'; +} + +/** The graticule mark. */ +export interface Graticule extends MarkOptions { + /** + * A geo mark whose *data* is a 10° global graticule. (For use with a + * spherical **projection** only.) + */ + mark: 'graticule'; +} diff --git a/packages/spec/src/spec/marks/Hexbin.ts b/packages/spec/src/spec/marks/Hexbin.ts new file mode 100644 index 00000000..2f66dbc6 --- /dev/null +++ b/packages/spec/src/spec/marks/Hexbin.ts @@ -0,0 +1,34 @@ +import { ParamRef } from '../Param.js'; +import { DotOptions } from './Dot.js'; +import { ChannelValue, MarkData, TextStyles } from './Marks.js'; + +export interface HexbinOptions extends DotOptions, TextStyles { + /** + * The basic mark type to use for hex-binned values. + * Defaults to a hexagon mark; dot and text marks are also supported. + */ + type?: 'dot' | 'circle' | 'hexagon' | 'text' | ParamRef; + + /** + * The distance between centers of neighboring hexagons, in pixels; defaults + * to 20. If also using a hexgrid mark, use matching **binWidth** values. + */ + binWidth?: number | ParamRef; + + /** + * How to subdivide bins. If not specified, defaults to the *fill* channel, + * if any, or the *stroke* channel, if any. If null, bins will not be + * subdivided. + */ + z?: ChannelValue; +} + +/** The hexbin mark. */ +export interface Hexbin extends MarkData, HexbinOptions { + /** + * A hexbin mark that bins **x** and **y** data into a hexagonal grid and + * visualizes aggregate functions per bin (e.g., count for binned density). + * Aggregate functions can be used for fill, stroke, or r (radius) options. + */ + mark: 'hexbin'; +} diff --git a/packages/spec/src/spec/marks/Hexgrid.ts b/packages/spec/src/spec/marks/Hexgrid.ts new file mode 100644 index 00000000..e5846c66 --- /dev/null +++ b/packages/spec/src/spec/marks/Hexgrid.ts @@ -0,0 +1,27 @@ +import { ParamRef } from '../Param.js'; +import { MarkOptions } from './Marks.js'; + +/** Options for the hexgrid mark. */ +export interface HexgridOptions extends MarkOptions { + /** + * The distance between centers of neighboring hexagons, in pixels; defaults + * to 20. Should match the **binWidth** of the hexbin mark. + */ + binWidth?: number | ParamRef; +} + +/** The hexgrid mark. */ +export interface Hexgrid extends HexgridOptions { + /** + * The hexgrid decoration mark complements the hexbin mark, showing the + * outlines of all hexagons spanning the frame with a default **stroke** of + * *currentColor* and a default **strokeOpacity** of 0.1, similar to the + * default axis grids. + * + * Note that the **binWidth** option of the hexgrid mark should match that of + * the hexbin transform. The grid is clipped by the frame. This is a + * stroke-only mark, and **fill** is not supported; to fill the frame, + * use the frame mark. + */ + mark: 'hexgrid'; +} diff --git a/packages/spec/src/spec/marks/Image.ts b/packages/spec/src/spec/marks/Image.ts new file mode 100644 index 00000000..620781a2 --- /dev/null +++ b/packages/spec/src/spec/marks/Image.ts @@ -0,0 +1,101 @@ +import { ParamRef } from '../Param.js'; +import { FrameAnchor } from '../PlotTypes.js'; +import { ChannelValue, ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** Options for the image mark. */ +export interface ImageOptions extends MarkOptions { + /** + * The horizontal position channel specifying the image’s center; typically + * bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel specifying the image’s center; typically + * bound to the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * The image width in pixels. When a number, it is interpreted as a constant + * radius in pixels; otherwise it is interpreted as a channel. Also sets the + * default **height**; if neither are set, defaults to 16. Images with a + * nonpositive width are not drawn. + */ + width?: ChannelValue | ParamRef; + + /** + * The image height in pixels. When a number, it is interpreted as a constant + * radius in pixels; otherwise it is interpreted as a channel. Also sets the + * default **height**; if neither are set, defaults to 16. Images with a + * nonpositive height are not drawn. + */ + height?: ChannelValue | ParamRef; + + /** + * The image clip radius, for circular images. If null (default), images are + * not clipped; when a number, it is interpreted as a constant in pixels; + * otherwise it is interpreted as a channel, typically bound to the *r* scale. + * Also defaults **height** and **width** to twice its value. + */ + r?: ChannelValue | ParamRef; + + /** + * The rotation angle, in degrees clockwise. When a number, it is interpreted + * as a constant; otherwise it is interpreted as a channel. + */ + rotate?: ChannelValue | ParamRef; + + /** + * The required image URL (or relative path). If a string that starts with a + * dot, slash, or URL protocol (*e.g.*, “https:”) it is assumed to be a + * constant; otherwise it is interpreted as a channel. + */ + src?: ChannelValue | ParamRef; + + /** + * The image [aspect ratio][1]; defaults to *xMidYMid meet*. To crop the image + * instead of scaling it to fit, use *xMidYMid slice*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio + */ + preserveAspectRatio?: string | ParamRef; + + /** + * The [cross-origin][1] behavior. See the [Plot.image notebook][2] for details. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/crossorigin + * [2]: https://observablehq.com/@observablehq/plot-image + */ + crossOrigin?: string | ParamRef; + + /** + * The frame anchor specifies defaults for **x** and **y** based on the plot’s + * frame; it may be one of the four sides (*top*, *right*, *bottom*, *left*), + * one of the four corners (*top-left*, *top-right*, *bottom-right*, + * *bottom-left*), or the *middle* of the frame. + */ + frameAnchor?: FrameAnchor | ParamRef; + + /** + * The [image-rendering attribute][1]; defaults to *auto* (bilinear). The + * option may be set to *pixelated* to disable bilinear interpolation for a + * sharper image; however, note that this is not supported in WebKit. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/image-rendering + */ + imageRendering?: string | ParamRef; +} + +export interface Image extends MarkData, ImageOptions { + /** + * An image mark that draws images as in a scatterplot. + * + * If either **x** or **y** is not specified, the default is determined by + * the **frameAnchor** option. If none of **x**, **y**, and **frameAnchor** + * are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], + * [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] + * and **y** = [*y₀*, *y₁*, *y₂*, …]. + */ + mark: 'image'; +} diff --git a/packages/spec/src/spec/marks/Line.ts b/packages/spec/src/spec/marks/Line.ts new file mode 100644 index 00000000..57f693f3 --- /dev/null +++ b/packages/spec/src/spec/marks/Line.ts @@ -0,0 +1,93 @@ +import { + ChannelValue, ChannelValueSpec, CurveAutoOptions, + MarkData, MarkOptions, MarkerOptions +} from './Marks.js'; + +/** Options for the line mark. */ +export interface LineOptions extends MarkOptions, MarkerOptions, CurveAutoOptions { + /** + * The required horizontal position channel, typically bound to the *x* + * scale. + */ + x?: ChannelValueSpec; + + /** + * The required vertical position channel, typically bound to the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * An optional ordinal channel for grouping data into (possibly stacked) + * series to be drawn as separate lines. If not specified, it defaults to + * **fill** if a channel, or **stroke** if a channel. + */ + z?: ChannelValue; +} + +/** Options for the lineX mark. */ +export interface LineXOptions extends LineOptions { + /** + * The vertical position channel, typically bound to the *y* scale; + * defaults to the zero-based index of the data [0, 1, 2, …]. + */ + y?: ChannelValueSpec; +} + +/** Options for the lineY mark. */ +export interface LineYOptions extends LineOptions { + /** + * The horizontal position channel, typically bound to the *x* scale; + * defaults to the zero-based index of the data [0, 1, 2, …]. + */ + x?: ChannelValueSpec; +} + +/** The line mark. */ +export interface Line extends MarkData, LineOptions { + /** + * A line mark that connects control points. + * + * Points along the line are connected in input order. If there are multiple + * series via the **z**, **fill**, or **stroke** channel, series are drawn in + * input order such that the last series is drawn on top. Typically *data* is + * already in sorted order, such as chronological for time series; if needed, + * consider a **sort** transform. + * + * If any **x** or **y** values are invalid (undefined, null, or NaN), the + * line will be interrupted, resulting in a break that divides the line shape + * into multiple segments. If a line segment consists of only a single point, + * it may appear invisible unless rendered with rounded or square line caps. + * In addition, some curves such as *cardinal-open* only render a visible + * segment if it contains multiple points. + * + * Variable aesthetic channels are supported: if the **stroke** is defined as + * a channel, the line will be broken into contiguous overlapping segments + * when the stroke color changes; the stroke color will apply to the interval + * spanning the current data point and the following data point. This + * behavior also applies to the **fill**, **fillOpacity**, **strokeOpacity**, + * **strokeWidth**, **opacity**, **href**, **title**, and **ariaLabel** + * channels. When any of these channels are used, setting an explicit **z** + * channel (possibly to null) is strongly recommended. + */ + mark: 'line'; +} + +/** The lineX mark. */ +export interface LineX extends MarkData, LineXOptions { + /** + * Like line, except that **x** defaults to the identity function assuming + * that *data* = [*x₀*, *x₁*, *x₂*, …] and **y** defaults to the zero-based + * index [0, 1, 2, …]. + */ + mark: 'lineX' +} + +/** The lineY mark. */ +export interface LineY extends MarkData, LineYOptions { + /** + * Like line, except **y** defaults to the identity function and assumes + * that *data* = [*y₀*, *y₁*, *y₂*, …] and **x** defaults to the zero-based + * index [0, 1, 2, …]. + */ + mark: 'lineY' +} diff --git a/packages/spec/src/spec/marks/Link.ts b/packages/spec/src/spec/marks/Link.ts new file mode 100644 index 00000000..fba08070 --- /dev/null +++ b/packages/spec/src/spec/marks/Link.ts @@ -0,0 +1,70 @@ +import { ParamRef } from '../Param.js'; +import { + ChannelValueSpec, CurveAutoOptions, MarkData, MarkOptions, MarkerOptions +} from './Marks.js'; + +/** Options for the link mark. */ +export interface LinkOptions extends MarkOptions, MarkerOptions, CurveAutoOptions { + /** + * The horizontal position, for vertical links; typically bound to the *x* + * scale; shorthand for setting defaults for both **x1** and **x2**. + */ + x?: ChannelValueSpec; + + /** + * The vertical position, for horizontal links; typically bound to the *y* + * scale; shorthand for setting defaults for both **y1** and **y2**. + */ + y?: ChannelValueSpec; + + /** + * The starting horizontal position; typically bound to the *x* scale; also + * sets a default for **x2**. + */ + x1?: ChannelValueSpec; + + /** + * The starting vertical position; typically bound to the *y* scale; also sets + * a default for **y2**. + */ + y1?: ChannelValueSpec; + + /** + * The ending horizontal position; typically bound to the *x* scale; also sets + * a default for **x1**. + */ + x2?: ChannelValueSpec; + + /** + * The ending vertical position; typically bound to the *y* scale; also sets a + * default for **y1**. + */ + y2?: ChannelValueSpec; + + /** + * The curve (interpolation) method for connecting adjacent points. + * + * Since a link has exactly two points, only the following curves (or a custom + * curve) are recommended: *linear*, *step*, *step-after*, *step-before*, + * *bump-x*, or *bump-y*. Note that the *linear* curve is incapable of showing + * a fill since a straight line has zero area. For a curved link, use an arrow + * mark with the **bend** option. + * + * If the plot uses a spherical **projection**, the default *auto* **curve** + * will render links as geodesics; to draw a straight line instead, use the + * *linear* **curve**. + */ + curve?: CurveAutoOptions['curve'] | ParamRef; +} + +/** The link mark. */ +export interface Link extends MarkData, LinkOptions { + /** + * A link mark, drawing line segments (curves) connecting pairs of points. + * + * If the plot uses a spherical **projection**, the default *auto* **curve** + * will render links as geodesics; to draw a straight line instead, use the + * *linear* **curve**. + */ + mark: 'link'; +} diff --git a/packages/spec/src/spec/marks/Marks.ts b/packages/spec/src/spec/marks/Marks.ts new file mode 100644 index 00000000..a853e9c4 --- /dev/null +++ b/packages/spec/src/spec/marks/Marks.ts @@ -0,0 +1,1071 @@ +import { AggregateExpression, SQLExpression } from '../Expression.js'; +import { ParamRef } from '../Param.js'; +import { PlotMarkData } from '../PlotFrom.js'; +import { CurveName, FrameAnchor, Interval, Reducer, ScaleName } from '../PlotTypes.js'; +import { Transform } from '../Transform.js'; + +/** + * The set of known channel names. Channels in custom marks may use other names; + * these known names are enumerated for convenient autocomplete. + */ +export type ChannelName = + | 'ariaLabel' + | 'fill' + | 'fillOpacity' + | 'fontSize' + | 'fx' + | 'fy' + | 'geometry' + | 'height' + | 'href' + | 'length' + | 'opacity' + | 'path' + | 'r' + | 'rotate' + | 'src' + | 'stroke' + | 'strokeOpacity' + | 'strokeWidth' + | 'symbol' + | 'text' + | 'title' + | 'weight' + | 'width' + | 'x' + | 'x1' + | 'x2' + | 'y' + | 'y1' + | 'y2' + | 'z' + | (string & Record); // custom channel; see also https://github.com/microsoft/TypeScript/issues/29729 + +type ChannelScale = ScaleName | 'auto' | boolean | null; + +/** + * A channel’s values may be expressed as: + * + * - a field name, to extract the corresponding value for each datum + * - an iterable of values, typically of the same length as the data + * - a channel transform or SQL expression + * - a constant date, number, or boolean + * - null to represent no value + */ +export type ChannelValue = + | any[] // column of values + | (string & Record) // field or literal color; see also https://github.com/microsoft/TypeScript/issues/29729 + | Date // constant + | number // constant + | boolean // constant + | null // constant + | Transform + | SQLExpression + | AggregateExpression; + +/** + * When specifying a mark channel’s value, you can provide a {value, scale} + * object to override the scale that would normally be associated with the + * channel. + */ +export type ChannelValueSpec = + | ChannelValue + | { + value: ChannelValue; + label?: string; + scale?: ScaleName | 'auto' | boolean | null; + }; + +/** + * In some contexts, when specifying a mark channel’s value, you can provide a + * {value, interval} object to specify an associated interval. + */ +export type ChannelValueIntervalSpec = + | ChannelValueSpec + | { value: ChannelValue; interval: Interval }; + +/** A channel name, or an implied one for domain sorting. */ +type ChannelDomainName = ChannelName | 'data' | 'width' | 'height'; + +/** + * The available inputs for imputing scale domains. In addition to a named + * channel, an input may be specified as: + * + * - *data* - impute from mark data + * - *width* - impute from |*x2* - *x1*| + * - *height* - impute from |*y2* - *y1*| + * - null - impute from input order + * + * If the *x* channel is not defined, the *x2* channel will be used instead if + * available, and similarly for *y* and *y2*; this is useful for marks that + * implicitly stack. The *data* input is typically used in conjunction with a + * custom **reduce** function, as when the built-in single-channel reducers are + * insufficient. + */ +export type ChannelDomainValue = ChannelDomainName | `-${ChannelDomainName}` | null; + +/** Options for imputing scale domains from channel values. */ +export interface ChannelDomainOptions { + /** + * How to produce a singular value (for subsequent sorting) from aggregated + * channel values; one of: + * + * - true (default) - alias for *max* + * - false or null - disabled; don’t impute the scale domain + * - a named reducer implementation such as *count* or *sum* + * - a function that takes an array of values and returns the reduced value + * - an object that implements the *reduceIndex* method + */ + reduce?: Reducer | boolean | null; + + /** How to order reduced values. */ + order?: 'ascending' | 'descending' | null; + + /** If true, reverse the order after sorting. */ + reverse?: boolean; + + /** + * If a positive number, limit the domain to the first *n* sorted values. If a + * negative number, limit the domain to the last *-n* sorted values. Hence, a + * positive **limit** with **reverse** true will return the top *n* values in + * descending order. + * + * If an array [*lo*, *hi*], slices the sorted domain from *lo* (inclusive) to + * *hi* (exclusive). As with [*array*.slice][1], if either *lo* or *hi* are + * negative, it indicates an offset from the end of the array; if *lo* is + * undefined it defaults to 0, and if *hi* is undefined it defaults to + * Infinity. + * + * Note: limiting the imputed domain of one scale, say *x*, does not affect + * the imputed domain of another scale, say *y*; each scale domain is imputed + * independently. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice + */ + limit?: number | [lo?: number, hi?: number]; +} + +/** How to derive a scale’s domain from a channel’s values. */ +export type ChannelDomainValueSpec = + | ChannelDomainValue + | ({ value: ChannelDomainValue } & ChannelDomainOptions); + +/** How to impute scale domains from channel values. */ +export type ChannelDomainSort = { + [key in ScaleName]?: ChannelDomainValueSpec +} & ChannelDomainOptions; + +/** + * Output channels for aggregating transforms, such as bin and group. Each + * declared output channel has an associated reducer, and typically a + * corresponding input channel in *options*. Non-grouping channels declared in + * *options* but not *outputs* are computed on reduced data after grouping, + * which defaults to the array of data for the current group. + * + * If **title** is in *options* but not *outputs*, the reducer defaults to + * summarizing the most common values. If **href** is in *options* but not + * *outputs*, the reducer defaults to *first*. When **x1** or **x2** is in + * *outputs*, reads the input channel **x** if **x1** or **x2** is not in + * *options*; likewise for **y1** or **y2**, reads the input channel **y** if + * **y1** or **y2** is not in *options*. + */ +export type ChannelReducers = { + [key in ChannelName]?: T | { reduce: T; scale?: ChannelScale } | null +}; + +/** Abstract (unscaled) values, and associated scale, per channel. */ +export type ChannelStates = { + [key in ChannelName]?: { value: any[]; scale: ScaleName | null } +}; + +/** Possibly-scaled values for each channel. */ +export type ChannelValues = { + [key in ChannelName]?: any[] +} & { channels: ChannelStates }; + +/** + * How to order values; one of: + * + * - a function for comparing data, returning a signed number + * - a channel value definition for sorting given values in ascending order + * - a {value, order} object for sorting given values + * - a {channel, order} object for sorting the named channel’s values + */ +export type SortOrder = + | ChannelValue + | { value?: ChannelValue; order?: 'ascending' | 'descending' } + | { channel?: ChannelName | `-${ChannelName}`; order?: 'ascending' | 'descending' }; + +/** The pointer mode for the tip; corresponds to pointerX, pointerY, and pointer. */ +export type TipPointer = 'x' | 'y' | 'xy'; + +export interface MarkData { + /** + * The data source for the mark. + */ + data: PlotMarkData; +} + +export interface MarkDataOptional { + /** + * The data source for the mark. + */ + data?: PlotMarkData; +} + +/** Shared options for all marks. */ +export interface MarkOptions { + /** + * Applies a transform to filter the mark’s index according to the given + * channel values; only truthy values are retained. + * + * Note that filtering only affects the rendered mark index, not the + * associated channel values, and thus has no effect on imputed scale domains. + */ + filter?: ChannelValue; + + /** + * Applies a transform to reverse the order of the mark’s index, say for + * reverse input order. + */ + reverse?: boolean | ParamRef; + + /** + * Either applies a transform to sort the mark’s index by the specified + * channel values, or imputes ordinal scale domains from this mark’s channels. + * + * When imputing ordinal scale domains from channel values, the **sort** + * option is an object whose keys are ordinal scale names such as *x* or *fx*, + * and whose values are channel names such as *y*, *y1*, or *y2*. For example, + * to impute the *y* scale’s domain from the associated *x* channel values in + * ascending order: + * + * ```js + * sort: {y: "x"} + * ``` + * + * For different sort options for different scales, replace the channel name + * with a *value* object and per-scale options: + * + * ```js + * sort: {y: {value: "-x"}} + * ``` + * + * When sorting the mark’s index, the **sort** option is instead one of: + * + * - a channel value definition for sorting given values in ascending order + * - a {value, order} object for sorting given values + * - a {channel, order} object for sorting the named channel’s values + */ + sort?: SortOrder | ChannelDomainSort; + + /** + * The horizontal facet position channel, for mark-level faceting, bound to + * the *fx* scale. + */ + fx?: ChannelValue; + + /** + * The vertical facet position channel, for mark-level faceting, bound to the + * *fy* scale. + */ + fy?: ChannelValue; + + /** + * Whether to enable or disable faceting; one of: + * + * - *auto* (default) - automatically determine if this mark should be faceted + * - *include* (or true) - draw the subset of the mark’s data in the current facet + * - *exclude* - draw the subset of the mark’s data *not* in the current facet + * - *super* - draw this mark in a single frame that covers all facets + * - null (or false) - repeat this mark’s data across all facets (*i.e.*, no faceting) + * + * When a mark uses *super* faceting, it is not allowed to use position scales + * (*x*, *y*, *fx*, or *fy*); *super* faceting is intended for decorations, + * such as labels and legends. + * + * When top-level faceting is used, the default *auto* setting is equivalent + * to *include* when the mark data is strictly equal to the top-level facet + * data; otherwise it is equivalent to null. When the *include* or *exclude* + * facet mode is chosen, the mark data must be parallel to the top-level facet + * data: the data must have the same length and order. If the data are not + * parallel, then the wrong data may be shown in each facet. The default + * *auto* therefore requires strict equality (`===`) for safety, and using the + * facet data as mark data is recommended when using the *exclude* facet mode. + * (To construct parallel data safely, consider using [*array*.map][1] on the + * facet data.) + * + * When mark-level faceting is used, the default *auto* setting is equivalent + * to *include*: the mark will be faceted if either the **fx** or **fy** + * channel option (or both) is specified. The null or false option will + * disable faceting, while *exclude* draws the subset of the mark’s data *not* + * in the current facet. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map + */ + facet?: 'auto' | 'include' | 'exclude' | 'super' | boolean | null | ParamRef; + + /** + * How to place the mark with respect to facets; one of: + * + * - null (default for most marks) - display the mark in each non-empty facet + * - *top*, *right*, *bottom*, or *left* - display the mark only in facets on + * the given side + * - *top-empty*, *right-empty*, *bottom-empty*, or *left-empty* (default for + * axis marks) - display the mark only in facets that have empty space on + * the given side: either the margin, or an empty facet + * - *empty* - display the mark in empty facets only + */ + facetAnchor?: + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right' + | 'top-empty' + | 'right-empty' + | 'bottom-empty' + | 'left-empty' + | 'empty' + | null + | ParamRef; + + /** + * Shorthand to set the same default for all four mark margins: **marginTop**, + * **marginRight**, **marginBottom**, and **marginLeft**; typically defaults + * to 0, except for axis marks. + */ + margin?: number | ParamRef; + + /** + * The mark’s top margin; the minimum distance in pixels between the top edges + * of the inner and outer plot area. + */ + marginTop?: number | ParamRef; + + /** + * The mark’s right margin; the minimum distance in pixels between the right + * edges of the mark’s inner and outer plot area. + */ + marginRight?: number | ParamRef; + + /** + * The mark’s bottom margin; the minimum distance in pixels between the bottom + * edges of the inner and outer plot area. + */ + marginBottom?: number | ParamRef; + + /** + * The mark’s left margin; the minimum distance in pixels between the left + * edges of the inner and outer plot area. + */ + marginLeft?: number | ParamRef; + + /** + * The [aria-description][1]; a constant textual description. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description + */ + ariaDescription?: string | ParamRef; + + /** + * The [aria-hidden][1] state; a constant indicating whether the element is + * exposed to an accessibility API. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden + */ + ariaHidden?: string | ParamRef; + + /** + * The [aria-label][1]; a channel specifying short textual labels representing + * the value in the accessibility tree. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label + */ + ariaLabel?: ChannelValue; + + /** + * The [pointer-events][1] property; a constant string such as *none*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events + */ + pointerEvents?: string | ParamRef; + + /** + * The title; a channel specifying accessible, short textual descriptions as + * strings (possibly with newlines). If the tip option is specified, the title + * will be displayed with an interactive tooltip instead of using the SVG + * [title element][1]. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title + */ + title?: ChannelValue; + + /** Whether to generate a tooltip for this mark, and any tip options. */ + tip?: + | boolean + | TipPointer + | (TipOptions & { pointer?: TipPointer }) + | ParamRef; + + /** + * How to clip the mark; one of: + * + * - *frame* or true - clip to the plot’s frame (inner area) + * - *sphere* - clip to the projected sphere (*e.g.*, front hemisphere) + * - null or false - do not clip + * + * The *sphere* clip option requires a geographic projection. + */ + clip?: 'frame' | 'sphere' | boolean | null | ParamRef; + + /** + * The horizontal offset in pixels; a constant option. On low-density screens, + * an additional 0.5px offset may be applied for crisp edges. + */ + dx?: number | ParamRef; + + /** + * The vertical offset in pixels; a constant option. On low-density screens, + * an additional 0.5px offset may be applied for crisp edges. + */ + dy?: number | ParamRef; + + /** + * The [fill][1]; a constant CSS color string, or a channel typically bound to + * the *color* scale. If all channel values are valid CSS colors, by default + * the channel will not be bound to the *color* scale, interpreting the colors + * literally. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + */ + fill?: ChannelValueSpec | ParamRef; + + /** + * The [fill-opacity][1]; a constant number between 0 and 1, or a channel + * typically bound to the *opacity* scale. If all channel values are numbers + * in [0, 1], by default the channel will not be bound to the *opacity* scale, + * interpreting the opacities literally. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity + */ + fillOpacity?: ChannelValueSpec | ParamRef; + + /** + * The [stroke][1]; a constant CSS color string, or a channel typically bound + * to the *color* scale. If all channel values are valid CSS colors, by + * default the channel will not be bound to the *color* scale, interpreting + * the colors literally. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke + */ + stroke?: ChannelValueSpec | ParamRef; + + /** + * The [stroke-dasharray][1]; a constant number indicating the length in + * pixels of alternating dashes and gaps, or a constant string of numbers + * separated by spaces or commas (_e.g._, *10 2* for dashes of 10 pixels + * separated by gaps of 2 pixels), or *none* (the default) for no dashing + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray + */ + strokeDasharray?: string | number | ParamRef; + + /** + * The [stroke-dashoffset][1]; a constant indicating the offset in pixels of + * the first dash along the stroke; defaults to zero. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dashoffset + */ + strokeDashoffset?: string | number | ParamRef; + + /** + * The [stroke-linecap][1]; a constant specifying how to cap stroked paths, + * such as *butt*, *round*, or *square*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap + */ + strokeLinecap?: string | ParamRef; + + /** + * The [stroke-linejoin][1]; a constant specifying how to join stroked paths, + * such as *bevel*, *miter*, *miter-clip*, or *round*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin + */ + strokeLinejoin?: string | ParamRef; + + /** + * The [stroke-miterlimit][1]; a constant number specifying how to limit the + * length of *miter* joins on stroked paths. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit + */ + strokeMiterlimit?: number | ParamRef; + + /** + * The [stroke-opacity][1]; a constant between 0 and 1, or a channel typically + * bound to the *opacity* scale. If all channel values are numbers in [0, 1], + * by default the channel will not be bound to the *opacity* scale, + * interpreting the opacities literally. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-opacity + */ + strokeOpacity?: ChannelValueSpec; + + /** + * The [stroke-width][1]; a constant number in pixels, or a channel. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width + */ + strokeWidth?: ChannelValueSpec; + + /** + * The [opacity][1]; a constant between 0 and 1, or a channel typically bound + * to the *opacity* scale. If all channel values are numbers in [0, 1], by + * default the channel will not be bound to the *opacity* scale, interpreting + * the opacities literally. For faster rendering, prefer the **strokeOpacity** + * or **fillOpacity** option. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/opacity + */ + opacity?: ChannelValueSpec; + + /** + * The [mix-blend-mode][1]; a constant string specifying how to blend content + * such as *multiply*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode + */ + mixBlendMode?: string | ParamRef; + + /** + * A CSS [filter][1]; a constant string used to adjust the rendering of + * images, such as *blur(5px)*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/filter + */ + imageFilter?: string | ParamRef; + + /** + * The [paint-order][1]; a constant string specifying the order in which the + * **fill**, **stroke**, and any markers are drawn; defaults to *normal*, + * which draws the fill, then stroke, then markers; defaults to *stroke* for + * the text mark to create a “halo” around text to improve legibility. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order + */ + paintOrder?: string | ParamRef; + + /** + * The [shape-rendering][1]; a constant string such as *crispEdges*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering + */ + shapeRendering?: string | ParamRef; + + /** + * The [href][1]; a channel specifying URLs for clickable links. May be used + * in conjunction with the **target** option to open links in another window. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/href + */ + href?: ChannelValue; + + /** + * The [target][1]; a constant string specifying the target window (_e.g._, + * *_blank*) for clickable links; used in conjunction with the **href** + * option. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/target + */ + target?: string | ParamRef; +} + +/** Options for insetting rectangular shapes. */ +export interface InsetOptions { + /** + * Shorthand to set the same default for all four insets: **insetTop**, + * **insetRight**, **insetBottom**, and **insetLeft**. All insets typically + * default to zero, though not always (say when using bin transform). A + * positive inset reduces effective area, while a negative inset increases it. + */ + inset?: number | ParamRef; + + /** + * Insets the top edge by the specified number of pixels. A positive value + * insets towards the bottom edge (reducing effective area), while a negative + * value insets away from the bottom edge (increasing it). + */ + insetTop?: number | ParamRef; + + /** + * Insets the right edge by the specified number of pixels. A positive value + * insets towards the left edge (reducing effective area), while a negative + * value insets away from the left edge (increasing it). + */ + insetRight?: number | ParamRef; + + /** + * Insets the bottom edge by the specified number of pixels. A positive value + * insets towards the top edge (reducing effective area), while a negative + * value insets away from the top edge (increasing it). + */ + insetBottom?: number | ParamRef; + + /** + * Insets the left edge by the specified number of pixels. A positive value + * insets towards the right edge (reducing effective area), while a negative + * value insets away from the right edge (increasing it). + */ + insetLeft?: number | ParamRef; +} + +/** Options for styling text (independent of anchor position). */ +export interface TextStyles { + /** + * The [text anchor][1] controls how text is aligned (typically horizontally) + * relative to its anchor point; it is one of *start*, *end*, or *middle*. If + * the frame anchor is *left*, *top-left*, or *bottom-left*, the default text + * anchor is *start*; if the frame anchor is *right*, *top-right*, or + * *bottom-right*, the default is *end*; otherwise it is *middle*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor + */ + textAnchor?: 'start' | 'middle' | 'end' | ParamRef; + + /** + * The line height in ems; defaults to 1. The line height affects the + * (typically vertical) separation between adjacent baselines of text, as well + * as the separation between the text and its anchor point. + */ + lineHeight?: number | ParamRef; + + /** + * The line width in ems (e.g., 10 for about 20 characters); defaults to + * infinity, disabling wrapping and clipping. + * + * If **textOverflow** is null, lines will be wrapped at the specified length. + * If a line is split at a soft hyphen (\xad), a hyphen (-) will be displayed + * at the end of the line. If **textOverflow** is not null, lines will be + * clipped according to the given strategy. + */ + lineWidth?: number | ParamRef; + + /** + * How truncate (or wrap) lines of text longer than the given **lineWidth**; + * one of: + * + * - null (default) - preserve overflowing characters (and wrap if needed) + * - *clip* or *clip-end* - remove characters from the end + * - *clip-start* - remove characters from the start + * - *ellipsis* or *ellipsis-end* - replace characters from the end with an ellipsis (…) + * - *ellipsis-start* - replace characters from the start with an ellipsis (…) + * - *ellipsis-middle* - replace characters from the middle with an ellipsis (…) + * + * If no **title** was specified, if text requires truncation, a title + * containing the non-truncated text will be implicitly added. + */ + textOverflow?: + | null + | 'clip' + | 'ellipsis' + | 'clip-start' + | 'clip-end' + | 'ellipsis-start' + | 'ellipsis-middle' + | 'ellipsis-end' + | ParamRef; + + /** + * If true, changes the default **fontFamily** to *monospace*, and uses + * simplified monospaced text metrics calculations. + */ + monospace?: boolean | ParamRef; + + /** + * The [font-family][1]; a constant; defaults to the plot’s font family, which + * is typically [*system-ui*][2]. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/font-family + * [2]: https://drafts.csswg.org/css-fonts-4/#valdef-font-family-system-ui + */ + fontFamily?: string | ParamRef; + + /** + * The [font size][1] in pixels; either a constant or a channel; defaults to + * the plot’s font size, which is typically 10. When a number, it is + * interpreted as a constant; otherwise it is interpreted as a channel. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size + */ + fontSize?: ChannelValue | ParamRef; + + /** + * The [font style][1]; a constant; defaults to the plot’s font style, which + * is typically *normal*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/font-style + */ + fontStyle?: string | ParamRef; + + /** + * The [font variant][1]; a constant; if the **text** channel contains numbers + * or dates, defaults to *tabular-nums* to facilitate comparing numbers; + * otherwise defaults to the plot’s font style, which is typically *normal*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant + */ + fontVariant?: string | ParamRef; + + /** + * The [font weight][1]; a constant; defaults to the plot’s font weight, which + * is typically *normal*. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight + */ + fontWeight?: string | number | ParamRef; +} + +/** Options for the tip mark. */ +export interface TipOptions extends MarkOptions, TextStyles { + /** + * The horizontal position channel specifying the tip’s anchor, typically + * bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The starting horizontal position channel specifying the tip’s anchor, + * typically bound to the *x* scale. + */ + x1?: ChannelValueSpec; + + /** + * The ending horizontal position channel specifying the tip’s anchor, + * typically bound to the *x* scale. + */ + x2?: ChannelValueSpec; + + /** + * The vertical position channel specifying the tip’s anchor, typically + * bound to the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * The starting vertical position channel specifying the tip’s anchor, + * typically bound to the *y* scale. + */ + y1?: ChannelValueSpec; + + /** + * The ending vertical position channel specifying the tip’s anchor, typically + * bound to the *y* scale. + */ + y2?: ChannelValueSpec; + + /** + * The frame anchor specifies defaults for **x** and **y** based on the plot’s + * frame; it may be one of the four sides (*top*, *right*, *bottom*, *left*), + * one of the four corners (*top-left*, *top-right*, *bottom-right*, + * *bottom-left*), or the *middle* of the frame. For example, for tips + * distributed horizontally at the top of the frame: + * + * ```js + * Plot.tip(data, {x: "date", frameAnchor: "top"}) + * ``` + */ + frameAnchor?: FrameAnchor | ParamRef; + + /** + * The tip anchor specifies how to orient the tip box relative to its anchor + * position; it refers to the part of the tip box that is attached to the + * anchor point. For example, the *top-left* anchor places the top-left corner + * of tip box near the anchor position, hence placing the tip box below and to + * the right of the anchor position. + */ + anchor?: FrameAnchor | ParamRef; + + /** + * If an explicit tip anchor is not specified, an anchor is chosen + * automatically such that the tip fits within the plot’s frame; if the + * preferred anchor fits, it is chosen. + */ + preferredAnchor?: FrameAnchor | null | ParamRef; + + /** + * How channel values are formatted for display. If a format is a string, it + * is interpreted as a (UTC) time format for temporal channels, and otherwise + * a number format. + */ + format?: { + [name in ChannelName]?: boolean | string | ParamRef + }; + + /** The image filter for the tip’s box; defaults to a drop shadow. */ + pathFilter?: string | ParamRef; + + /** The size of the tip’s pointer in pixels; defaults to 12. */ + pointerSize?: number | ParamRef; + + /** The padding around the text in pixels; defaults to 8. */ + textPadding?: number | ParamRef; +} + +/** How to interpolate between control points. */ +export type Curve = CurveName; + +/** Options for marks that support curves, such as lines and areas. */ +export interface CurveOptions extends CurveAutoOptions { + /** + * The curve (interpolation) method for connecting adjacent points. One of: + * + * - *basis* - a cubic basis spline (repeating the end points) + * - *basis-open* - an open cubic basis spline + * - *basis-closed* - a closed cubic basis spline + * - *bump-x* - a Bézier curve with horizontal tangents + * - *bump-y* - a Bézier curve with vertical tangents + * - *bundle* - a straightened cubic basis spline (suitable for lines only, not areas) + * - *cardinal* - a cubic cardinal spline (with one-sided differences at the ends) + * - *cardinal-open* - an open cubic cardinal spline + * - *cardinal-closed* - an closed cubic cardinal spline + * - *catmull-rom* - a cubic Catmull–Rom spline (with one-sided differences at the ends) + * - *catmull-rom-open* - an open cubic Catmull–Rom spline + * - *catmull-rom-closed* - a closed cubic Catmull–Rom spline + * - *linear* - a piecewise linear curve (*i.e.*, straight line segments) + * - *linear-closed* - a closed piecewise linear curve (*i.e.*, straight line segments) + * - *monotone-x* - a cubic spline that preserves monotonicity in *x* + * - *monotone-y* - a cubic spline that preserves monotonicity in *y* + * - *natural* - a natural cubic spline + * - *step* - a piecewise constant function where *y* changes at the midpoint of *x* + * - *step-after* - a piecewise constant function where *y* changes after *x* + * - *step-before* - a piecewise constant function where *x* changes after *y* + */ + curve?: Curve | ParamRef; +} + +/** Options for marks that support possibly-projected curves. */ +export interface CurveAutoOptions { + /** + * The curve (interpolation) method for connecting adjacent points. One of: + * + * - *basis* - a cubic basis spline (repeating the end points) + * - *basis-open* - an open cubic basis spline + * - *basis-closed* - a closed cubic basis spline + * - *bump-x* - a Bézier curve with horizontal tangents + * - *bump-y* - a Bézier curve with vertical tangents + * - *bundle* - a straightened cubic basis spline (suitable for lines only, not areas) + * - *cardinal* - a cubic cardinal spline (with one-sided differences at the ends) + * - *cardinal-open* - an open cubic cardinal spline + * - *cardinal-closed* - an closed cubic cardinal spline + * - *catmull-rom* - a cubic Catmull–Rom spline (with one-sided differences at the ends) + * - *catmull-rom-open* - an open cubic Catmull–Rom spline + * - *catmull-rom-closed* - a closed cubic Catmull–Rom spline + * - *linear* - a piecewise linear curve (*i.e.*, straight line segments) + * - *linear-closed* - a closed piecewise linear curve (*i.e.*, straight line segments) + * - *monotone-x* - a cubic spline that preserves monotonicity in *x* + * - *monotone-y* - a cubic spline that preserves monotonicity in *y* + * - *natural* - a natural cubic spline + * - *step* - a piecewise constant function where *y* changes at the midpoint of *x* + * - *step-after* - a piecewise constant function where *y* changes after *x* + * - *step-before* - a piecewise constant function where *x* changes after *y* + * - *auto* (default) - like *linear*, but use the (possibly spherical) projection, if any + * + * The *auto* curve is typically used in conjunction with a spherical + * projection to interpolate along geodesics. + */ + curve?: Curve | 'auto' | ParamRef; + + /** + * The tension option only has an effect on bundle, cardinal and Catmull–Rom + * splines (*bundle*, *cardinal*, *cardinal-open*, *cardinal-closed*, + * *catmull-rom*, *catmull-rom-open*, and *catmull-rom-closed*). For bundle + * splines, it corresponds to [beta][1]; for cardinal splines, [tension][2]; + * for Catmull–Rom splines, [alpha][3]. + * + * [1]: https://d3js.org/d3-shape/curve#curveBundle_beta + * [2]: https://d3js.org/d3-shape/curve#curveCardinal_tension + * [3]: https://d3js.org/d3-shape/curve#curveCatmullRom_alpha + */ + tension?: number | ParamRef; +} + +/** + * A built-in stack offset method; one of: + * + * - *normalize* - rescale each stack to fill [0, 1] + * - *center* - align the centers of all stacks + * - *wiggle* - translate stacks to minimize apparent movement + * + * If a given stack has zero total value, the *normalize* offset will not adjust + * the stack’s position. Both the *center* and *wiggle* offsets ensure that the + * lowest element across stacks starts at zero for better default axes. The + * *wiggle* offset is recommended for streamgraphs in conjunction with the + * *inside-out* order. For more, see [Byron & Wattenberg][1]. + * + * [1]: https://leebyron.com/streamgraph/ + */ +export type StackOffsetName = + | 'center' + | 'normalize' + | 'wiggle' + | ('expand' & Record) // deprecated; use normalize + | ('silhouette' & Record); // deprecated; use center + +/** + * A stack offset method; one of: + * + * - *normalize* - rescale each stack to fill [0, 1] + * - *center* - align the centers of all stacks + * - *wiggle* - translate stacks to minimize apparent movement + * + * If a given stack has zero total value, the *normalize* offset will not adjust + * the stack’s position. Both the *center* and *wiggle* offsets ensure that the + * lowest element across stacks starts at zero for better default axes. The + * *wiggle* offset is recommended for streamgraphs in conjunction with the + * *inside-out* order. For more, see [Byron & Wattenberg][1]. + * + * [1]: https://leebyron.com/streamgraph/ + */ +export type StackOffset = StackOffsetName; + +/** + * The built-in stack order methods; one of: + * + * - *x* - alias of *value*; for stackX only + * - *y* - alias of *value*; for stackY only + * - *value* - ascending value (or descending with **reverse**) + * - *sum* - total value per series + * - *appearance* - position of maximum value per series + * - *inside-out* (default with *wiggle*) - order the earliest-appearing series on the inside + * + * The *inside-out* order is recommended for streamgraphs in conjunction with + * the *wiggle* offset. For more, see [Byron & Wattenberg][1]. + * + * [1]: https://leebyron.com/streamgraph/ + */ +export type StackOrderName = 'value' | 'x' | 'y' | 'z' | 'sum' | 'appearance' | 'inside-out'; + +/** + * How to order layers prior to stacking; one of: + * + * - a named stack order method such as *inside-out* or *sum* + * - a field name, for natural order of the corresponding values + * - an accessor function, for natural order of the corresponding values + * - a comparator function for ordering data + * - an array of explicit **z** values in the desired order + */ +export type StackOrder = + | StackOrderName + | `-${StackOrderName}` + | (string & Record) + | any[]; + +/** Options for the stack transform. */ +export interface StackOptions { + /** + * After stacking, an optional **offset** can be applied to translate and + * scale stacks, say to produce a streamgraph; defaults to null for a zero + * baseline (**y** = 0 for stackY, and **x** = 0 for stackX). If the *wiggle* + * offset is used, the default **order** changes to *inside-out*. + */ + offset?: StackOffset | null | ParamRef; + + /** + * The order in which stacks are layered; one of: + * + * - null (default) for input order + * - a named stack order method such as *inside-out* or *sum* + * - a field name, for natural order of the corresponding values + * - a function of data, for natural order of the corresponding values + * - an array of explicit **z** values in the desired order + * + * If the *wiggle* **offset** is used, as for a streamgraph, the default + * changes to *inside-out*. + */ + order?: StackOrder | null | ParamRef; + + /** If true, reverse the effective order of the stacks. */ + reverse?: boolean | ParamRef; + + /** + * The **z** channel defines the series of each value in the stack. Used when + * the **order** is *sum*, *appearance*, *inside-out*, or an explicit array of + * **z** values. + */ + z?: ChannelValue; +} + +/** + * The built-in marker implementations; one of: + * + * - *arrow* - an arrowhead with *auto* orientation + * - *arrow-reverse* - an arrowhead with *auto-start-reverse* orientation + * - *dot* - a filled *circle* with no stroke and 2.5px radius + * - *circle-fill* - a filled circle with a white stroke and 3px radius + * - *circle-stroke* - a stroked circle with a white fill and 3px radius + * - *circle* - alias for *circle-fill* + * - *tick* - a small opposing line + * - *tick-x* - a small horizontal line + * - *tick-y* - a small vertical line + */ +export type MarkerName = + | 'arrow' + | 'arrow-reverse' + | 'dot' + | 'circle' + | 'circle-fill' + | 'circle-stroke' + | 'tick' + | 'tick-x' + | 'tick-y'; + +/** Options for marks that support markers, such as lines and links. */ +export interface MarkerOptions { + /** + * Shorthand to set the same default for markerStart, markerMid, and + * markerEnd; one of: + * + * - a marker name such as *arrow* or *circle* + * - *none* (default) - no marker + * * true - alias for *circle-fill* + * * false or null - alias for *none* + */ + marker?: MarkerName | 'none' | boolean | null | ParamRef; + + /** + * The marker for the starting point of a line segment; one of: + * + * - a marker name such as *arrow* or *circle* + * * *none* (default) - no marker + * * true - alias for *circle-fill* + * * false or null - alias for *none* + */ + markerStart?: MarkerName | 'none' | boolean | null | ParamRef; + + /** + * The marker for any middle (interior) points of a line segment. If the line + * segment only has a start and end point, this option has no effect. One of: + * + * - a marker name such as *arrow* or *circle* + * * *none* (default) - no marker + * * true - alias for *circle-fill* + * * false or null - alias for *none* + * * a function - a custom marker function; see below + */ + markerMid?: MarkerName | 'none' | boolean | null | ParamRef; + + /** + * The marker for the ending point of a line segment; one of: + * + * - a marker name such as *arrow* or *circle* + * * *none* (default) - no marker + * * true - alias for *circle-fill* + * * false or null - alias for *none* + */ + markerEnd?: MarkerName | 'none' | boolean | null | ParamRef; +} diff --git a/packages/spec/src/spec/marks/Raster.ts b/packages/spec/src/spec/marks/Raster.ts new file mode 100644 index 00000000..95e650ea --- /dev/null +++ b/packages/spec/src/spec/marks/Raster.ts @@ -0,0 +1,145 @@ +import { ParamRef } from '../Param.js'; +import { ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** + * A spatial interpolation method; one of: + * + * - *none* - do not perform interpolation (the default), maps samples to single bins + * - *linear* - apply proportional linear interpolation across adjacent bins + * - *nearest* - assign each pixel to the closest sample’s value (Voronoi diagram) + * - *barycentric* - apply barycentric interpolation over the Delaunay triangulation + * - *random-walk* - apply a random walk from each pixel, stopping when near a sample + */ +export type GridInterpolate = + | 'none' + | 'linear' + | 'nearest' + | 'barycentric' + | 'random-walk'; + +/** Options for grid2d marks. */ +export interface Grid2DOptions { + /** + * The horizontal position channel, typically bound to the *x* scale. + * Domain values are binned into a grid with *width* horizontal bins. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel, typically bound to the *y* scale. + * Domain values are binned into a grid with *height* vertical bins. + */ + y?: ChannelValueSpec; + + /** The width (number of columns) of the grid, in actual pixels. */ + width?: number | ParamRef; + + /** The height (number of rows) of the grid, in actual pixels. */ + height?: number | ParamRef; + + /** + * The effective screen size of a raster pixel, used to determine the height + * and width of the raster from the frame’s dimensions; defaults to 1. + */ + pixelSize?: number | ParamRef; + + /** + * The bin padding, one of 1 (default) to include extra padding for the final + * bin, or 0 to make the bins flush with the maximum domain value. + */ + pad?: number | ParamRef; + + /** + * The kernel density bandwidth for smoothing, in pixels. + */ + bandwidth?: number | ParamRef; + + /** + * The spatial interpolation method; one of: + * + * - *none* - do not perform interpolation (the default), maps samples to single bins + * - *linear* - apply proportional linear interpolation across adjacent bins + * - *nearest* - assign each pixel to the closest sample’s value (Voronoi diagram) + * - *barycentric* - apply barycentric interpolation over the Delaunay triangulation + * - *random-walk* - apply a random walk from each pixel, stopping when near a sample + */ + interpolate?: GridInterpolate | null | ParamRef; +} + +/** Options for the raster mark. */ +export interface RasterOptions extends Grid2DOptions, Omit { + /** + * The [image-rendering attribute][1]; defaults to *auto* (bilinear). The + * option may be set to *pixelated* to disable bilinear interpolation for a + * sharper image; however, note that this is not supported in WebKit. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/image-rendering + */ + imageRendering?: string | ParamRef; + + /** + * The fill, typically bound to the *color* scale. Can be specified as a + * constant or a channel based on the input data. Use the special value + * `"density"` to map computed density values to pixel colors. Use an + * aggregate expression to instead visualize an aggregate value per raster + * bin. If fill is set to a constant color or to a non-aggregate field, + * opacity will be used to convey densities. If a non-aggregate (group by) + * field is provided, multiple rasters are created with a unique categorical + * color per layer. + */ + fill?: ChannelValueSpec | ParamRef; + + /** + * The opacity, typically bound to the *opacity* scale. Can be specified as a + * constant or a channel based on the input data. Use the special value + * `"density"` to map computed density values to opacity. Use an aggregate + * expression to instead visualize an aggregate value per raster bin. + */ + fillOpacity?: ChannelValueSpec | ParamRef; +} + +/** The raster mark. */ +export interface Raster extends MarkData, RasterOptions { + /** + * A raster mark which renders a raster image from spatial samples. It + * represents discrete samples in abstract coordinates **x** and **y**; + * the **fill** and **fillOpacity** channels specify further abstract + * values (_e.g._, height in a topographic map) to be spatially + * interpolated to produce an image. + * + * The **x** and **y** data domains are binned into the cells ("pixels") + * of a raster grid, typically with an aggregate function evaluated over + * the binned data. The result can be optionally smoothed (blurred). + * + * To create a smoothed density heatmap, use the heatmap mark, which is + * a raster mark with different default options. + */ + mark: 'raster'; +} + +/** The heatmap mark. */ +export interface Heatmap extends MarkData, RasterOptions { + /** + * Like raster, but with default options for accurate density estimation + * via smoothing. The *bandwidth* (20), *interpolate* ("linear"), and + * *pixelSize* (2) options are set to produce smoothed density heatmaps. + */ + mark: 'heatmap'; +} + +/** The rasterTile mark. */ +export interface RasterTile extends MarkData, RasterOptions { + /** + * An experimental raster mark which performs tiling and prefetching to + * support more scalable rasters upon panning the domain. Uses a tile + * size that matches with current width and height, and prefetches data + * from neighboring tile segments. + */ + mark: 'rasterTile'; + + /** + * The coordinates of the tile origin in the **x** and **y** data domains. + * Defaults to [0, 0]. + */ + origin?: [number, number] | ParamRef; +} diff --git a/packages/spec/src/spec/marks/Rect.ts b/packages/spec/src/spec/marks/Rect.ts new file mode 100644 index 00000000..412ad5d9 --- /dev/null +++ b/packages/spec/src/spec/marks/Rect.ts @@ -0,0 +1,183 @@ +import { ParamRef } from '../Param.js'; +import { Interval } from '../PlotTypes.js'; +import { + ChannelValueIntervalSpec, ChannelValueSpec, InsetOptions, + MarkData, MarkOptions, StackOptions +} from './Marks.js'; + +/** Options for marks that render rectangles, including bar, cell, and rect. */ +export interface RectCornerOptions { + /** + * The rounded corner [*x*-radius][1], either in pixels or as a percentage of + * the rect width. If **rx** is not specified, it defaults to **ry** if + * present, and otherwise draws square corners. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx + */ + rx?: number | string | ParamRef; + + /** + * The rounded corner [*y*-radius][1], either in pixels or as a percentage of + * the rect height. If **ry** is not specified, it defaults to **rx** if + * present, and otherwise draws square corners. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry + */ + ry?: number | string | ParamRef; +} + +/** Options for the rect mark. */ +export interface RectOptions extends MarkOptions, InsetOptions, RectCornerOptions, StackOptions { + /** + * The horizontal position (or length/width) channel, typically bound to the + * *x* scale. + * + * If an **interval** is specified, then **x1** and **x2** are derived from + * **x**, representing the lower and upper bound of the containing interval, + * respectively. For example, for a vertical bar chart of items sold by day: + * + * ```js + * Plot.rectY(sales, {x: "date", interval: "day", y2: "items"}) + * ``` + * + * If *x* represents ordinal values, use a bar or cell mark instead. + */ + x?: ChannelValueIntervalSpec; + + /** + * The required primary (starting, often left) horizontal position channel, + * typically bound to the *x* scale. Setting this option disables the rectX + * mark’s implicit stackX transform. + * + * If *x* represents ordinal values, use a bar or cell mark instead. + */ + x1?: ChannelValueSpec; + + /** + * The required secondary (ending, often right) horizontal position channel, + * typically bound to the *x* scale. Setting this option disables the rectX + * mark’s implicit stackX transform. + * + * If *x* represents ordinal values, use a bar or cell mark instead. + */ + x2?: ChannelValueSpec; + + /** + * The vertical position (or length/height) channel, typically bound to the + * *y* scale. + * + * If an **interval** is specified, then **y1** and **y2** are derived from + * **y**, representing the lower and upper bound of the containing interval, + * respectively. For example, for a horizontal bar chart of items sold by day: + * + * ```js + * Plot.rectX(sales, {y: "date", interval: "day", x2: "items"}) + * ``` + * + * If *y* represents ordinal values, use a bar or cell mark instead. + */ + y?: ChannelValueIntervalSpec; + + /** + * The required primary (starting, often bottom) vertical position channel, + * typically bound to the *y* scale. Setting this option disables the rectY + * mark’s implicit stackY transform. + * + * If *y* represents ordinal values, use a bar or cell mark instead. + */ + y1?: ChannelValueSpec; + + /** + * The required secondary (ending, often top) vertical position channel, + * typically bound to the *y* scale. Setting this option disables the rectY + * mark’s implicit stackY transform. + * + * If *y* represents ordinal values, use a bar or cell mark instead. + */ + y2?: ChannelValueSpec; + + /** + * How to convert a continuous value (**x** for rectY, **y** for rectX, or + * both for rect) into an interval (**x1** and **x2** for rectY, or **y1** and + * **y2** for rectX, or both for rect); one of: + * + * - a named time interval such as *day* (for date intervals) + * - a number (for number intervals), defining intervals at integer multiples of *n* + * + * Setting this option disables the implicit stack transform (stackX for rectX, + * or stackY for rectY). + */ + interval?: Interval | ParamRef; +} + +/** Options for the rectX mark. */ +export interface RectXOptions extends RectOptions { + /** + * The horizontal position (or length/width) channel, typically bound to the + * *x* scale. + * + * If neither **x1** nor **x2** is specified, an implicit stackX transform is + * applied and **x** defaults to the identity function, assuming that *data* = + * [*x₀*, *x₁*, *x₂*, …]. Otherwise, if only one of **x1** or **x2** is + * specified, the other defaults to **x**, which defaults to zero. + */ + x?: ChannelValueSpec; // disallow x interval +} + +/** Options for the rectY mark. */ +export interface RectYOptions extends RectOptions { + /** + * The vertical position (or length/height) channel, typically bound to the + * *y* scale. + * + * If neither **y1** nor **y2** is specified, an implicit stackY transform is + * applied and **y** defaults to the identity function, assuming that *data* = + * [*y₀*, *y₁*, *y₂*, …]. Otherwise, if only one of **y1** or **y2** is + * specified, the other defaults to **y**, which defaults to zero. + */ + y?: ChannelValueSpec; // disallow y interval +} + +/** The rect mark. */ +export interface Rect extends MarkData, RectOptions { + /** + * A rect mark. The rectangle extends horizontally from **x1** to **x2**, + * and vertically from **y1** to **y2**. The position channels are often + * derived with a transform. + * + * When **y** extends from zero, for example for a histogram where the + * height of each rect reflects a count of values, use the rectY mark for an + * implicit stackY transform; similarly, if **x** extends from zero, use the + * rectX mark for an implicit stackX transform. + * + * If an **interval** is specified, then **x1** and **x2** are derived from + * **x**, and **y1** and **y2** are derived from **y**, each representing the + * lower and upper bound of the containing interval, respectively. + * + * Both *x* and *y* should be quantitative or temporal; otherwise, use a bar + * or cell mark. + */ + mark: 'rect'; +} + +/** The rectX mark. */ +export interface RectX extends MarkData, RectXOptions { + /** + * Like rect, but if neither **x1** nor **x2** is specified, an implicit + * stackX transform is applied to **x**, and if **x** is not specified, it + * defaults to the identity function, assuming that *data* is an array of + * numbers [*x₀*, *x₁*, *x₂*, …]. + */ + mark: 'rectX'; +} + +/** The rectY mark. */ +export interface RectY extends MarkData, RectYOptions { + /** + * Like rect, but if neither **y1** nor **y2** is specified, apply an + * implicit stackY transform is applied to **y**, and if **y** is not + * specified, it defaults to the identity function, assuming that *data* + * is an array of numbers [*y₀*, *y₁*, *y₂*, …]. + */ + mark: 'rectY'; +} diff --git a/packages/spec/src/spec/marks/Regression.ts b/packages/spec/src/spec/marks/Regression.ts new file mode 100644 index 00000000..dcef77d8 --- /dev/null +++ b/packages/spec/src/spec/marks/Regression.ts @@ -0,0 +1,63 @@ +import { ParamRef } from '../Param.js'; +import { ChannelValue, ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** Options for regression marks. */ +interface RegressionOptions extends MarkOptions { + /** + * The confidence interval in (0, 1), or 0 to hide bands; defaults to 0.95. + */ + ci?: number | ParamRef; + + /** + * The distance in pixels between samples of the confidence band; + * defaults to 4. + */ + precision?: number | ParamRef; + + /** + * An optional ordinal channel for grouping data into (possibly stacked) + * series, producing an independent regression for each group. If not + * specified, it defaults to **fill** if a channel, or **stroke** if a + * channel. + */ + z?: ChannelValue; +} + +/** Options for the regressionY mark. */ +export interface RegressionYOptions extends RegressionOptions { + /** + * The independent variable horizontal position channel, typically bound to + * the *x* scale; defaults to the zero-based index of the data [0, 1, 2, …]. + */ + x?: ChannelValueSpec; + + /** + * The dependent variable vertical position channel, typically bound to the + * *y* scale; defaults to identity, assuming that *data* = [*y₀*, *y₁*, *y₂*, + * …]. + */ + y?: ChannelValueSpec; +} + +/** The regressionY mark. */ +export interface RegressionY extends MarkData, RegressionYOptions { + /** + * A mark that draws [linear regression][1] lines with confidence bands, + * representing the estimated relation of a dependent variable (*y*) on an + * independent variable (*x*). + * + * The linear regression line is fit using the [least squares][2] approach. + * See Torben Jansen’s [“Linear regression with confidence bands”][3] and + * [this StatExchange question][4] for details on the confidence interval + * calculation. + * + * Multiple regressions can be produced by specifying a **z**, **fill**, or + * **stroke** channel. + * + * [1]: https://en.wikipedia.org/wiki/Linear_regression + * [2]: https://en.wikipedia.org/wiki/Least_squares + * [3]: https://observablehq.com/@toja/linear-regression-with-confidence-bands + * [4]: https://stats.stackexchange.com/questions/101318/understanding-shape-and-calculation-of-confidence-bands-in-linear-regression + */ + mark: 'regressionY'; +} diff --git a/packages/spec/src/spec/marks/Rule.ts b/packages/spec/src/spec/marks/Rule.ts new file mode 100644 index 00000000..f2c8ac3f --- /dev/null +++ b/packages/spec/src/spec/marks/Rule.ts @@ -0,0 +1,113 @@ +import { ParamRef } from '../Param.js'; +import { Interval } from '../PlotTypes.js'; +import { + ChannelValueIntervalSpec, ChannelValueSpec, InsetOptions, + MarkDataOptional, MarkOptions, MarkerOptions +} from './Marks.js'; + +/** Options for the ruleX and ruleY marks. */ +interface RuleOptions extends MarkOptions, MarkerOptions { + /** + * How to convert a continuous value (**y** for ruleX, or **x** for ruleY) + * into an interval (**y1** and **y2** for ruleX, or **x1** and **x2** for + * ruleY); one of: + * + * - a named time interval such as *day* (for date intervals) + * - a number (for number intervals), defining intervals at integer multiples of *n* + */ + interval?: Interval | ParamRef; +} + +/** Options for the ruleX mark. */ +export interface RuleXOptions extends RuleOptions, Omit { + /** + * The horizontal position of the tick; an optional channel bound to the *x* + * scale. If not specified, the rule will be horizontally centered in the + * plot’s frame. + */ + x?: ChannelValueSpec; + + /** + * Shorthand for specifying both the primary and secondary vertical position + * of the tick as the bounds of the containing interval; can only be used in + * conjunction with the **interval** option. + */ + y?: ChannelValueIntervalSpec; + + /** + * The primary (starting, often bottom) vertical position of the tick; a + * channel bound to the *y* scale. + * + * If *y* represents ordinal values, use a tickX mark instead. + */ + y1?: ChannelValueSpec; + + /** + * The secondary (ending, often top) vertical position of the tick; a channel + * bound to the *y* scale. + * + * If *y* represents ordinal values, use a tickX mark instead. + */ + y2?: ChannelValueSpec; +} + +/** Options for the ruleY mark. */ +export interface RuleYOptions extends RuleOptions, Omit { + /** + * Shorthand for specifying both the primary and secondary horizontal position + * of the tick as the bounds of the containing interval; can only be used in + * conjunction with the **interval** option. + */ + x?: ChannelValueIntervalSpec; + + /** + * The primary (starting, often left) horizontal position of the tick; a + * channel bound to the *x* scale. + * + * If *x* represents ordinal values, use a tickY mark instead. + */ + x1?: ChannelValueSpec; + + /** + * The secondary (ending, often right) horizontal position of the tick; a + * channel bound to the *x* scale. + * + * If *x* represents ordinal values, use a tickY mark instead. + */ + x2?: ChannelValueSpec; + + /** + * The vertical position of the tick; an optional channel bound to the *y* + * scale. If not specified, the rule will be vertically centered in the plot’s + * frame. + */ + y?: ChannelValueSpec; +} + +/** The ruleX mark. */ +export interface RuleX extends MarkDataOptional, RuleXOptions { + /** + * A horizontally-positioned ruleX mark (a vertical line, |). The **x** + * channel specifies the rule’s horizontal position and defaults to identity, + * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]; the optional **y1** and + * **y2** channels specify its vertical extent. + * + * The ruleX mark is often used to highlight specific *x* values. + * If *y* represents ordinal values, use a tickX mark instead. + */ + mark: 'ruleX'; +} + +/** The ruleY mark. */ +export interface RuleY extends MarkDataOptional, RuleXOptions { + /** + * A vertically-positioned ruleY mark (a horizontal line, —). The **y** + * channel specifies the rule's vertical position and defaults to identity, + * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]; the optional **x1** and + * **x2** channels specify its horizontal extent. + * + * The ruleY mark is often used to highlight specific *y* values. + * If *x* represents ordinal values, use a tickY mark instead. + */ + mark: 'ruleY'; +} diff --git a/packages/spec/src/spec/marks/Text.ts b/packages/spec/src/spec/marks/Text.ts new file mode 100644 index 00000000..9c2d7f83 --- /dev/null +++ b/packages/spec/src/spec/marks/Text.ts @@ -0,0 +1,122 @@ +import { ParamRef } from '../Param.js'; +import { FrameAnchor, Interval } from '../PlotTypes.js'; +import { + ChannelValue, ChannelValueIntervalSpec, ChannelValueSpec, + MarkDataOptional, MarkOptions, TextStyles +} from './Marks.js'; + +/** Options for the text mark. */ +export interface TextOptions extends MarkOptions, TextStyles { + /** + * The horizontal position channel specifying the text’s anchor point, + * typically bound to the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The vertical position channel specifying the text’s anchor point, typically + * bound to the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * The text contents channel, possibly with line breaks (\n, \r\n, or \r). If + * not specified, defaults to the zero-based index [0, 1, 2, …]. + */ + text?: ChannelValue; + + /** + * The frame anchor specifies defaults for **x** and **y**, along with + * **textAnchor** and **lineAnchor**, based on the plot’s frame; it may be one + * of the four sides (*top*, *right*, *bottom*, *left*), one of the four + * corners (*top-left*, *top-right*, *bottom-right*, *bottom-left*), or the + * *middle* of the frame. + */ + frameAnchor?: FrameAnchor | ParamRef; + + /** + * The line anchor controls how text is aligned (typically vertically) + * relative to its anchor point; it is one of *top*, *bottom*, or *middle*. If + * the frame anchor is *top*, *top-left*, or *top-right*, the default line + * anchor is *top*; if the frame anchor is *bottom*, *bottom-right*, or + * *bottom-left*, the default is *bottom*; otherwise it is *middle*. + */ + lineAnchor?: 'top' | 'middle' | 'bottom' | ParamRef; + + /** + * The rotation angle in degrees clockwise; a constant or a channel; defaults + * to 0°. When a number, it is interpreted as a constant; otherwise it is + * interpreted as a channel. + */ + rotate?: ChannelValue | ParamRef; +} + +/** Options for the textX mark. */ +export interface TextXOptions extends Omit { + /** + * The vertical position of the text’s anchor point, typically bound to the + * *y* scale. + */ + y?: ChannelValueIntervalSpec; + + /** + * An interval (such as *day* or a number), to transform **y** values to the + * middle of the interval. + */ + interval?: Interval | ParamRef; +} + +/** Options for the textY mark. */ +export interface TextYOptions extends Omit { + /** + * The horizontal position of the text’s anchor point, typically bound to the + * *x* scale. + */ + x?: ChannelValueIntervalSpec; + + /** + * An interval (such as *day* or a number), to transform **x** values to the + * middle of the interval. + */ + interval?: Interval; +} + +/** The text mark. */ +export interface Text extends MarkDataOptional, TextOptions { + /** + * A text mark. The **text** channel specifies the textual contents of the + * mark, which may be preformatted with line breaks (\n, \r\n, or \r), or + * wrapped or clipped using the **lineWidth** and **textOverflow** options. + * + * If **text** contains numbers or dates, a default formatter will be + * applied, and the **fontVariant** will default to *tabular-nums* instead + * of *normal*. If **text** is not specified, it defaults to the identity + * function for primitive data (such as numbers, dates, and strings), and to + * the zero-based index [0, 1, 2, …] for objects (so that something + * identifying is visible by default). + * + * If either **x** or **y** is not specified, the default is determined by + * the **frameAnchor** option. + */ + mark: 'text'; +} + +/** The textX mark. */ +export interface TextX extends MarkDataOptional, TextXOptions { + /** + * Like text, but **x** defaults to the identity function, assuming that + * *data* = [*x₀*, *x₁*, *x₂*, …]. If an **interval** is specified, such as + * *day*, **y** is transformed to the middle of the interval. + */ + mark: 'textX'; +} + +/** The textY mark. */ +export interface TextY extends MarkDataOptional, TextYOptions { + /** + * Like text, but **y** defaults to the identity function, assuming that + * *data* = [*y₀*, *y₁*, *y₂*, …]. If an **interval** is specified, such as + * *day*, **x** is transformed to the middle of the interval. + */ + mark: 'textY'; +} diff --git a/packages/spec/src/spec/marks/Tick.ts b/packages/spec/src/spec/marks/Tick.ts new file mode 100644 index 00000000..7bce5157 --- /dev/null +++ b/packages/spec/src/spec/marks/Tick.ts @@ -0,0 +1,69 @@ +import { + ChannelValueSpec, InsetOptions, MarkData, MarkOptions, MarkerOptions +} from './Marks.js'; + +/** Options for the tickX mark. */ +export interface TickXOptions extends MarkOptions, MarkerOptions, Omit { + /** + * The required horizontal position of the tick; a channel typically bound to + * the *x* scale. + */ + x?: ChannelValueSpec; + + /** + * The optional vertical position of the tick; an ordinal channel typically + * bound to the *y* scale. If not specified, the tick spans the vertical + * extent of the frame; otherwise the *y* scale must be a *band* scale. + * + * If *y* represents quantitative or temporal values, use a ruleX mark + * instead. + */ + y?: ChannelValueSpec; +} + +/** Options for the tickY mark. */ +export interface TickYOptions extends MarkOptions, MarkerOptions, Omit { + /** + * The required vertical position of the tick; a channel typically bound to + * the *y* scale. + */ + y?: ChannelValueSpec; + + /** + * The optional horizontal position of the tick; an ordinal channel typically + * bound to the *x* scale. If not specified, the tick spans the horizontal + * extent of the frame; otherwise the *x* scale must be a *band* scale. + * + * If *x* represents quantitative or temporal values, use a ruleY mark + * instead. + */ + x?: ChannelValueSpec; +} + +/** The tickX mark. */ +export interface TickX extends MarkData, TickXOptions { + /** + * A horizontally-positioned tickX mark (a vertical line, |). The **x** + * channel specifies the tick’s horizontal position and defaults to identity, + * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]; the optional **y** ordinal + * channel specifies its vertical position. + * + * If *y* represents quantitative or temporal values, use a ruleX mark + * instead. + */ + mark: 'tickX'; +} + +/** The tickY mark. */ +export interface TickY extends MarkData, TickYOptions { + /** + * A vertically-positioned tickY mark (a horizontal line, —). The **y** + * channel specifies the tick's vertical position and defaults to identity, + * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]; the optional **x** ordinal + * channel specifies its horizontal position. + * + * If *x* represents quantitative or temporal values, use a ruleY mark + * instead. + */ + mark: 'tickY'; +} diff --git a/packages/spec/src/spec/marks/Vector.ts b/packages/spec/src/spec/marks/Vector.ts new file mode 100644 index 00000000..1b6a66cc --- /dev/null +++ b/packages/spec/src/spec/marks/Vector.ts @@ -0,0 +1,113 @@ +import { ParamRef } from '../Param.js'; +import { FrameAnchor } from '../PlotTypes.js'; +import { ChannelValue, ChannelValueSpec, MarkData, MarkOptions } from './Marks.js'; + +/** + * The built-in vector shape implementations; one of: + * + * - *arrow* - a straight line with an open arrowhead at the end (↑) + * - *spike* - an isosceles triangle with a flat base (▲) + */ +export type VectorShapeName = 'arrow' | 'spike'; + +/** How to draw a vector: either a named shape or a custom implementation. */ +export type VectorShape = VectorShapeName; + +/** Options for the vector mark. */ +export interface VectorOptions extends MarkOptions { + /** + * The horizontal position of the vector’s anchor point; an optional channel + * bound to the *x* scale. Default depends on the **frameAnchor**. + */ + x?: ChannelValueSpec; + + /** + * The vertical position of the vector’s anchor point; an optional channel + * bound to the *y* scale. Default depends on the **frameAnchor**. + */ + y?: ChannelValueSpec; + + /** + * The vector shape’s radius, such as half the width of the *arrow*’s head or + * the *spike*’s base; a constant number in pixels. Defaults to 3.5 pixels. + */ + r?: number | ParamRef; + + /** + * The vector’s length; either an optional channel bound to the *length* scale + * or a constant number in pixels. Defaults to 12 pixels. + */ + length?: ChannelValueSpec; + + /** + * The vector’s orientation (rotation angle); either a constant number in + * degrees clockwise, or an optional channel (with no associated scale). + * Defaults to 0 degrees with the vector pointing up. + */ + rotate?: ChannelValue; + + /** The shape of the vector; a constant. Defaults to *arrow*. */ + shape?: VectorShape | ParamRef; + + /** + * The vector’s position along its orientation relative to its anchor point; a + * constant. Assuming a default **rotate** angle of 0°, one of: + * + * - *start* - from [*x*, *y*] to [*x*, *y* - *l*] + * - *middle* (default) - from [*x*, *y* + *l* / 2] to [*x*, *y* - *l* / 2] + * - *end* - from [*x*, *y* + *l*] to [*x*, *y*] + * + * where [*x*, *y*] is the vector’s anchor point and *l* is the vector’s + * (possibly scaled) length in pixels. + */ + anchor?: 'start' | 'middle' | 'end' | ParamRef; + + /** + * The vector’s frame anchor, to default **x** and **y** relative to the + * frame; a constant representing one of the frame corners (*top-left*, + * *top-right*, *bottom-right*, *bottom-left*), sides (*top*, *right*, + * *bottom*, *left*), or *middle* (default). Has no effect if both **x** + * and **y** are specified. + */ + frameAnchor?: FrameAnchor | ParamRef; +} + +/** The vector mark. */ +export interface Vector extends MarkData, VectorOptions { + /** + * A vector mark. + * + * If none of **frameAnchor**, **x**, and **y** are specified, then **x** and + * **y** default to accessors assuming that *data* contains tuples [[*x₀*, + * *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] + */ + mark: 'vector'; +} + +/** The vectorX mark. */ +export interface VectorX extends MarkData, VectorOptions { + /** + * Like vector, but **x** instead defaults to the identity function and **y** + * defaults to null, assuming that *data* is an array of numbers [*x₀*, *x₁*, + * *x₂*, …]. + */ + mark: 'vectorX'; +} + +/** The vectorY mark. */ +export interface VectorY extends MarkData, VectorOptions { + /** + * Like vector, but **y** instead defaults to the identity function and **x** + * defaults to null, assuming that *data* is an array of numbers [*y₀*, *y₁*, + * *y₂*, …]. + */ + mark: 'vectorY'; +} + +/** The spike mark. */ +export interface Spike extends MarkData, VectorOptions { + /** + * Like vector, but with default *options* suitable for drawing a spike map. + */ + mark: 'spike'; +} diff --git a/packages/spec/src/util.js b/packages/spec/src/util.js index 844af67e..f45b5089 100644 --- a/packages/spec/src/util.js +++ b/packages/spec/src/util.js @@ -45,3 +45,11 @@ export function isFunction(value) { export function error(message, data) { throw Object.assign(Error(message), { data }); } + +const re = /^(?:[-+]\d{2})?\d{4}(?:-\d{2}(?:-\d{2})?)?(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[-+]\d{2}:?\d{2})?)?$/; + +// adapted from https://github.com/mbostock/isoformat/ +export function isoparse(string, fallback) { + if (!re.test(string += '')) return fallback; + return new Date(string); +} diff --git a/packages/spec/tsconfig.json b/packages/spec/tsconfig.json new file mode 100644 index 00000000..d6eaae60 --- /dev/null +++ b/packages/spec/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": ["src/**/*.js", "src/**/*.ts"], + "compilerOptions": { + "allowJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "dist/types", + "module": "node16", + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/packages/sql/src/Query.js b/packages/sql/src/Query.js index d14e4441..8cd1bf41 100644 --- a/packages/sql/src/Query.js +++ b/packages/sql/src/Query.js @@ -53,6 +53,7 @@ export class Query { qualify: [], orderby: [] }; + this.cteFor = null; } clone() { @@ -61,9 +62,18 @@ export class Query { return q; } + /** + * Retrieve current WITH common table expressions (CTEs). + * @returns {any[]} + *//** + * Add WITH common table expressions (CTEs). + * @param {...any} expr Expressions to add. + * @returns {this} + */ with(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.with; } else { const list = []; @@ -88,9 +98,18 @@ export class Query { } } + /** + * Retrieve current SELECT expressions. + * @returns {any[]} + *//** + * Add SELECT expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ select(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.select; } else { const list = []; @@ -124,9 +143,18 @@ export class Query { return this; } + /** + * Retrieve current from expressions. + * @returns {any[]} + *//** + * Provide table from expressions. + * @param {...any} expr + * @returns {this} + */ from(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.from; } else { const list = []; @@ -157,9 +185,19 @@ export class Query { return this.from(...expr); } + /** + * Retrieve current SAMPLE settings. + * @returns {any[]} + *//** + * Set SAMPLE settings. + * @param {number|object} value The percentage or number of rows to sample. + * @param {string} [method] The sampling method to use. + * @returns {this} + */ sample(value, method) { const { query } = this; if (arguments.length === 0) { + // @ts-ignore return query.sample; } else { let spec = value; @@ -173,9 +211,18 @@ export class Query { } } + /** + * Retrieve current WHERE expressions. + * @returns {any[]} + *//** + * Add WHERE expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ where(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.where; } else { query.where = query.where.concat( @@ -190,9 +237,18 @@ export class Query { return this.where(...expr); } + /** + * Retrieve current GROUP BY expressions. + * @returns {any[]} + *//** + * Add GROUP BY expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ groupby(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.groupby; } else { query.groupby = query.groupby.concat( @@ -207,9 +263,18 @@ export class Query { return this.groupby(...expr); } + /** + * Retrieve current HAVING expressions. + * @returns {any[]} + *//** + * Add HAVING expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ having(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.having; } else { query.having = query.having.concat( @@ -219,9 +284,18 @@ export class Query { } } + /** + * Retrieve current WINDOW definitions. + * @returns {any[]} + *//** + * Add WINDOW definitions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ window(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.window; } else { const list = []; @@ -239,9 +313,18 @@ export class Query { } } + /** + * Retrieve current QUALIFY expressions. + * @returns {any[]} + *//** + * Add QUALIFY expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ qualify(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.qualify; } else { query.qualify = query.qualify.concat( @@ -251,9 +334,18 @@ export class Query { } } + /** + * Retrieve current ORDER BY expressions. + * @returns {any[]} + *//** + * Add ORDER BY expressions. + * @param {...any} expr Expressions to add. + * @returns {this} + */ orderby(...expr) { const { query } = this; if (expr.length === 0) { + // @ts-ignore return query.orderby; } else { query.orderby = query.orderby.concat( @@ -263,6 +355,14 @@ export class Query { } } + /** + * Retrieve current LIMIT value. + * @returns {number|null} + *//** + * Set the query result LIMIT. + * @param {number} value The limit value. + * @returns {this} + */ limit(value) { const { query } = this; if (arguments.length === 0) { @@ -273,6 +373,14 @@ export class Query { } } + /** + * Retrieve current OFFSET value. + * @returns {number|null} + *//** + * Set the query result OFFSET. + * @param {number} value The offset value. + * @returns {this} + */ offset(value) { const { query } = this; if (arguments.length === 0) { @@ -391,6 +499,7 @@ export class SetOperation { this.op = op; this.queries = queries.map(q => q.clone()); this.query = { orderby: [] }; + this.cteFor = null; } clone() { diff --git a/packages/sql/src/aggregates.js b/packages/sql/src/aggregates.js index 30574889..e6637a2d 100644 --- a/packages/sql/src/aggregates.js +++ b/packages/sql/src/aggregates.js @@ -19,11 +19,24 @@ export function agg(strings, ...exprs) { * rather than instantiate this class. */ export class AggregateFunction extends SQLExpression { + /** + * Create a new AggregateFunction instance. + * @param {*} op The aggregate operation. + * @param {*} [args] The aggregate function arguments. + * @param {*} [type] The SQL data type to cast to. + * @param {boolean} [isDistinct] Flag indicating if this is a distinct value aggregate. + * @param {*} [filter] Filtering expression to apply prior to aggregation. + */ constructor(op, args, type, isDistinct, filter) { args = (args || []).map(asColumn); const { strings, exprs } = aggExpr(op, args, type, isDistinct, filter); const { spans, cols } = parseSQL(strings, exprs); - super(spans, cols, { aggregate: op, args, type, isDistinct, filter }); + super(spans, cols); + this.aggregate = op; + this.args = args; + this.type = type; + this.isDistinct = isDistinct; + this.filter = filter; } get basis() { @@ -37,36 +50,69 @@ export class AggregateFunction extends SQLExpression { return `${op.toLowerCase()}${tail}`; } + /** + * Return a new derived aggregate function over distinct values. + * @returns {AggregateFunction} A new aggregate function. + */ distinct() { const { aggregate: op, args, type, filter } = this; return new AggregateFunction(op, args, type, true, filter); } + /** + * Return a new derived aggregate function that filters values. + * @param {*} filter The filter expresion. + * @returns {AggregateFunction} A new aggregate function. + */ where(filter) { const { aggregate: op, args, type, isDistinct } = this; return new AggregateFunction(op, args, type, isDistinct, filter); } + /** + * Return a new window function over this aggregate. + * @returns {WindowFunction} A new aggregate function. + */ window() { const { aggregate: op, args, type, isDistinct } = this; const func = new AggregateFunction(op, args, null, isDistinct); return new WindowFunction(op, func, type); } + /** + * Return a window function over this aggregate with the given partitioning. + * @param {*} expr The grouping (partition by) criteria for the window function. + * @returns {WindowFunction} A new window function. + */ partitionby(...expr) { return this.window().partitionby(...expr); } + /** + * Return a window function over this aggregate with the given ordering. + * @param {*} expr The sorting (order by) criteria for the window function. + * @returns {WindowFunction} A new window function. + */ orderby(...expr) { return this.window().orderby(...expr); } - rows(prev, next) { - return this.window().rows(prev, next); + /** + * Return a window function over this aggregate with the given row frame. + * @param {(number|null)[] | import('./expression.js').ParamLike} frame The row-based window frame. + * @returns {WindowFunction} A new window function. + */ + rows(frame) { + return this.window().rows(frame); } - range(prev, next) { - return this.window().range(prev, next); + /** + * Return a window function over this aggregate with the given range frame. + * @param {(number|null)[] | import('./expression.js').ParamLike} frame The range-based window frame. + * @returns {WindowFunction} A new window function. + */ + range(frame) { + return this.window().range(frame); } } diff --git a/packages/sql/src/desc.js b/packages/sql/src/desc.js index df1f0861..2723988d 100644 --- a/packages/sql/src/desc.js +++ b/packages/sql/src/desc.js @@ -4,8 +4,8 @@ import { asColumn } from './ref.js'; /** * Annotate an expression to indicate descending sort order. * Null values are ordered last. - * @param {SQLExpression|string} expr A SQL expression or column name string. - * @returns {SQLExpression} An expression with descending order. + * @param {import('./expression.js').SQLExpression|string} expr A SQL expression or column name string. + * @returns {import('./expression.js').SQLExpression} An expression with descending order. */ export function desc(expr) { const e = asColumn(expr); diff --git a/packages/sql/src/expression.js b/packages/sql/src/expression.js index 585e0b85..9c4b6f6a 100644 --- a/packages/sql/src/expression.js +++ b/packages/sql/src/expression.js @@ -1,16 +1,25 @@ import { literalToSQL } from './to-sql.js'; +/** + * @typedef {{ + * value: any; + * addEventListener(type: string, callback: Function): any; + * column?: string, + * columns?: string[] + * }} ParamLike + */ + /** * Test if a value is parameter-like. Parameters have addEventListener methods. * @param {*} value The value to test. - * @returns True if the value is param-like, false otherwise. + * @returns {value is ParamLike} True if the value is param-like, false otherwise. */ export const isParamLike = value => typeof value?.addEventListener === 'function'; /** * Test if a value is a SQL expression instance. * @param {*} value The value to test. - * @returns {boolean} True if value is a SQL expression, false otherwise. + * @returns {value is SQLExpression} True if value is a SQL expression, false otherwise. */ export function isSQLExpression(value) { return value instanceof SQLExpression; @@ -24,7 +33,7 @@ export class SQLExpression { /** * Create a new SQL expression instance. - * @param {(string|SQLExpression|Ref)[]} parts The parts of the expression. + * @param {(string | ParamLike | SQLExpression | import('./ref.js').Ref)[]} parts The parts of the expression. * @param {string[]} [columns=[]] The column dependencies * @param {object} [props] Additional properties for this expression. */ @@ -35,6 +44,8 @@ export class SQLExpression { const params = this._expr.filter(part => isParamLike(part)); if (params.length > 0) { + /** @type {ParamLike[]} */ + // @ts-ignore this._params = Array.from(new Set(params)); this._params.forEach(param => { param.addEventListener('value', () => update(this, this.map?.get('value'))); diff --git a/packages/sql/src/load/load.js b/packages/sql/src/load/load.js index b2cc9ee7..2132091c 100644 --- a/packages/sql/src/load/load.js +++ b/packages/sql/src/load/load.js @@ -38,7 +38,7 @@ export function loadSpatial(tableName, fileName, options = {}) { : Object.entries(opt) .map(([key, value]) => `${key}=${value}`) .join(', '); - rest.open_options = open.toUpperCase(); + Object.assign(rest, { open_options: open.toUpperCase() }); } // TODO: handle box_2d for spatial_filter_box option // TODO: handle wkb_blob for spatial_filter option diff --git a/packages/sql/src/load/sql-from.js b/packages/sql/src/load/sql-from.js index a0e7ff31..8feecf00 100644 --- a/packages/sql/src/load/sql-from.js +++ b/packages/sql/src/load/sql-from.js @@ -1,5 +1,12 @@ import { literalToSQL } from '../to-sql.js'; +/** + * Create a SQL query that embeds the given data for loading. + * @param {*} data The dataset + * @param {object} [options] Loading options + * @param {string[]|object} [options.columns] The columns to include + * @returns {string} SQL query string to load data + */ export function sqlFrom(data, { columns = Object.keys(data?.[0] || {}) } = {}) { diff --git a/packages/sql/src/ref.js b/packages/sql/src/ref.js index 95b788f6..616e4cf2 100644 --- a/packages/sql/src/ref.js +++ b/packages/sql/src/ref.js @@ -5,7 +5,7 @@ export class Ref { /** * Create a new Ref instance. * @param {string|Ref|null} table The table name. - * @param {string|null} column The column name. + * @param {string|null} [column] The column name. */ constructor(table, column) { if (table) this.table = String(table); @@ -87,11 +87,11 @@ export function relation(name) { /** * Create a column reference. - * @param {string} [table] The table name (optional). - * @param {string} column The column name. + * @param {string} table The table name (optional). + * @param {string} [column] The column name. * @returns {Ref} The generated column reference. */ -export function column(table, column) { +export function column(table, column = null) { if (arguments.length === 1) { column = table; table = null; diff --git a/packages/sql/src/scales.js b/packages/sql/src/scales.js index 95175c74..42976792 100644 --- a/packages/sql/src/scales.js +++ b/packages/sql/src/scales.js @@ -13,7 +13,7 @@ function scaleLinear() { }; } -function scaleLog({ base } = {}) { +function scaleLog({ base = null } = {}) { if (base == null || base === Math.E) { return { apply: Math.log, diff --git a/packages/sql/src/windows.js b/packages/sql/src/windows.js index 0143eb9b..c1a0ed07 100644 --- a/packages/sql/src/windows.js +++ b/packages/sql/src/windows.js @@ -9,6 +9,16 @@ import { repeat } from './repeat.js'; * rather than instantiate this class. */ export class WindowFunction extends SQLExpression { + /** + * Create a new WindowFunction instance. + * @param {string} op The window operation indicator. + * @param {*} func The window function expression. + * @param {*} [type] The SQL data type to cast to. + * @param {string} [name] The window definition name. + * @param {*} [group] Grouping (partition by) expressions. + * @param {*} [order] Sorting (order by) expressions. + * @param {*} [frame] The window frame definition. + */ constructor(op, func, type, name, group = '', order = '', frame = '') { // build and parse expression let expr; @@ -24,7 +34,14 @@ export class WindowFunction extends SQLExpression { expr = sql`(${expr})::${type}`; } const { _expr, _deps } = expr; - super(_expr, _deps, { window: op, func, type, name, group, order, frame }); + super(_expr, _deps); + this.window = op; + this.func = func; + this.type = type; + this.name = name; + this.group = group; + this.order = order; + this.frame = frame; } get basis() { @@ -36,11 +53,21 @@ export class WindowFunction extends SQLExpression { return func.label ?? func.toString(); } + /** + * Return an updated window function over a named window definition. + * @param {string} name The window definition name. + * @returns {WindowFunction} A new window function. + */ over(name) { const { window: op, func, type, group, order, frame } = this; return new WindowFunction(op, func, type, name, group, order, frame); } + /** + * Return an updated window function with the given partitioning. + * @param {*} expr The grouping (partition by) criteria for the window function. + * @returns {WindowFunction} A new window function. + */ partitionby(...expr) { const exprs = expr.flat().filter(x => x).map(asColumn); const group = sql( @@ -51,6 +78,11 @@ export class WindowFunction extends SQLExpression { return new WindowFunction(op, func, type, name, group, order, frame); } + /** + * Return an updated window function with the given ordering. + * @param {*} expr The sorting (order by) criteria for the window function. + * @returns {WindowFunction} A new window function. + */ orderby(...expr) { const exprs = expr.flat().filter(x => x).map(asColumn); const order = sql( @@ -61,12 +93,22 @@ export class WindowFunction extends SQLExpression { return new WindowFunction(op, func, type, name, group, order, frame); } + /** + * Return an updated window function with the given rows frame. + * @param {(number|null)[] | import('./expression.js').ParamLike} expr The row-based window frame. + * @returns {WindowFunction} A new window function. + */ rows(expr) { const frame = windowFrame('ROWS', expr); const { window: op, func, type, name, group, order } = this; return new WindowFunction(op, func, type, name, group, order, frame); } + /** + * Return an updated window function with the given range frame. + * @param {(number|null)[] | import('./expression.js').ParamLike} expr The range-based window frame. + * @returns {WindowFunction} A new window function. + */ range(expr) { const frame = windowFrame('RANGE', expr); const { window: op, func, type, name, group, order } = this; diff --git a/packages/vgplot/src/api.js b/packages/vgplot/src/api.js index 58c6aad7..91eaf28e 100644 --- a/packages/vgplot/src/api.js +++ b/packages/vgplot/src/api.js @@ -100,7 +100,6 @@ export { grid, label, padding, - round, xScale, xDomain, xRange, diff --git a/packages/vgplot/src/layout/concat.js b/packages/vgplot/src/layout/concat.js index b7bc4634..2f0db197 100644 --- a/packages/vgplot/src/layout/concat.js +++ b/packages/vgplot/src/layout/concat.js @@ -6,7 +6,7 @@ export function concat({ direction = 'vertical', wrap = false }, children) { div.style.justifyContent = 'flex-start'; div.style.alignItems = 'flex-start'; children.forEach(child => div.appendChild(child)); - div.value = { element: div }; + Object.assign(div, { value: { element: div } }); return div; } diff --git a/packages/vgplot/src/layout/space.js b/packages/vgplot/src/layout/space.js index 29b67602..392735ff 100644 --- a/packages/vgplot/src/layout/space.js +++ b/packages/vgplot/src/layout/space.js @@ -2,9 +2,7 @@ export function space({ dim = 'width', size = 10 }) { const span = document.createElement('span'); span.style.display = 'inline-block'; span.style[dim] = Number.isNaN(+size) ? size : `${size}px`; - const obj = { element: span }; - span.value = obj; - return span; + return Object.assign(span, { value: { element: span } }); } export function vspace(size) { diff --git a/packages/vgplot/src/plot/attributes.js b/packages/vgplot/src/plot/attributes.js index d6aac195..47ed8b8d 100644 --- a/packages/vgplot/src/plot/attributes.js +++ b/packages/vgplot/src/plot/attributes.js @@ -71,7 +71,6 @@ export const inset = attrf('inset'); export const grid = attrf('grid'); export const label = attrf('label'); export const padding = attrf('padding'); -export const round = attrf('round'); // x scale attributes export const xScale = attrf('xScale'); diff --git a/packages/widget/jsconfig.json b/packages/widget/jsconfig.json new file mode 100644 index 00000000..a2e4f75e --- /dev/null +++ b/packages/widget/jsconfig.json @@ -0,0 +1,10 @@ +{ + "files": ["src/index.js"], + "compilerOptions": { + "noEmit": true, + "checkJs": true, + "noImplicitAny": false, + "module": "node16", + "skipLibCheck": true + } +} diff --git a/packages/widget/package.json b/packages/widget/package.json index a256d687..cb4d796d 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -12,12 +12,13 @@ "scripts": { "build": "vite build", "dev": "vite", - "test": "tsc", + "test": "tsc -p jsconfig.json", "lint": "eslint src --ext .js", "prepublishOnly": "npm run test && npm run lint && hatch fmt --check && npm run build && hatch build", "publish": "hatch publish" }, "dependencies": { + "@uwdata/mosaic-spec": "0.7.1", "@uwdata/vgplot": "^0.7.1", "apache-arrow": "^15.0.0", "uuid": "^9.0.1" diff --git a/packages/widget/src/index.js b/packages/widget/src/index.js index dcfc93d2..ad8e440b 100644 --- a/packages/widget/src/index.js +++ b/packages/widget/src/index.js @@ -1,6 +1,5 @@ -// @ts-check - import { coordinator, namedPlots } from '@uwdata/vgplot'; +import { isSelection } from '@uwdata/mosaic-core'; import { parseSpec, astToDOM } from '@uwdata/mosaic-spec'; import * as arrow from 'apache-arrow'; import './style.css'; @@ -10,7 +9,7 @@ import { v4 as uuidv4 } from 'uuid'; * @typedef {Record} Params * * @typedef Model - * @prop {Record} spec the current specification + * @prop {import('@uwdata/mosaic-spec').Spec} spec the current specification * @prop {boolean} temp_indexes whether data cube indexes should be created as temp tables * @prop {Params} params the current params */ @@ -74,11 +73,14 @@ export default { for (const [name, param] of dom.params) { params[name] = { value: param.value, - ...(param.predicate ? { predicate: String(param.predicate()) } : {}), + ...(isSelection(param) ? { predicate: String(param.predicate()) } : {}), }; param.addEventListener('value', (value) => { - params[name] = { value, predicate: String(param.predicate()) }; + params[name] = { + value, + ...(isSelection(param) ? { predicate: String(param.predicate()) } : {}), + }; view.model.set('params', params); view.model.save_changes(); }); @@ -142,7 +144,6 @@ export default { }, }; -/** @param {Record} spec */ function instantiateSpec(spec) { const ast = parseSpec(spec); return astToDOM(ast); diff --git a/packages/widget/tsconfig.json b/packages/widget/tsconfig.json deleted file mode 100644 index 9958b4dc..00000000 --- a/packages/widget/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "module": "nodenext", - "checkJs": true, - "noEmit": true, - "skipLibCheck": true - }, - "include": ["src/**/*"], -} diff --git a/specs/esm/axes.js b/specs/esm/axes.js index b2cee2bd..f268f001 100644 --- a/specs/esm/axes.js +++ b/specs/esm/axes.js @@ -1,7 +1,7 @@ import * as vg from "@uwdata/vgplot"; export default vg.plot( - vg.gridY({strokeDasharray: [0.75, 2], strokeOpacity: 1}), + vg.gridY({strokeDasharray: "0.75 2", strokeOpacity: 1}), vg.axisY({anchor: "left", tickSize: 0, dx: 38, dy: -4, lineAnchor: "bottom"}), vg.axisY({ anchor: "right", diff --git a/specs/esm/seattle-temp.js b/specs/esm/seattle-temp.js index f7bc2e82..3aade9b7 100644 --- a/specs/esm/seattle-temp.js +++ b/specs/esm/seattle-temp.js @@ -29,7 +29,7 @@ export default vg.plot( ), vg.ruleY( [15], - {strokeOpacity: 0.5, strokeDasharray: [5, 5]} + {strokeOpacity: 0.5, strokeDasharray: "5 5"} ), vg.xTickFormat("%b"), vg.yLabel("Temperature Range (°C)"), diff --git a/specs/esm/voronoi.js b/specs/esm/voronoi.js index 39d967c6..579e9cb9 100644 --- a/specs/esm/voronoi.js +++ b/specs/esm/voronoi.js @@ -17,7 +17,6 @@ export default vg.vconcat( stroke: "white", strokeWidth: 1, strokeOpacity: 0.5, - inset: 1, fill: "species", fillOpacity: 0.2 } diff --git a/specs/json/axes.json b/specs/json/axes.json index 8dd85d9d..9078bba6 100644 --- a/specs/json/axes.json +++ b/specs/json/axes.json @@ -6,10 +6,7 @@ "plot": [ { "mark": "gridY", - "strokeDasharray": [ - 0.75, - 2 - ], + "strokeDasharray": "0.75 2", "strokeOpacity": 1 }, { diff --git a/specs/json/seattle-temp.json b/specs/json/seattle-temp.json index 59c7e2e6..6fa3ff73 100644 --- a/specs/json/seattle-temp.json +++ b/specs/json/seattle-temp.json @@ -52,10 +52,7 @@ 15 ], "strokeOpacity": 0.5, - "strokeDasharray": [ - 5, - 5 - ] + "strokeDasharray": "5 5" } ], "xTickFormat": "%b", diff --git a/specs/json/voronoi.json b/specs/json/voronoi.json index 21f6d7e1..bc42e01a 100644 --- a/specs/json/voronoi.json +++ b/specs/json/voronoi.json @@ -27,7 +27,6 @@ "stroke": "white", "strokeWidth": 1, "strokeOpacity": 0.5, - "inset": 1, "fill": "species", "fillOpacity": 0.2 }, diff --git a/specs/ts/aeromagnetic-survey.ts b/specs/ts/aeromagnetic-survey.ts new file mode 100644 index 00000000..9fee1766 --- /dev/null +++ b/specs/ts/aeromagnetic-survey.ts @@ -0,0 +1,67 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Aeromagnetic Survey", + "description": "A raster visualization of the 1955 [Great Britain aeromagnetic survey](https://www.bgs.ac.uk/datasets/gb-aeromagnetic-survey/), which measured the Earth’s magnetic field by plane. Each sample recorded the longitude and latitude alongside the strength of the [IGRF](https://www.ncei.noaa.gov/products/international-geomagnetic-reference-field) in [nanoteslas](https://en.wikipedia.org/wiki/Tesla_(unit)). This example demonstrates both raster interpolation and smoothing (blur) options.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-igfr90-raster)." + }, + "data": { + "ca55": { + "file": "data/ca55-south.parquet" + } + }, + "params": { + "interp": "random-walk", + "blur": 0 + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "menu", + "label": "Interpolation Method", + "options": [ + "none", + "nearest", + "barycentric", + "random-walk" + ], + "as": "$interp" + }, + { + "hspace": "1em" + }, + { + "input": "slider", + "label": "Blur", + "min": 0, + "max": 100, + "as": "$blur" + } + ] + }, + { + "vspace": "1em" + }, + { + "plot": [ + { + "mark": "raster", + "data": { + "from": "ca55" + }, + "x": "LONGITUDE", + "y": "LATITUDE", + "fill": { + "max": "MAG_IGRF90" + }, + "interpolate": "$interp", + "bandwidth": "$blur" + } + ], + "colorScale": "diverging", + "colorDomain": "Fixed" + } + ] +}; diff --git a/specs/ts/airline-travelers.ts b/specs/ts/airline-travelers.ts new file mode 100644 index 00000000..156cbc0a --- /dev/null +++ b/specs/ts/airline-travelers.ts @@ -0,0 +1,70 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Airline Travelers", + "description": "A labeled line chart comparing airport travelers in 2019 and 2020.", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-labeled-line-chart)." + }, + "data": { + "travelers": { + "file": "data/travelers.parquet" + }, + "endpoint": "SELECT * FROM travelers ORDER BY date DESC LIMIT 1\n" + }, + "plot": [ + { + "mark": "ruleY", + "data": [ + 0 + ] + }, + { + "mark": "lineY", + "data": { + "from": "travelers" + }, + "x": "date", + "y": "previous", + "strokeOpacity": 0.35 + }, + { + "mark": "lineY", + "data": { + "from": "travelers" + }, + "x": "date", + "y": "current" + }, + { + "mark": "text", + "data": { + "from": "endpoint" + }, + "x": "date", + "y": "previous", + "text": [ + "2019" + ], + "fillOpacity": 0.5, + "lineAnchor": "bottom", + "dy": -6 + }, + { + "mark": "text", + "data": { + "from": "endpoint" + }, + "x": "date", + "y": "current", + "text": [ + "2020" + ], + "lineAnchor": "top", + "dy": 6 + } + ], + "yGrid": true, + "yLabel": "↑ Travelers per day", + "yTickFormat": "s" +}; diff --git a/specs/ts/athletes.ts b/specs/ts/athletes.ts new file mode 100644 index 00000000..28f5bc33 --- /dev/null +++ b/specs/ts/athletes.ts @@ -0,0 +1,117 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Olympic Athletes", + "description": "An interactive dashboard of athlete statistics. The input menus and searchbox filter the display and are automatically populated by backing data columns.\n" + }, + "data": { + "athletes": { + "file": "data/athletes.parquet" + } + }, + "hconcat": [ + { + "vconcat": [ + { + "hconcat": [ + { + "input": "menu", + "label": "Sport", + "as": "$query", + "from": "athletes", + "column": "sport" + }, + { + "input": "menu", + "label": "Sex", + "as": "$query", + "from": "athletes", + "column": "sex" + }, + { + "input": "search", + "label": "Name", + "as": "$query", + "from": "athletes", + "column": "name", + "type": "contains" + } + ] + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "dot", + "data": { + "from": "athletes", + "filterBy": "$query" + }, + "x": "weight", + "y": "height", + "fill": "sex", + "r": 2, + "opacity": 0.1 + }, + { + "mark": "regressionY", + "data": { + "from": "athletes", + "filterBy": "$query" + }, + "x": "weight", + "y": "height", + "stroke": "sex" + }, + { + "select": "intervalXY", + "as": "$query", + "brush": { + "fillOpacity": 0, + "stroke": "black" + } + } + ], + "xyDomain": "Fixed", + "colorDomain": "Fixed", + "margins": { + "left": 35, + "top": 20, + "right": 1 + }, + "width": 570, + "height": 350 + }, + { + "vspace": 5 + }, + { + "input": "table", + "from": "athletes", + "maxWidth": 570, + "height": 250, + "filterBy": "$query", + "columns": [ + "name", + "nationality", + "sex", + "height", + "weight", + "sport" + ], + "width": { + "name": 180, + "nationality": 100, + "sex": 50, + "height": 50, + "weight": 50, + "sport": 100 + } + } + ] + } + ] +}; diff --git a/specs/ts/axes.ts b/specs/ts/axes.ts new file mode 100644 index 00000000..9341bee4 --- /dev/null +++ b/specs/ts/axes.ts @@ -0,0 +1,57 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Axes & Gridlines", + "description": "Customized axis and gridline marks can be used in addition to standard scale attributes such as `xAxis`, `yGrid`, etc. Just add data!\n" + }, + "plot": [ + { + "mark": "gridY", + "strokeDasharray": "0.75 2", + "strokeOpacity": 1 + }, + { + "mark": "axisY", + "anchor": "left", + "tickSize": 0, + "dx": 38, + "dy": -4, + "lineAnchor": "bottom" + }, + { + "mark": "axisY", + "anchor": "right", + "tickSize": 0, + "tickPadding": 5, + "label": "y-axis", + "labelAnchor": "center" + }, + { + "mark": "axisX", + "label": "x-axis", + "labelAnchor": "center" + }, + { + "mark": "gridX" + }, + { + "mark": "ruleY", + "data": [ + 0 + ] + } + ], + "xDomain": [ + 0, + 100 + ], + "yDomain": [ + 0, + 100 + ], + "xInsetLeft": 36, + "marginLeft": 0, + "marginRight": 35, + "width": 680 +}; diff --git a/specs/ts/bias.ts b/specs/ts/bias.ts new file mode 100644 index 00000000..c611e47a --- /dev/null +++ b/specs/ts/bias.ts @@ -0,0 +1,43 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Bias Parameter", + "description": "Dynamically adjust queried values by adding a Param value. The SQL expression is re-computed in the database upon updates.\n" + }, + "data": { + "walk": { + "file": "data/random-walk.parquet" + } + }, + "params": { + "point": 0 + }, + "vconcat": [ + { + "input": "slider", + "label": "Bias", + "as": "$point", + "min": 1, + "max": 1000, + "step": 0.1 + }, + { + "plot": [ + { + "mark": "areaY", + "data": { + "from": "walk" + }, + "x": "t", + "y": { + "sql": "v + $point" + }, + "fill": "steelblue" + } + ], + "width": 680, + "height": 200 + } + ] +}; diff --git a/specs/ts/contours.ts b/specs/ts/contours.ts new file mode 100644 index 00000000..1282471d --- /dev/null +++ b/specs/ts/contours.ts @@ -0,0 +1,84 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Contour Plot", + "description": "Here `heatmap` and `contour` marks visualize the density of data points in a scatter plot of penguin measurments. Setting the `fill` color to `\"species\"` subdivides the data into three sets of densities.\n" + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "params": { + "bandwidth": 40, + "thresholds": 10 + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "slider", + "label": "Bandwidth (σ)", + "as": "$bandwidth", + "min": 1, + "max": 100 + }, + { + "input": "slider", + "label": "Thresholds", + "as": "$thresholds", + "min": 2, + "max": 20 + } + ] + }, + { + "plot": [ + { + "mark": "heatmap", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "species", + "bandwidth": "$bandwidth" + }, + { + "mark": "contour", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "stroke": "species", + "bandwidth": "$bandwidth", + "thresholds": "$thresholds" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "currentColor", + "r": 1 + } + ], + "xAxis": "bottom", + "xLabelAnchor": "center", + "yAxis": "right", + "yLabelAnchor": "center", + "margins": { + "top": 5, + "bottom": 30, + "left": 5, + "right": 50 + }, + "width": 700, + "height": 480 + } + ] +}; diff --git a/specs/ts/crossfilter.ts b/specs/ts/crossfilter.ts new file mode 100644 index 00000000..62c670f5 --- /dev/null +++ b/specs/ts/crossfilter.ts @@ -0,0 +1,72 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "params": { + "brush": { + "select": "crossfilter" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": { + "bin": "delay" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "xLabel": "Arrival Delay (min)", + "xLabelAnchor": "center", + "yTickFormat": "s", + "height": 200 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": { + "bin": "time" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "xLabel": "Departure Time (hour)", + "xLabelAnchor": "center", + "yTickFormat": "s", + "height": 200 + } + ] +}; diff --git a/specs/ts/density1d.ts b/specs/ts/density1d.ts new file mode 100644 index 00000000..c0630403 --- /dev/null +++ b/specs/ts/density1d.ts @@ -0,0 +1,78 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Density 1D", + "description": "Density plots (`densityY` mark) for over 200,000 flights, created using kernel density estimation. Binning is performned in-database, subsequent smoothing in-browser. The distance density uses a log-scaled domain. To change the amount of smoothing, use the slider to set the kernel bandwidth.\n" + }, + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "params": { + "brush": { + "select": "crossfilter" + }, + "bandwidth": 20 + }, + "vconcat": [ + { + "input": "slider", + "label": "Bandwidth (σ)", + "as": "$bandwidth", + "min": 0.1, + "max": 100, + "step": 0.1 + }, + { + "plot": [ + { + "mark": "densityY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": "delay", + "fill": "#888", + "fillOpacity": 0.5, + "bandwidth": "$bandwidth" + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "yAxis": null, + "xDomain": "Fixed", + "width": 600, + "marginLeft": 10, + "height": 200 + }, + { + "plot": [ + { + "mark": "densityY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": "distance", + "fill": "#888", + "fillOpacity": 0.5, + "bandwidth": "$bandwidth" + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "yAxis": null, + "xScale": "log", + "xDomain": "Fixed", + "width": 600, + "marginLeft": 10, + "height": 200 + } + ] +}; diff --git a/specs/ts/density2d.ts b/specs/ts/density2d.ts new file mode 100644 index 00000000..e3e5cea0 --- /dev/null +++ b/specs/ts/density2d.ts @@ -0,0 +1,81 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Density 2D", + "description": "A 2D `density` plot in which circle size indicates the point density. The data is divided by fill color into three sets of densities. To change the amount of smoothing, use the slider to set the kernel bandwidth.\n" + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "params": { + "bandwidth": 20, + "bins": 20 + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "slider", + "label": "Bandwidth (σ)", + "as": "$bandwidth", + "min": 1, + "max": 100 + }, + { + "input": "slider", + "label": "Bins", + "as": "$bins", + "min": 10, + "max": 60 + } + ] + }, + { + "plot": [ + { + "mark": "density", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "r": "density", + "fill": "species", + "fillOpacity": 0.5, + "width": "$bins", + "height": "$bins", + "bandwidth": "$bandwidth" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "currentColor", + "r": 1 + } + ], + "rRange": [ + 0, + 16 + ], + "xAxis": "bottom", + "xLabelAnchor": "center", + "yAxis": "right", + "yLabelAnchor": "center", + "margins": { + "top": 5, + "bottom": 30, + "left": 5, + "right": 50 + }, + "width": 700, + "height": 480 + } + ] +}; diff --git a/specs/ts/driving-shifts.ts b/specs/ts/driving-shifts.ts new file mode 100644 index 00000000..1292eea3 --- /dev/null +++ b/specs/ts/driving-shifts.ts @@ -0,0 +1,46 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Driving Shifts into Reverse", + "description": "A connected scatter plot of miles driven vs. gas prices.", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-connected-scatterplot), which in turn adapts Hannah Fairfield's [New York Times article](http://www.nytimes.com/imagepages/2010/05/02/business/02metrics.html).\n" + }, + "data": { + "driving": { + "file": "data/driving.parquet" + } + }, + "plot": [ + { + "mark": "line", + "data": { + "from": "driving" + }, + "x": "miles", + "y": "gas", + "curve": "catmull-rom", + "marker": true + }, + { + "mark": "text", + "data": { + "from": "driving" + }, + "x": "miles", + "y": "gas", + "text": { + "sql": "year::VARCHAR" + }, + "dy": -6, + "lineAnchor": "bottom", + "filter": { + "sql": "year % 5 = 0" + } + } + ], + "inset": 10, + "grid": true, + "xLabel": "Miles driven (per person-year)", + "yLabel": "Cost of gasoline ($ per gallon)" +}; diff --git a/specs/ts/earthquakes-feed.ts b/specs/ts/earthquakes-feed.ts new file mode 100644 index 00000000..ccfbe091 --- /dev/null +++ b/specs/ts/earthquakes-feed.ts @@ -0,0 +1,51 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Earthquakes Feed", + "description": "Earthquake data from the USGS live feed. To show land masses, this example loads and parses TopoJSON data in the database. Requires the DuckDB `spatial` extension.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-live-earthquake-map)." + }, + "data": { + "feed": { + "type": "spatial", + "file": "data/usgs-feed.geojson" + }, + "world": { + "type": "spatial", + "file": "data/countries-110m.json", + "layer": "land" + } + }, + "plot": [ + { + "mark": "geo", + "data": { + "from": "world" + }, + "fill": "currentColor", + "fillOpacity": 0.2 + }, + { + "mark": "sphere", + "strokeWidth": 0.5 + }, + { + "mark": "geo", + "data": { + "from": "feed" + }, + "r": { + "sql": "POW(10, mag)" + }, + "stroke": "red", + "fill": "red", + "fillOpacity": 0.2, + "title": "title", + "href": "url", + "target": "_blank" + } + ], + "margin": 2, + "projectionType": "equirectangular" +}; diff --git a/specs/ts/earthquakes-globe.ts b/specs/ts/earthquakes-globe.ts new file mode 100644 index 00000000..ab33acbc --- /dev/null +++ b/specs/ts/earthquakes-globe.ts @@ -0,0 +1,85 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Earthquakes Globe", + "description": "A rotatable globe of earthquake activity. To show land masses, this example loads and parses TopoJSON data in the database. Requires the DuckDB `spatial` extension.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-earthquake-globe)." + }, + "data": { + "earthquakes": { + "file": "data/earthquakes.parquet" + }, + "land": { + "type": "spatial", + "file": "data/countries-110m.json", + "layer": "land" + } + }, + "params": { + "longitude": -180, + "latitude": -30, + "rotate": [ + "$longitude", + "$latitude" + ] + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "slider", + "label": "Longitude", + "as": "$longitude", + "min": -180, + "max": 180, + "step": 1 + }, + { + "input": "slider", + "label": "Latitude", + "as": "$latitude", + "min": -90, + "max": 90, + "step": 1 + } + ] + }, + { + "plot": [ + { + "mark": "geo", + "data": { + "from": "land" + }, + "geometry": { + "geojson": "geom" + }, + "fill": "currentColor", + "fillOpacity": 0.2 + }, + { + "mark": "sphere" + }, + { + "mark": "dot", + "data": { + "from": "earthquakes" + }, + "x": "longitude", + "y": "latitude", + "r": { + "sql": "POW(10, magnitude)" + }, + "stroke": "red", + "fill": "red", + "fillOpacity": 0.2 + } + ], + "margin": 10, + "style": "overflow: visible;", + "projectionType": "orthographic", + "projectionRotate": "$rotate" + } + ] +}; diff --git a/specs/ts/flights-10m.ts b/specs/ts/flights-10m.ts new file mode 100644 index 00000000..4c421db5 --- /dev/null +++ b/specs/ts/flights-10m.ts @@ -0,0 +1,99 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Cross-Filter Flights (10M)", + "description": "Histograms showing arrival delay, departure time, and distance flown for 10 million flights.\nOnce loaded, automatically-generated indexes enable efficient cross-filtered selections.\n\n_You may need to wait a few seconds for the dataset to load._\n" + }, + "data": { + "flights10m": "SELECT GREATEST(-60, LEAST(ARR_DELAY, 180))::DOUBLE AS delay, DISTANCE AS distance, DEP_TIME AS time FROM 'https://uwdata.github.io/mosaic-datasets/data/flights-10m.parquet'" + }, + "params": { + "brush": { + "select": "crossfilter" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights10m", + "filterBy": "$brush" + }, + "x": { + "bin": "delay" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "marginLeft": 75, + "width": 600, + "height": 200 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights10m", + "filterBy": "$brush" + }, + "x": { + "bin": "time" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "marginLeft": 75, + "width": 600, + "height": 200 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights10m", + "filterBy": "$brush" + }, + "x": { + "bin": "distance" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "marginLeft": 75, + "width": 600, + "height": 200 + } + ] +}; diff --git a/specs/ts/flights-200k.ts b/specs/ts/flights-200k.ts new file mode 100644 index 00000000..1f5db65f --- /dev/null +++ b/specs/ts/flights-200k.ts @@ -0,0 +1,101 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Cross-Filter Flights (200k)", + "description": "Histograms showing arrival delay, departure time, and distance flown for over 200,000 flights. Select a histogram region to cross-filter the charts. Each plot uses an `intervalX` interactor to populate a shared Selection with `crossfilter` resolution.\n" + }, + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "params": { + "brush": { + "select": "crossfilter" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": { + "bin": "delay" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "yTickFormat": "s", + "width": 600, + "height": 200 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": { + "bin": "time" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "yTickFormat": "s", + "width": 600, + "height": 200 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights", + "filterBy": "$brush" + }, + "x": { + "bin": "distance" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "yTickFormat": "s", + "width": 600, + "height": 200 + } + ] +}; diff --git a/specs/ts/flights-density.ts b/specs/ts/flights-density.ts new file mode 100644 index 00000000..74c346ba --- /dev/null +++ b/specs/ts/flights-density.ts @@ -0,0 +1,75 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Flights Density", + "description": "Density `heatmap` and `contour` lines for 200,000+ flights by departure hour and arrival delay. The sliders adjust the smoothing (bandwidth) and number of contour thresholds.\n" + }, + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "params": { + "bandwidth": 7, + "thresholds": 10 + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "slider", + "label": "Bandwidth (σ)", + "as": "$bandwidth", + "min": 1, + "max": 100 + }, + { + "input": "slider", + "label": "Thresholds", + "as": "$thresholds", + "min": 2, + "max": 20 + } + ] + }, + { + "plot": [ + { + "mark": "heatmap", + "data": { + "from": "flights" + }, + "x": "time", + "y": "delay", + "fill": "density", + "bandwidth": "$bandwidth" + }, + { + "mark": "contour", + "data": { + "from": "flights" + }, + "x": "time", + "y": "delay", + "stroke": "white", + "strokeOpacity": 0.5, + "bandwidth": "$bandwidth", + "thresholds": "$thresholds" + } + ], + "colorScale": "symlog", + "colorScheme": "ylgnbu", + "xAxis": "top", + "xLabelAnchor": "center", + "xZero": true, + "yAxis": "right", + "yLabelAnchor": "center", + "marginTop": 30, + "marginLeft": 5, + "marginRight": 40, + "width": 700, + "height": 500 + } + ] +}; diff --git a/specs/ts/flights-hexbin.ts b/specs/ts/flights-hexbin.ts new file mode 100644 index 00000000..d64db241 --- /dev/null +++ b/specs/ts/flights-hexbin.ts @@ -0,0 +1,161 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Flights Hexbin", + "description": "Hexagonal bins show the density of over 200,000 flights by departure time and arrival delay. Select regions in the marginal histograms to filter the density display.\n" + }, + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "params": { + "scale": { + "value": "log" + }, + "query": { + "select": "intersect" + } + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "menu", + "label": "Color Scale", + "as": "$scale", + "options": [ + "log", + "linear", + "sqrt" + ] + }, + { + "hspace": 10 + }, + { + "legend": "color", + "for": "hexbins" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "flights" + }, + "x": { + "bin": "time" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$query" + } + ], + "margins": { + "left": 5, + "right": 5, + "top": 30, + "bottom": 0 + }, + "xDomain": "Fixed", + "xAxis": "top", + "yAxis": null, + "xLabelAnchor": "center", + "width": 605, + "height": 70 + }, + { + "hspace": 80 + } + ] + }, + { + "hconcat": [ + { + "name": "hexbins", + "plot": [ + { + "mark": "hexbin", + "data": { + "from": "flights", + "filterBy": "$query" + }, + "x": "time", + "y": "delay", + "fill": { + "count": null + }, + "binWidth": 10 + }, + { + "mark": "hexgrid", + "binWidth": 10 + } + ], + "colorScheme": "ylgnbu", + "colorScale": "$scale", + "margins": { + "left": 5, + "right": 0, + "top": 0, + "bottom": 5 + }, + "xAxis": null, + "yAxis": null, + "xyDomain": "Fixed", + "width": 600, + "height": 455 + }, + { + "plot": [ + { + "mark": "rectX", + "data": { + "from": "flights" + }, + "x": { + "count": null + }, + "y": { + "bin": "delay" + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalY", + "as": "$query" + } + ], + "margins": { + "left": 0, + "right": 50, + "top": 4, + "bottom": 5 + }, + "yDomain": [ + -60, + 180 + ], + "xAxis": null, + "yAxis": "right", + "yLabelAnchor": "center", + "width": 80, + "height": 455 + } + ] + } + ] +}; diff --git a/specs/ts/gaia.ts b/specs/ts/gaia.ts new file mode 100644 index 00000000..792c97aa --- /dev/null +++ b/specs/ts/gaia.ts @@ -0,0 +1,149 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Gaia Star Catalog", + "description": "A 5M row sample of the 1.8B element Gaia star catalog.\nA `raster` sky map reveals our Milky Way galaxy. Select high parallax stars in the histogram to reveal a\n[Hertzsprung-Russel diagram](https://en.wikipedia.org/wiki/Hertzsprung%E2%80%93Russell_diagram)\nin the plot of stellar color vs. magnitude on the right.\n\n_You may need to wait a few seconds for the dataset to load._\n" + }, + "data": { + "gaia": "-- compute u and v with natural earth projection\nWITH prep AS (\n SELECT\n radians((-l + 540) % 360 - 180) AS lambda,\n radians(b) AS phi,\n asin(sqrt(3)/2 * sin(phi)) AS t,\n t^2 AS t2,\n t2^3 AS t6,\n *\n FROM 'https://uwdata.github.io/mosaic-datasets/data/gaia-5m.parquet'\n)\nSELECT\n (1.340264 * lambda * cos(t)) / (sqrt(3)/2 * (1.340264 + (-0.081106 * 3 * t2) + (t6 * (0.000893 * 7 + 0.003796 * 9 * t2)))) AS u,\n t * (1.340264 + (-0.081106 * t2) + (t6 * (0.000893 + 0.003796 * t2))) AS v,\n * EXCLUDE('t', 't2', 't6')\nFROM prep\nWHERE parallax BETWEEN -5 AND 20\n" + }, + "params": { + "brush": { + "select": "crossfilter" + }, + "bandwidth": 0, + "pixelSize": 2, + "scaleType": "sqrt" + }, + "hconcat": [ + { + "vconcat": [ + { + "plot": [ + { + "mark": "raster", + "data": { + "from": "gaia", + "filterBy": "$brush" + }, + "x": "u", + "y": "v", + "fill": "density", + "bandwidth": "$bandwidth", + "pixelSize": "$pixelSize" + }, + { + "select": "intervalXY", + "pixelSize": 2, + "as": "$brush" + } + ], + "xyDomain": "Fixed", + "colorScale": "$scaleType", + "colorScheme": "viridis", + "width": 440, + "height": 250, + "marginLeft": 25, + "marginTop": 20, + "marginRight": 1 + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "gaia", + "filterBy": "$brush" + }, + "x": { + "bin": "phot_g_mean_mag" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "yScale": "$scaleType", + "yGrid": true, + "width": 220, + "height": 120, + "marginLeft": 65 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "gaia", + "filterBy": "$brush" + }, + "x": { + "bin": "parallax" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "xDomain": "Fixed", + "yScale": "$scaleType", + "yGrid": true, + "width": 220, + "height": 120, + "marginLeft": 65 + } + ] + } + ] + }, + { + "hspace": 10 + }, + { + "plot": [ + { + "mark": "raster", + "data": { + "from": "gaia", + "filterBy": "$brush" + }, + "x": "bp_rp", + "y": "phot_g_mean_mag", + "fill": "density", + "bandwidth": "$bandwidth", + "pixelSize": "$pixelSize" + }, + { + "select": "intervalXY", + "pixelSize": 2, + "as": "$brush" + } + ], + "xyDomain": "Fixed", + "colorScale": "$scaleType", + "colorScheme": "viridis", + "yReverse": true, + "width": 230, + "height": 370, + "marginLeft": 25, + "marginTop": 20, + "marginRight": 1 + } + ] +}; diff --git a/specs/ts/legends.ts b/specs/ts/legends.ts new file mode 100644 index 00000000..c74e4f3b --- /dev/null +++ b/specs/ts/legends.ts @@ -0,0 +1,329 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Legends", + "description": "Tests for different legend types and configurations. We test both legends defined within plots (with a zero-size frame) and external legends that reference a named plot.\n" + }, + "params": { + "toggle": { + "select": "single" + }, + "interval": { + "select": "intersect" + }, + "domain": [ + "foo", + "bar", + "baz", + "bop", + "doh" + ] + }, + "plotDefaults": { + "margin": 0, + "width": 0, + "height": 20 + }, + "vconcat": [ + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Color Swatch", + "as": "$toggle" + } + ], + "name": "color-categorical", + "colorScale": "categorical", + "colorDomain": "$domain" + }, + { + "hspace": 35 + }, + { + "legend": "color", + "for": "color-categorical", + "label": "Color Swatch (External)", + "as": "$toggle" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "symbol", + "label": "Symbol Swatch", + "as": "$toggle" + } + ], + "name": "symbol-categorical", + "symbolDomain": "$domain" + }, + { + "hspace": 35 + }, + { + "legend": "symbol", + "for": "symbol-categorical", + "label": "Symbol Swatch (External)", + "as": "$toggle" + } + ] + }, + { + "vspace": "1em" + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "opacity", + "label": "Opacity Ramp", + "as": "$interval" + } + ], + "name": "opacity-linear", + "opacityDomain": [ + 0, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "opacity", + "for": "opacity-linear", + "label": "Opacity Ramp (External)", + "as": "$interval" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "opacity" + } + ], + "name": "opacity-linear-no-label", + "opacityDomain": [ + 0, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "opacity", + "for": "opacity-linear-no-label" + } + ] + }, + { + "vspace": "1em" + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Linear Color Ramp", + "as": "$interval" + } + ], + "name": "color-linear", + "colorDomain": [ + 0, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-linear", + "label": "Linear Color Ramp (External)", + "as": "$interval" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color" + } + ], + "name": "color-linear-no-label", + "colorDomain": [ + 0, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-linear-no-label" + } + ] + }, + { + "vspace": "1em" + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Logarithmic Color Ramp", + "as": "$interval" + } + ], + "name": "color-log", + "colorScale": "log", + "colorDomain": [ + 1, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-log", + "label": "Logarithmic Color Ramp (External)", + "as": "$interval" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Diverging Color Ramp", + "as": "$interval" + } + ], + "name": "color-diverging", + "colorScale": "diverging", + "colorDomain": [ + -100, + 100 + ], + "colorConstant": 20 + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-diverging", + "label": "Diverging Color Ramp (External)", + "as": "$interval" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Diverging Symlog Color Ramp", + "as": "$interval" + } + ], + "name": "color-diverging-symlog", + "colorScale": "diverging-symlog", + "colorDomain": [ + -100, + 100 + ], + "colorConstant": 20 + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-diverging-symlog", + "label": "Diverging Symlog Color Ramp (External)", + "as": "$interval" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Quantize Color Ramp" + } + ], + "name": "color-quantize", + "colorScale": "quantize", + "colorDomain": [ + 0, + 100 + ] + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-quantize", + "label": "Quantize Color Ramp (External)" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "legend": "color", + "label": "Threshold Color Ramp" + } + ], + "name": "color-threshold", + "colorScale": "threshold", + "colorDomain": [ + 0, + 10, + 20, + 40, + 80 + ] + }, + { + "hspace": 30 + }, + { + "legend": "color", + "for": "color-threshold", + "label": "Threshold Color Ramp (External)" + } + ] + } + ] +}; diff --git a/specs/ts/line-density.ts b/specs/ts/line-density.ts new file mode 100644 index 00000000..6ad72402 --- /dev/null +++ b/specs/ts/line-density.ts @@ -0,0 +1,115 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Line Density", + "description": "The `denseLine` mark shows the densities of line series, here for a collection of stock prices. The top plot normalizes by arc length to remove the vertical artifacts visible in the unnormalized plot below. Select a region in the lower plot to zoom the upper plot. The bandwidth slider smooths the data, while the pixel size menu adjusts the raster resolution.\n" + }, + "data": { + "stocks_after_2006": { + "file": "data/stocks_after_2006.parquet", + "select": [ + "Symbol", + "Close", + "Date" + ], + "where": "Close < 100" + } + }, + "params": { + "brush": { + "select": "intersect" + }, + "bandwidth": 0, + "pixelSize": 2, + "schemeColor": "pubugn", + "scaleColor": "sqrt" + }, + "vconcat": [ + { + "hconcat": [ + { + "input": "slider", + "label": "Bandwidth (σ)", + "as": "$bandwidth", + "min": 0, + "max": 10, + "step": 0.1 + }, + { + "input": "menu", + "label": "Pixel Size", + "as": "$pixelSize", + "options": [ + 0.5, + 1, + 2 + ] + } + ] + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "denseLine", + "data": { + "from": "stocks_after_2006", + "filterBy": "$brush" + }, + "x": "Date", + "y": "Close", + "z": "Symbol", + "fill": "density", + "bandwidth": "$bandwidth", + "pixelSize": "$pixelSize" + } + ], + "colorScheme": "$schemeColor", + "colorScale": "$scaleColor", + "yLabel": "Close (Normalized) ↑", + "yNice": true, + "margins": { + "left": 30, + "top": 20, + "right": 0 + }, + "width": 680, + "height": 240 + }, + { + "plot": [ + { + "mark": "denseLine", + "data": { + "from": "stocks_after_2006" + }, + "x": "Date", + "y": "Close", + "z": "Symbol", + "fill": "density", + "normalize": false, + "bandwidth": "$bandwidth", + "pixelSize": "$pixelSize" + }, + { + "select": "intervalXY", + "as": "$brush" + } + ], + "colorScheme": "$schemeColor", + "colorScale": "$scaleColor", + "yLabel": "Close (Unnormalized) ↑", + "yNice": true, + "margins": { + "left": 30, + "top": 20, + "right": 0 + }, + "width": 680, + "height": 240 + } + ] +}; diff --git a/specs/ts/line.ts b/specs/ts/line.ts new file mode 100644 index 00000000..ad5a5a09 --- /dev/null +++ b/specs/ts/line.ts @@ -0,0 +1,22 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "data": { + "aapl": { + "file": "data/stocks.parquet", + "where": "Symbol = 'AAPL'" + } + }, + "plot": [ + { + "mark": "lineY", + "data": { + "from": "aapl" + }, + "x": "Date", + "y": "Close" + } + ], + "width": 680, + "height": 200 +}; diff --git a/specs/ts/linear-regression.ts b/specs/ts/linear-regression.ts new file mode 100644 index 00000000..f908a01b --- /dev/null +++ b/specs/ts/linear-regression.ts @@ -0,0 +1,46 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Linear Regression", + "description": "A linear regression plot predicting athletes' heights based on their weights. Regression computation is performed in the database. The area around a regression line shows a 95% confidence interval. Select a region to view regression results for a data subset.\n" + }, + "data": { + "athletes": { + "file": "data/athletes.parquet" + } + }, + "plot": [ + { + "mark": "dot", + "data": { + "from": "athletes" + }, + "x": "weight", + "y": "height", + "fill": "sex", + "r": 2, + "opacity": 0.05 + }, + { + "mark": "regressionY", + "data": { + "from": "athletes", + "filterBy": "$query" + }, + "x": "weight", + "y": "height", + "stroke": "sex" + }, + { + "select": "intervalXY", + "as": "$query", + "brush": { + "fillOpacity": 0, + "stroke": "currentColor" + } + } + ], + "xyDomain": "Fixed", + "colorDomain": "Fixed" +}; diff --git a/specs/ts/mark-types.ts b/specs/ts/mark-types.ts new file mode 100644 index 00000000..0e0bb61c --- /dev/null +++ b/specs/ts/mark-types.ts @@ -0,0 +1,235 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Mark Types", + "description": "A subset of supported mark types.\n\n- Row 1: `barY`, `lineY`, `text`, `tickY`, `areaY`\n- Row 2: `regressionY`, `hexbin`, `contour`, `heatmap`, `denseLine`\n" + }, + "data": { + "md": [ + { + "i": 0, + "u": "A", + "v": 2 + }, + { + "i": 1, + "u": "B", + "v": 8 + }, + { + "i": 2, + "u": "C", + "v": 3 + }, + { + "i": 3, + "u": "D", + "v": 7 + }, + { + "i": 4, + "u": "E", + "v": 5 + }, + { + "i": 5, + "u": "F", + "v": 4 + }, + { + "i": 6, + "u": "G", + "v": 6 + }, + { + "i": 7, + "u": "H", + "v": 1 + } + ] + }, + "plotDefaults": { + "xAxis": null, + "yAxis": null, + "margins": { + "left": 5, + "top": 5, + "right": 5, + "bottom": 5 + }, + "width": 160, + "height": 100, + "yDomain": [ + 0, + 9 + ] + }, + "vconcat": [ + { + "hconcat": [ + { + "mark": "barY", + "data": { + "from": "md" + }, + "x": "u", + "y": "v", + "fill": "steelblue" + }, + { + "mark": "lineY", + "data": { + "from": "md" + }, + "x": "u", + "y": "v", + "stroke": "steelblue", + "curve": "monotone-x", + "marker": "circle" + }, + { + "mark": "text", + "data": { + "from": "md" + }, + "x": "u", + "y": "v", + "text": "u", + "fill": "steelblue" + }, + { + "mark": "tickY", + "data": { + "from": "md" + }, + "x": "u", + "y": "v", + "stroke": "steelblue" + }, + { + "mark": "areaY", + "data": { + "from": "md" + }, + "x": "u", + "y": "v", + "fill": "steelblue" + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "dot", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "fill": "currentColor", + "r": 1.5 + }, + { + "mark": "regressionY", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "stroke": "steelblue" + } + ], + "xDomain": [ + -0.5, + 7.5 + ] + }, + { + "plot": [ + { + "mark": "hexgrid", + "stroke": "#aaa", + "strokeOpacity": 0.5 + }, + { + "mark": "hexbin", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "fill": { + "count": null + } + } + ], + "colorScheme": "blues", + "xDomain": [ + -1, + 8 + ] + }, + { + "plot": [ + { + "mark": "contour", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "stroke": "steelblue", + "bandwidth": 15 + } + ], + "xDomain": [ + -1, + 8 + ] + }, + { + "plot": [ + { + "mark": "heatmap", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "fill": "density", + "bandwidth": 15 + } + ], + "colorScheme": "blues", + "xDomain": [ + -1, + 8 + ] + }, + { + "plot": [ + { + "mark": "denseLine", + "data": { + "from": "md" + }, + "x": "i", + "y": "v", + "fill": "density", + "bandwidth": 2, + "pixelSize": 1 + } + ], + "colorScheme": "blues", + "xDomain": [ + -1, + 8 + ] + } + ] + } + ] +}; diff --git a/specs/ts/moving-average.ts b/specs/ts/moving-average.ts new file mode 100644 index 00000000..2ecb6258 --- /dev/null +++ b/specs/ts/moving-average.ts @@ -0,0 +1,92 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Moving Average", + "description": "This plot shows daily reported COVID-19 cases from March 3 (day 1) to May 5, 2020 (day 64) in Berlin, Germany, as reported by the [Robert Koch Institute](https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/nCoV.html). We can smooth the raw counts using a moving average over various choices of window query frames.\n", + "credit": "Adapted from the [Arquero window query tutorial](https://observablehq.com/@uwdata/working-with-window-queries)." + }, + "data": { + "cases": { + "file": "data/berlin-covid.parquet" + } + }, + "params": { + "frame": [ + -6, + 0 + ] + }, + "vconcat": [ + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "cases" + }, + "x1": "day", + "x2": { + "sql": "day + 1" + }, + "inset": 1, + "y": "cases", + "fill": "steelblue" + }, + { + "mark": "lineY", + "data": { + "from": "cases" + }, + "x": { + "sql": "day + 0.5" + }, + "y": { + "avg": "cases", + "orderby": "day", + "rows": "$frame" + }, + "curve": "monotone-x", + "stroke": "currentColor" + } + ], + "width": 680, + "height": 300 + }, + { + "input": "menu", + "label": "Window Frame", + "as": "$frame", + "options": [ + { + "label": "7-day moving average, with prior 6 days: [-6, 0]", + "value": [ + -6, + 0 + ] + }, + { + "label": "7-day moving average, centered at current day: [-3, 3]", + "value": [ + -3, + 3 + ] + }, + { + "label": "Moving average, with all prior days [-∞, 0]", + "value": [ + null, + 0 + ] + }, + { + "label": "Global average [-∞, +∞]", + "value": [ + null, + null + ] + } + ] + } + ] +}; diff --git a/specs/ts/normalize.ts b/specs/ts/normalize.ts new file mode 100644 index 00000000..254b1861 --- /dev/null +++ b/specs/ts/normalize.ts @@ -0,0 +1,74 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Normalized Stock Prices", + "description": "What is the return on investment for different days? Hover over the chart to normalize the stock prices for the percentage return on a given day. A `nearestX` interactor selects the nearest date, and parameterized expressions reactively update in response.\n" + }, + "data": { + "stocks": { + "file": "data/stocks.parquet" + }, + "labels": "SELECT MAX(Date) as Date, ARGMAX(Close, Date) AS Close, Symbol FROM stocks GROUP BY Symbol" + }, + "params": { + "point": { + "date": "2013-05-13" + } + }, + "plot": [ + { + "mark": "ruleX", + "x": "$point" + }, + { + "mark": "textX", + "x": "$point", + "text": "$point", + "frameAnchor": "top", + "lineAnchor": "bottom", + "dy": -7 + }, + { + "mark": "text", + "data": { + "from": "labels" + }, + "x": "Date", + "y": { + "sql": "Close / (SELECT MAX(Close) FROM stocks WHERE Symbol = source.Symbol AND Date = $point)" + }, + "dx": 2, + "text": "Symbol", + "fill": "Symbol", + "textAnchor": "start" + }, + { + "mark": "lineY", + "data": { + "from": "stocks" + }, + "x": "Date", + "y": { + "sql": "Close / (SELECT MAX(Close) FROM stocks WHERE Symbol = source.Symbol AND Date = $point)" + }, + "stroke": "Symbol" + }, + { + "select": "nearestX", + "as": "$point" + } + ], + "yScale": "log", + "yDomain": [ + 0.2, + 6 + ], + "yGrid": true, + "xLabel": null, + "yLabel": null, + "yTickFormat": "%", + "width": 680, + "height": 400, + "marginRight": 35 +}; diff --git a/specs/ts/nyc-taxi-rides.ts b/specs/ts/nyc-taxi-rides.ts new file mode 100644 index 00000000..e971459d --- /dev/null +++ b/specs/ts/nyc-taxi-rides.ts @@ -0,0 +1,159 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "NYC Taxi Rides", + "description": "Pickup and dropoff points for 1M NYC taxi rides on Jan 1-3, 2010.\nThis example projects lon/lat coordinates in the database upon load.\nSelect a region in one plot to filter the other.\nWhat spatial patterns can you find?\nRequires the DuckDB `spatial` extension.\n\n_You may need to wait a few seconds for the dataset to load._\n" + }, + "config": { + "extensions": "spatial" + }, + "data": { + "rides": { + "file": "https://uwdata.github.io/mosaic-datasets/data/nyc-rides-2010.parquet", + "select": [ + "pickup_datetime::TIMESTAMP AS datetime", + "ST_Transform(ST_Point(pickup_latitude, pickup_longitude), 'EPSG:4326', 'ESRI:102718') AS pick", + "ST_Transform(ST_Point(dropoff_latitude, dropoff_longitude), 'EPSG:4326', 'ESRI:102718') AS drop" + ] + }, + "trips": "SELECT\n (HOUR(datetime) + MINUTE(datetime)/60) AS time,\n ST_X(pick) AS px, ST_Y(pick) AS py,\n ST_X(drop) AS dx, ST_Y(drop) AS dy\nFROM rides\n" + }, + "params": { + "filter": { + "select": "crossfilter" + } + }, + "vconcat": [ + { + "hconcat": [ + { + "plot": [ + { + "mark": "raster", + "data": { + "from": "trips", + "filterBy": "$filter" + }, + "x": "px", + "y": "py", + "bandwidth": 0 + }, + { + "select": "intervalXY", + "as": "$filter" + }, + { + "mark": "text", + "data": [ + { + "label": "Taxi Pickups" + } + ], + "dx": 10, + "dy": 10, + "text": "label", + "fill": "black", + "fontSize": "1.2em", + "frameAnchor": "top-left" + } + ], + "width": 335, + "height": 550, + "margin": 0, + "xAxis": null, + "yAxis": null, + "xDomain": [ + 975000, + 1005000 + ], + "yDomain": [ + 190000, + 240000 + ], + "colorScale": "symlog", + "colorScheme": "blues" + }, + { + "hspace": 10 + }, + { + "plot": [ + { + "mark": "raster", + "data": { + "from": "trips", + "filterBy": "$filter" + }, + "x": "dx", + "y": "dy", + "bandwidth": 0 + }, + { + "select": "intervalXY", + "as": "$filter" + }, + { + "mark": "text", + "data": [ + { + "label": "Taxi Dropoffs" + } + ], + "dx": 10, + "dy": 10, + "text": "label", + "fill": "black", + "fontSize": "1.2em", + "frameAnchor": "top-left" + } + ], + "width": 335, + "height": 550, + "margin": 0, + "xAxis": null, + "yAxis": null, + "xDomain": [ + 975000, + 1005000 + ], + "yDomain": [ + 190000, + 240000 + ], + "colorScale": "symlog", + "colorScheme": "oranges" + } + ] + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "rectY", + "data": { + "from": "trips" + }, + "x": { + "bin": "time" + }, + "y": { + "count": null + }, + "fill": "steelblue", + "inset": 0.5 + }, + { + "select": "intervalX", + "as": "$filter" + } + ], + "yTickFormat": "s", + "xLabel": "Pickup Hour →", + "width": 680, + "height": 100 + } + ] +}; diff --git a/specs/ts/observable-latency.ts b/specs/ts/observable-latency.ts new file mode 100644 index 00000000..ff301bc0 --- /dev/null +++ b/specs/ts/observable-latency.ts @@ -0,0 +1,123 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Observable Latency", + "description": "Web request latency on Observable.com.\nEach pixel in the heatmap shows the most common route (URL pattern) at a given response latency within a time interval.\nUse the bar chart of most-requested routes to filter the heatmap and isolate specific patterns.\nOr, select a range in the heatmap to show the corresponding most-requested routes.\n\n_You may need to wait a few seconds for the dataset to load._\n", + "credit": "Adapted from an [Observable Framework example](https://observablehq.com/framework/examples/api/)." + }, + "data": { + "latency": { + "file": "https://uwdata.github.io/mosaic-datasets/data/observable-latency.parquet" + } + }, + "params": { + "filter": { + "select": "crossfilter" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "frame", + "fill": "black" + }, + { + "mark": "raster", + "data": { + "from": "latency", + "filterBy": "$filter" + }, + "x": "time", + "y": "latency", + "fill": { + "argmax": [ + "route", + "count" + ] + }, + "fillOpacity": { + "sum": "count" + }, + "width": 2016, + "height": 500, + "imageRendering": "pixelated" + }, + { + "select": "intervalXY", + "as": "$filter" + } + ], + "colorDomain": "Fixed", + "colorScheme": "observable10", + "opacityDomain": [ + 0, + 25 + ], + "opacityClamp": true, + "yScale": "log", + "yLabel": "↑ Duration (ms)", + "yDomain": [ + 0.5, + 10000 + ], + "yTickFormat": "s", + "xScale": "utc", + "xLabel": null, + "xDomain": [ + 1706227200000, + 1706832000000 + ], + "width": 680, + "height": 300, + "margins": { + "left": 35, + "top": 20, + "bottom": 30, + "right": 20 + } + }, + { + "plot": [ + { + "mark": "barX", + "data": { + "from": "latency", + "filterBy": "$filter" + }, + "x": { + "sum": "count" + }, + "y": "route", + "fill": "route", + "sort": { + "y": "-x", + "limit": 15 + } + }, + { + "select": "toggleY", + "as": "$filter" + }, + { + "select": "toggleY", + "as": "$highlight" + }, + { + "select": "highlight", + "by": "$highlight" + } + ], + "colorDomain": "Fixed", + "xLabel": "Routes by Total Requests", + "xTickFormat": "s", + "yLabel": null, + "width": 680, + "height": 300, + "marginTop": 5, + "marginLeft": 220, + "marginBottom": 35 + } + ] +}; diff --git a/specs/ts/overview-detail.ts b/specs/ts/overview-detail.ts new file mode 100644 index 00000000..dfeb53fa --- /dev/null +++ b/specs/ts/overview-detail.ts @@ -0,0 +1,51 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Overview + Detail", + "description": "Select the top \"overview\" series to zoom the \"focus\" view below. An `intervalX` interactor updates a selection that filters the focus view. The `line` and `area` marks can apply [M4](https://observablehq.com/@uwdata/m4-scalable-time-series-visualization) optimization to reduce the number of data points returned: rather than draw all points, a dramatically smaller subset can still faithfully represent these area charts.\n" + }, + "data": { + "walk": { + "file": "data/random-walk.parquet" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "areaY", + "data": { + "from": "walk" + }, + "x": "t", + "y": "v", + "fill": "steelblue" + }, + { + "select": "intervalX", + "as": "$brush" + } + ], + "width": 680, + "height": 200 + }, + { + "plot": [ + { + "mark": "areaY", + "data": { + "from": "walk", + "filterBy": "$brush" + }, + "x": "t", + "y": "v", + "fill": "steelblue" + } + ], + "yDomain": "Fixed", + "width": 680, + "height": 200 + } + ] +}; diff --git a/specs/ts/pan-zoom.ts b/specs/ts/pan-zoom.ts new file mode 100644 index 00000000..f30f9f24 --- /dev/null +++ b/specs/ts/pan-zoom.ts @@ -0,0 +1,132 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Pan & Zoom", + "description": "Linked panning and zooming across plots: drag to pan, scroll to zoom. `panZoom` interactors update a set of bound selections, one per unique axis.\n" + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "hconcat": [ + { + "vconcat": [ + { + "plot": [ + { + "mark": "frame" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "species", + "r": 2, + "clip": true + }, + { + "select": "panZoom", + "x": "$xs", + "y": "$ys" + } + ], + "width": 320, + "height": 240 + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "frame" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "flipper_length", + "fill": "species", + "r": 2, + "clip": true + }, + { + "select": "panZoom", + "x": "$xs", + "y": "$zs" + } + ], + "width": 320, + "height": 240 + } + ] + }, + { + "hspace": 10 + }, + { + "vconcat": [ + { + "plot": [ + { + "mark": "frame" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "bill_depth", + "fill": "species", + "r": 2, + "clip": true + }, + { + "select": "panZoom", + "x": "$ws", + "y": "$ys" + } + ], + "width": 320, + "height": 240 + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "frame" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "flipper_length", + "fill": "species", + "r": 2, + "clip": true + }, + { + "select": "panZoom", + "x": "$ws", + "y": "$zs" + } + ], + "width": 320, + "height": 240 + } + ] + } + ] +}; diff --git a/specs/ts/population-arrows.ts b/specs/ts/population-arrows.ts new file mode 100644 index 00000000..3fe33b42 --- /dev/null +++ b/specs/ts/population-arrows.ts @@ -0,0 +1,72 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Population Change Arrows", + "description": "An `arrow` connects the positions in 1980 and 2015 of each city on this population × inequality chart. Color encodes variation.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-arrow-variation-chart)." + }, + "data": { + "metros": { + "file": "data/metros.parquet" + } + }, + "params": { + "bend": true + }, + "vconcat": [ + { + "legend": "color", + "for": "arrows", + "label": "Change in inequality from 1980 to 2015" + }, + { + "name": "arrows", + "plot": [ + { + "mark": "arrow", + "data": { + "from": "metros" + }, + "x1": "POP_1980", + "y1": "R90_10_1980", + "x2": "POP_2015", + "y2": "R90_10_2015", + "bend": "$bend", + "stroke": { + "sql": "R90_10_2015 - R90_10_1980" + } + }, + { + "mark": "text", + "data": { + "from": "metros" + }, + "x": "POP_2015", + "y": "R90_10_2015", + "filter": "highlight", + "text": "nyt_display", + "fill": "currentColor", + "dy": -6 + } + ], + "grid": true, + "inset": 10, + "xScale": "log", + "xLabel": "Population →", + "yLabel": "↑ Inequality", + "yTicks": 4, + "colorScheme": "BuRd", + "colorTickFormat": "+f" + }, + { + "input": "menu", + "label": "Bend Arrows?", + "options": [ + true, + false + ], + "as": "$bend" + } + ] +}; diff --git a/specs/ts/presidential-opinion.ts b/specs/ts/presidential-opinion.ts new file mode 100644 index 00000000..4cd9f179 --- /dev/null +++ b/specs/ts/presidential-opinion.ts @@ -0,0 +1,64 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Presidential Opinion", + "description": "Opinion poll data on historical U.S. presidents. Image marks are used to show presidential pictures. The dropdown menu toggles the opinion metric shown.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-image-medals)." + }, + "data": { + "presidents": { + "file": "data/us-president-favorability.parquet" + } + }, + "params": { + "sign": 1 + }, + "vconcat": [ + { + "plot": [ + { + "mark": "ruleY", + "data": [ + 0 + ] + }, + { + "mark": "image", + "data": { + "from": "presidents" + }, + "x": "First Inauguration Date", + "y": { + "sql": "\"Very Favorable %\" + \"Somewhat Favorable %\" + $sign * (\"Very Unfavorable %\" + \"Somewhat Unfavorable %\")" + }, + "src": "Portrait URL", + "r": 20, + "preserveAspectRatio": "xMidYMin slice", + "title": "Name" + } + ], + "xInset": 20, + "xLabel": "First inauguration date →", + "yInsetTop": 4, + "yGrid": true, + "yLabel": "↑ Opinion (%)", + "yTickFormat": "+f" + }, + { + "input": "menu", + "label": "Opinion Metric", + "options": [ + { + "label": "Any Opinion", + "value": 1 + }, + { + "label": "Net Favorability", + "value": -1 + } + ], + "as": "$sign" + } + ] +}; diff --git a/specs/ts/seattle-temp.ts b/specs/ts/seattle-temp.ts new file mode 100644 index 00000000..12759abb --- /dev/null +++ b/specs/ts/seattle-temp.ts @@ -0,0 +1,63 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Seattle Temperatures", + "description": "Historical monthly temperatures in Seattle, WA. The gray range shows the minimum and maximum recorded temperatures. The blue range shows the average lows and highs.\n" + }, + "data": { + "weather": { + "file": "data/seattle-weather.parquet" + } + }, + "plot": [ + { + "mark": "areaY", + "data": { + "from": "weather" + }, + "x": { + "dateMonth": "date" + }, + "y1": { + "max": "temp_max" + }, + "y2": { + "min": "temp_min" + }, + "fill": "#ccc", + "fillOpacity": 0.25, + "curve": "monotone-x" + }, + { + "mark": "areaY", + "data": { + "from": "weather" + }, + "x": { + "dateMonth": "date" + }, + "y1": { + "avg": "temp_max" + }, + "y2": { + "avg": "temp_min" + }, + "fill": "steelblue", + "fillOpacity": 0.75, + "curve": "monotone-x" + }, + { + "mark": "ruleY", + "data": [ + 15 + ], + "strokeOpacity": 0.5, + "strokeDasharray": "5 5" + } + ], + "xTickFormat": "%b", + "yLabel": "Temperature Range (°C)", + "width": 680, + "height": 300 +}; diff --git a/specs/ts/sorted-bars.ts b/specs/ts/sorted-bars.ts new file mode 100644 index 00000000..de2afb50 --- /dev/null +++ b/specs/ts/sorted-bars.ts @@ -0,0 +1,50 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Sorted Bars", + "description": "Sort and limit an aggregate bar chart of gold medals by country.\n" + }, + "data": { + "athletes": { + "file": "data/athletes.parquet" + } + }, + "vconcat": [ + { + "input": "menu", + "label": "Sport", + "as": "$query", + "from": "athletes", + "column": "sport", + "value": "aquatics" + }, + { + "vspace": 10 + }, + { + "plot": [ + { + "mark": "barX", + "data": { + "from": "athletes", + "filterBy": "$query" + }, + "x": { + "sum": "gold" + }, + "y": "nationality", + "fill": "steelblue", + "sort": { + "y": "-x", + "limit": 10 + } + } + ], + "xLabel": "Gold Medals", + "yLabel": "Nationality", + "yLabelAnchor": "top", + "marginTop": 15 + } + ] +}; diff --git a/specs/ts/splom.ts b/specs/ts/splom.ts new file mode 100644 index 00000000..4bc95356 --- /dev/null +++ b/specs/ts/splom.ts @@ -0,0 +1,511 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Scatter Plot Matrix (SPLOM)", + "description": "A scatter plot matrix enables inspection of pairwise bivariate distributions. Do points cluster or separate in some dimensions but not others? Select a region to highlight corresponding points across all plots.\n" + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "params": { + "brush": { + "select": "single" + } + }, + "plotDefaults": { + "xTicks": 3, + "yTicks": 4, + "xDomain": "Fixed", + "yDomain": "Fixed", + "colorDomain": "Fixed", + "marginTop": 5, + "marginBottom": 10, + "marginLeft": 10, + "marginRight": 5, + "xAxis": null, + "yAxis": null, + "xLabelAnchor": "center", + "yLabelAnchor": "center", + "xTickFormat": "s", + "yTickFormat": "s", + "width": 150, + "height": 150 + }, + "vconcat": [ + { + "hconcat": [ + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "body_mass", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "yAxis": "left", + "marginLeft": 45, + "width": 185 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_depth", + "y": "body_mass", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "flipper_length", + "y": "body_mass", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "body_mass", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "flipper_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "yAxis": "left", + "marginLeft": 45, + "width": 185 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_depth", + "y": "flipper_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "flipper_length", + "y": "flipper_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "flipper_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "yAxis": "left", + "marginLeft": 45, + "width": 185 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_depth", + "y": "bill_depth", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "flipper_length", + "y": "bill_depth", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "bill_depth", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ] + } + ] + }, + { + "hconcat": [ + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "yAxis": "left", + "xAxis": "bottom", + "marginLeft": 45, + "marginBottom": 35, + "width": 185, + "height": 175 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_depth", + "y": "bill_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "xAxis": "bottom", + "height": 175, + "marginBottom": 35 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "flipper_length", + "y": "bill_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "xAxis": "bottom", + "height": 175, + "marginBottom": 35 + }, + { + "plot": [ + { + "mark": "frame", + "stroke": "#ccc" + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "bill_length", + "fill": "species", + "r": 2 + }, + { + "select": "intervalXY", + "as": "$brush" + }, + { + "select": "highlight", + "by": "$brush", + "opacity": 0.1 + } + ], + "xAxis": "bottom", + "height": 175, + "marginBottom": 35 + } + ] + } + ] +}; diff --git a/specs/ts/symbols.ts b/specs/ts/symbols.ts new file mode 100644 index 00000000..01021d90 --- /dev/null +++ b/specs/ts/symbols.ts @@ -0,0 +1,72 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Symbol Plots", + "description": "Two scatter plots with `dot` marks: one with stroked symbols, the other filled.\n" + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "vconcat": [ + { + "hconcat": [ + { + "name": "stroked", + "plot": [ + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "flipper_length", + "stroke": "species", + "symbol": "species" + } + ], + "grid": true, + "xLabel": "Body mass (g) →", + "yLabel": "↑ Flipper length (mm)" + }, + { + "legend": "symbol", + "for": "stroked", + "columns": 1 + } + ] + }, + { + "vspace": 20 + }, + { + "hconcat": [ + { + "name": "filled", + "plot": [ + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "body_mass", + "y": "flipper_length", + "fill": "species", + "symbol": "species" + } + ], + "grid": true, + "xLabel": "Body mass (g) →", + "yLabel": "↑ Flipper length (mm)" + }, + { + "legend": "symbol", + "for": "filled", + "columns": 1 + } + ] + } + ] +}; diff --git a/specs/ts/table.ts b/specs/ts/table.ts new file mode 100644 index 00000000..7d80b11c --- /dev/null +++ b/specs/ts/table.ts @@ -0,0 +1,16 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Sortable Table", + "description": "A sortable, \"infinite scroll\" `table` view over a backing database table. Click column headers to sort, or command-click to reset the order. Data is queried as needed as the table is sorted or scrolled.\n" + }, + "data": { + "flights": { + "file": "data/flights-200k.parquet" + } + }, + "input": "table", + "from": "flights", + "height": 300 +}; diff --git a/specs/ts/unemployment.ts b/specs/ts/unemployment.ts new file mode 100644 index 00000000..7b84049b --- /dev/null +++ b/specs/ts/unemployment.ts @@ -0,0 +1,47 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "U.S. Unemployment", + "description": "A choropleth map of unemployment rates for U.S. counties. Requires the DuckDB `spatial` extension.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-us-choropleth)." + }, + "data": { + "counties": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "counties" + }, + "rates": { + "file": "data/us-county-unemployment.parquet" + }, + "combined": "SELECT a.geom AS geom, b.rate AS rate FROM counties AS a, rates AS b WHERE a.id = b.id\n" + }, + "vconcat": [ + { + "legend": "color", + "for": "county-map", + "label": "Unemployment (%)" + }, + { + "name": "county-map", + "plot": [ + { + "mark": "geo", + "data": { + "from": "combined" + }, + "fill": "rate", + "title": { + "sql": "concat(rate, '%')" + } + } + ], + "margin": 0, + "colorScale": "quantile", + "colorN": 9, + "colorScheme": "blues", + "projectionType": "albers-usa" + } + ] +}; diff --git a/specs/ts/us-county-map.ts b/specs/ts/us-county-map.ts new file mode 100644 index 00000000..a9431146 --- /dev/null +++ b/specs/ts/us-county-map.ts @@ -0,0 +1,56 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "U.S. Counties", + "description": "A map of U.S. counties. County name tooltips are anchored to invisible centroid dot marks. Requires the DuckDB `spatial` extension.\n" + }, + "data": { + "counties": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "counties" + }, + "states": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "states" + } + }, + "plot": [ + { + "mark": "geo", + "data": { + "from": "counties" + }, + "stroke": "currentColor", + "strokeWidth": 0.25 + }, + { + "mark": "geo", + "data": { + "from": "states" + }, + "stroke": "currentColor", + "strokeWidth": 1 + }, + { + "mark": "dot", + "data": { + "from": "counties" + }, + "x": { + "centroidX": "geom" + }, + "y": { + "centroidY": "geom" + }, + "r": 2, + "fill": "transparent", + "tip": true, + "title": "name" + } + ], + "margin": 0, + "projectionType": "albers" +}; diff --git a/specs/ts/us-state-map.ts b/specs/ts/us-state-map.ts new file mode 100644 index 00000000..992e7a97 --- /dev/null +++ b/specs/ts/us-state-map.ts @@ -0,0 +1,44 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "U.S. States", + "description": "A map of U.S. states overlaid with computed centroids. Requires the DuckDB `spatial` extension.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-state-centroids)." + }, + "data": { + "states": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "states" + } + }, + "plot": [ + { + "mark": "geo", + "data": { + "from": "states" + }, + "stroke": "currentColor", + "strokeWidth": 1 + }, + { + "mark": "dot", + "data": { + "from": "states" + }, + "x": { + "centroidX": "geom" + }, + "y": { + "centroidY": "geom" + }, + "r": 2, + "fill": "steelblue", + "tip": true, + "title": "name" + } + ], + "margin": 0, + "projectionType": "albers" +}; diff --git a/specs/ts/voronoi.ts b/specs/ts/voronoi.ts new file mode 100644 index 00000000..ddb448aa --- /dev/null +++ b/specs/ts/voronoi.ts @@ -0,0 +1,112 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Voronoi Diagram", + "description": "The `voronoi` mark shows the regions closest to each point. It is [constructed from its dual](https://observablehq.com/@mbostock/the-delaunays-dual), a Delaunay triangle mesh. Reveal triangulations or convex hulls using the dropdowns.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-voronoi-scatterplot)." + }, + "data": { + "penguins": { + "file": "data/penguins.parquet" + } + }, + "params": { + "mesh": 0, + "hull": 0 + }, + "vconcat": [ + { + "plot": [ + { + "mark": "voronoi", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "stroke": "white", + "strokeWidth": 1, + "strokeOpacity": 0.5, + "fill": "species", + "fillOpacity": 0.2 + }, + { + "mark": "hull", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "stroke": "species", + "strokeOpacity": "$hull", + "strokeWidth": 1.5 + }, + { + "mark": "delaunayMesh", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "z": "species", + "stroke": "species", + "strokeOpacity": "$mesh", + "strokeWidth": 1 + }, + { + "mark": "dot", + "data": { + "from": "penguins" + }, + "x": "bill_length", + "y": "bill_depth", + "fill": "species", + "r": 2 + }, + { + "mark": "frame" + } + ], + "inset": 10, + "width": 680 + }, + { + "hconcat": [ + { + "input": "menu", + "label": "Delaunay Mesh", + "options": [ + { + "value": 0, + "label": "Hide" + }, + { + "value": 0.5, + "label": "Show" + } + ], + "as": "$mesh" + }, + { + "hspace": 5 + }, + { + "input": "menu", + "label": "Convex Hull", + "options": [ + { + "value": 0, + "label": "Hide" + }, + { + "value": 1, + "label": "Show" + } + ], + "as": "$hull" + } + ] + } + ] +}; diff --git a/specs/ts/walmart-openings.ts b/specs/ts/walmart-openings.ts new file mode 100644 index 00000000..7ec4fa3c --- /dev/null +++ b/specs/ts/walmart-openings.ts @@ -0,0 +1,67 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Walmart Openings", + "description": "Maps showing Walmart store openings per decade. Requires the DuckDB `spatial` extension.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-map-large-multiples)." + }, + "data": { + "states": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "states" + }, + "nation": { + "type": "spatial", + "file": "data/us-counties-10m.json", + "layer": "nation" + }, + "walmarts": { + "file": "data/walmarts.parquet" + } + }, + "vconcat": [ + { + "plot": [ + { + "mark": "geo", + "data": { + "from": "states" + }, + "stroke": "#aaa", + "strokeWidth": 1 + }, + { + "mark": "geo", + "data": { + "from": "nation" + }, + "stroke": "currentColor" + }, + { + "mark": "dot", + "data": { + "from": "walmarts" + }, + "x": "longitude", + "y": "latitude", + "fy": { + "sql": "10 * decade(date)" + }, + "r": 1.5, + "fill": "blue" + }, + { + "mark": "axisFy", + "frameAnchor": "top", + "dy": 30, + "tickFormat": "d" + } + ], + "margin": 0, + "fyLabel": null, + "projectionType": "albers" + } + ] +}; diff --git a/specs/ts/weather.ts b/specs/ts/weather.ts new file mode 100644 index 00000000..5f01f802 --- /dev/null +++ b/specs/ts/weather.ts @@ -0,0 +1,129 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Seattle Weather", + "description": "An interactive view of Seattle’s weather, including maximum temperature, amount of precipitation, and type of weather. By dragging on the scatter plot, you can see the proportion of days in that range that have sun, fog, drizzle, rain, or snow.\n", + "credit": "Based on a [Vega-Lite/Altair example](https://vega.github.io/vega-lite/examples/interactive_seattle_weather.html) by Jake Vanderplas." + }, + "data": { + "weather": { + "file": "data/seattle-weather.parquet" + } + }, + "params": { + "click": { + "select": "single" + }, + "domain": [ + "sun", + "fog", + "drizzle", + "rain", + "snow" + ], + "colors": [ + "#e7ba52", + "#a7a7a7", + "#aec7e8", + "#1f77b4", + "#9467bd" + ] + }, + "vconcat": [ + { + "hconcat": [ + { + "plot": [ + { + "mark": "dot", + "data": { + "from": "weather", + "filterBy": "$click" + }, + "x": { + "dateMonthDay": "date" + }, + "y": "temp_max", + "fill": "weather", + "r": "precipitation", + "fillOpacity": 0.7 + }, + { + "select": "intervalX", + "as": "$range", + "brush": { + "fill": "none", + "stroke": "#888" + } + }, + { + "select": "highlight", + "by": "$range", + "fill": "#ccc", + "fillOpacity": 0.2 + }, + { + "legend": "color", + "as": "$click", + "columns": 1 + } + ], + "xyDomain": "Fixed", + "xTickFormat": "%b", + "colorDomain": "$domain", + "colorRange": "$colors", + "rDomain": "Fixed", + "rRange": [ + 2, + 10 + ], + "width": 680, + "height": 300 + } + ] + }, + { + "plot": [ + { + "mark": "barX", + "data": { + "from": "weather" + }, + "x": { + "count": null + }, + "y": "weather", + "fill": "#ccc", + "fillOpacity": 0.2 + }, + { + "mark": "barX", + "data": { + "from": "weather", + "filterBy": "$range" + }, + "x": { + "count": null + }, + "y": "weather", + "fill": "weather" + }, + { + "select": "toggleY", + "as": "$click" + }, + { + "select": "highlight", + "by": "$click" + } + ], + "xDomain": "Fixed", + "yDomain": "$domain", + "yLabel": null, + "colorDomain": "$domain", + "colorRange": "$colors", + "width": 680 + } + ] +}; diff --git a/specs/ts/wind-map.ts b/specs/ts/wind-map.ts new file mode 100644 index 00000000..b0cccc24 --- /dev/null +++ b/specs/ts/wind-map.ts @@ -0,0 +1,59 @@ +import { Spec } from '@uwdata/mosaic-spec'; + +export const spec : Spec = { + "meta": { + "title": "Wind Map", + "description": "`vector` marks on a grid show both direction and intensity—here, the speed of winds. Expressions for `rotate`, `length`, and `stroke` values are evaluated in the database.\n", + "credit": "Adapted from an [Observable Plot example](https://observablehq.com/@observablehq/plot-wind-map)." + }, + "data": { + "wind": { + "file": "data/wind.parquet" + } + }, + "params": { + "length": 2 + }, + "vconcat": [ + { + "legend": "color", + "for": "wind-map", + "label": "Speed (m/s)" + }, + { + "name": "wind-map", + "plot": [ + { + "mark": "vector", + "data": { + "from": "wind" + }, + "x": "longitude", + "y": "latitude", + "rotate": { + "sql": "degrees(atan2(u, v))" + }, + "length": { + "sql": "$length * sqrt(u * u + v * v)" + }, + "stroke": { + "sql": "sqrt(u * u + v * v)" + } + } + ], + "lengthScale": "identity", + "colorZero": true, + "inset": 10, + "aspectRatio": 1, + "width": 680 + }, + { + "input": "slider", + "min": 1, + "max": 7, + "step": 0.1, + "as": "$length", + "label": "Vector Length" + } + ] +}; diff --git a/specs/yaml/axes.yaml b/specs/yaml/axes.yaml index 3514c2fa..9cdd0a08 100644 --- a/specs/yaml/axes.yaml +++ b/specs/yaml/axes.yaml @@ -5,7 +5,7 @@ meta: scale attributes such as `xAxis`, `yGrid`, etc. Just add data! plot: - mark: gridY - strokeDasharray: [0.75, 2] # dashed + strokeDasharray: 0.75 2 # dashed strokeOpacity: 1 # opaque - mark: axisY anchor: left diff --git a/specs/yaml/seattle-temp.yaml b/specs/yaml/seattle-temp.yaml index d9d013ad..43658195 100644 --- a/specs/yaml/seattle-temp.yaml +++ b/specs/yaml/seattle-temp.yaml @@ -26,7 +26,7 @@ plot: - mark: ruleY data: [15] strokeOpacity: 0.5 - strokeDasharray: [5, 5] + strokeDasharray: 5 5 xTickFormat: '%b' yLabel: Temperature Range (°C) width: 680 diff --git a/specs/yaml/voronoi.yaml b/specs/yaml/voronoi.yaml index 0f86eaa7..aa8b2ef0 100644 --- a/specs/yaml/voronoi.yaml +++ b/specs/yaml/voronoi.yaml @@ -19,7 +19,6 @@ vconcat: stroke: white strokeWidth: 1 strokeOpacity: 0.5 - inset: 1 fill: species fillOpacity: 0.2 - mark: hull