diff --git a/.eslintrc.json b/.eslintrc.json index 7a17582b67..fc5c91c75c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,14 +35,7 @@ } }, { - "files": ["**/test/**/*.js"], - "rules": { - "max-len": ["warn", { - "code": 200, - "comments": 200, - "ignoreTrailingComments": true - }] - } + "files": ["**/test/**/*.js"] }, { "files": ["**/*.md"], "rules": { @@ -54,24 +47,11 @@ }], "rules": { "prettier/prettier": ["error", { - "printWidth": 200, "tabWidth": 4, "bracketSpacing": false }], - "max-len": ["warn", { - "code": 200, - "comments": 80, - "ignoreTrailingComments": true - }], "@typescript-eslint/camelcase": "off", "linebreak-style": ["error","unix"], - "camelcase": "off", - "no-const-assign": "error", - "no-this-before-super": "error", - "no-undef": "error", - "no-unreachable": "error", - "no-unused-vars": "error", - "constructor-super": "error", - "valid-typeof": "error" + "camelcase": "off" } } \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 90b93bb798..8cb50d0c7a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -87,6 +87,9 @@ parameters: - name: webassemblySteps type: stepList default: + - bash: yarn lint_js + displayName: "Lint" + - bash: yarn build_js --ci displayName: "Build" @@ -104,6 +107,9 @@ parameters: - name: pythonBuildDockerSteps type: stepList default: + - bash: yarn lint_python + displayName: "Lint" + - bash: yarn build_python --ci $(python_flag) $(manylinux_flag) displayName: 'build' env: diff --git a/cpp/perspective/build.js b/cpp/perspective/build.js index d39bb1f157..93ae87abc6 100644 --- a/cpp/perspective/build.js +++ b/cpp/perspective/build.js @@ -9,7 +9,10 @@ const cwd = path.join(process.cwd(), "dist", env); try { execSync(`mkdirp ${cwd}`, {stdio}); process.env.CLICOLOR_FORCE = 1; - execSync(`emcmake cmake ${__dirname} -DCMAKE_BUILD_TYPE=${env}`, {cwd, stdio}); + execSync(`emcmake cmake ${__dirname} -DCMAKE_BUILD_TYPE=${env}`, { + cwd, + stdio, + }); execSync(`emmake make -j${os.cpus().length}`, {cwd, stdio}); execSync(`cpx esm/**/* ../esm`, {cwd, stdio}); execSync(`cpx cjs/**/* ../cjs`, {cwd, stdio}); diff --git a/docs/md/concepts.md b/docs/md/concepts.md index fccc3dc769..b4acc6c835 100644 --- a/docs/md/concepts.md +++ b/docs/md/concepts.md @@ -3,9 +3,9 @@ id: concepts title: Conceptual Overview --- -Perspective is designed and built as an _interactive_ visualization -component for _large, real-time_ datasets. It is designed to be fast, intuitive, -and easily composable, making fine-grained analysis possible on any dataset. +Perspective is designed and built as an _interactive_ visualization component +for _large, real-time_ datasets. It is designed to be fast, intuitive, and +easily composable, making fine-grained analysis possible on any dataset. ## Overview @@ -14,8 +14,8 @@ This document outlines the main concepts behind Perspective: - [`Table`](#table) Perspective's typed data interface - [`View`](#view) a query from a `Table` that calculates and returns data -and explains (with live examples) the query options that can be used to create -a `View`: +and explains (with live examples) the query options that can be used to create a +`View`: - [`row_pivots`](#row-pivots) splitting the dataset by unique row values - [`column_pivots`](#column-pivots) splitting the dataset by unique column @@ -27,8 +27,8 @@ a `View`: - [`filter`](#filter) filtering the dataset based on a column's values For language-specific guidance, API documentation, or quick-start user guides, -use the sidebar to find the documentation for the language of choice. Though -the way these concepts operate in practice may vary slightly across different +use the sidebar to find the documentation for the language of choice. Though the +way these concepts operate in practice may vary slightly across different languages based on nuance, the concepts documented here hold true across all implementations of the Perspective library. @@ -44,14 +44,14 @@ operations can be called. A `Table` contains columns, each of which have a unique name, are strongly and consistently typed, and contains rows of data conforming to the column's type. -Each column in a `Table` must have the same number of rows, though not every -row must contain data; null-values are used to indicate missing values in the +Each column in a `Table` must have the same number of rows, though not every row +must contain data; null-values are used to indicate missing values in the dataset. The columns of a `Table` are _immutable after creation_, which means their names and data types cannot be changed after the `Table` has been created. Columns -cannot be added or deleted after creation, but a `View` can be used to select -an arbitrary set of columns from the `Table`. +cannot be added or deleted after creation, but a `View` can be used to select an +arbitrary set of columns from the `Table`. The immutability of columns and data types after creation is important, as it allows Perspective to operate quickly over a large dataset and accumulate data @@ -64,18 +64,18 @@ The mapping of a `Table`'s column names to data types is referred to as a ```javascript const schema = { - a: "integer", - b: "float", - c: "date", - d: "datetime", - e: "boolean", - f: "string" + a: "integer", + b: "float", + c: "date", + d: "datetime", + e: "boolean", + f: "string", }; ``` -Because Perspective is built in multiple languages, data types are -expressed with a common vocabulary of across all supported host languages. -These _String_ types are used to represent the data supported by Perspective: +Because Perspective is built in multiple languages, data types are expressed +with a common vocabulary of across all supported host languages. These _String_ +types are used to represent the data supported by Perspective: - `"integer"`: a 32-bit (or 64-bit depending on platform) integer - `"float"`: a 64-bit float @@ -104,26 +104,26 @@ schema = { A `Table` can be initialized in two ways: -- With a [`schema`](#schema-and-data-types); this table will be initialized +- With a [`schema`](#schema-and-data-types); this table will be initialized empty. -- With a dataset in a supported format; in this case, a `schema` is inferred - from the dataset's structure upon initialization. Perspective supports a +- With a dataset in a supported format; in this case, a `schema` is inferred + from the dataset's structure upon initialization. Perspective supports a variety of table-like data structures in Python and Javascript such as CSV, - `pandas.DataFrame` and JSON; see the language specific API documentation for - a comprehensive list of supported formats. + `pandas.DataFrame` and JSON; see the language specific API documentation for a + comprehensive list of supported formats. ### Limit Initializing a `Table` with a `limit` sets the total number of rows the `Table` -is allowed to have. When the `Table` is updated, and the resulting size of -the `Table` would exceed its `limit`, rows that exceed `limit` overwrite the -oldest rows in the `Table`. +is allowed to have. When the `Table` is updated, and the resulting size of the +`Table` would exceed its `limit`, rows that exceed `limit` overwrite the oldest +rows in the `Table`. To create a `Table` with a `limit`, provide the `limit` property with an integer indicating the `limit`: ```javascript -const table = await perspective.table(data, {limit: 1000}); +const table = await perspective.table(data, { limit: 1000 }); ``` `limit` cannot be used in conjunction with `index`. @@ -131,16 +131,16 @@ const table = await perspective.table(data, {limit: 1000}); ### Index Initializing a `Table` with an `index` allows Perspective to treat a column as -the primary key, allowing in-place updates of rows. Only a -single column can be used as an `index`, and like other `Table` parameters, -cannot be changed or removed after the `Table` creation. A column of any type -may be used as an `index`. +the primary key, allowing in-place updates of rows. Only a single column can be +used as an `index`, and like other `Table` parameters, cannot be changed or +removed after the `Table` creation. A column of any type may be used as an +`index`. To create an indexed `Table`, provide the `index` property with a string column name to be used as an index: ```javascript -const table = await perspective.table(data, {index: "a"}); +const table = await perspective.table(data, { index: "a" }); ``` An indexed `Table` allows for in-place _updates_ whenever a new rows shares an @@ -151,16 +151,16 @@ some column values `undefined`, and _removes_ to delete a row by `index`. Once a `Table` has been created, it can be updated with new data conforming to the `Table`'s `schema`. The dataset used for `update()` must conform with the -formats supported by Perspective, and cannot be a `schema` (as the `schema` -is immutable). +formats supported by Perspective, and cannot be a `schema` (as the `schema` is +immutable). If a `Table` was initialized with a `schema` instead of a dataset, use `update` to fill the `Table` with data. ```javascript const table = await perspective.table({ - a: "integer", - b: "float" + a: "integer", + b: "float", }); table.update(new_data); ``` @@ -172,25 +172,25 @@ using the `index` to determine which rows to update: ```javascript // Create an indexed table const table = await perspective.table( - { - id: [1, 2, 3, 4], - name: ["a", "b", "c", "d"] - }, - {index: "id"} + { + id: [1, 2, 3, 4], + name: ["a", "b", "c", "d"], + }, + { index: "id" } ); // Update rows with primary key `1` and `4` table.update({ - id: [1, 4], - name: ["x", "y"] + id: [1, 4], + name: ["x", "y"], }); ``` Provide a dataset with the rows to be updated as specified by primary key, and -Perspective handles the lookup into those rows and applies the specified -updates from the dataset. If `update` is called on an indexed `Table` and -no primary keys are specified, or if the specified keys are not present in the -`Table`, Perspective will append the dataset to the end of the `Table`. +Perspective handles the lookup into those rows and applies the specified updates +from the dataset. If `update` is called on an indexed `Table` and no primary +keys are specified, or if the specified keys are not present in the `Table`, +Perspective will append the dataset to the end of the `Table`. ### `remove()` @@ -199,11 +199,11 @@ An indexed `Table` can also have rows removed by primary key: ```javascript // Create an indexed table const table = await perspective.table( - { - id: [1, 2, 3, 4], - name: ["a", "b", "c", "d"] - }, - {index: "id"} + { + id: [1, 2, 3, 4], + name: ["a", "b", "c", "d"], + }, + { index: "id" } ); // Remove rows with primary key `1` and `4` @@ -221,7 +221,8 @@ binding language's garbage collector (Javascript, Python etc.) The `Table`'s `delete` method guarantees the cleanup of all resources associated with a `Table`, which is _especially important_ in Javascript, as the JS garbage -collector [cannot automatically clean up](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-management) +collector +[cannot automatically clean up](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-management) objects created in C++ through Emscripten. It is best practice to explicitly `delete` any `Table`s when they are no longer @@ -246,31 +247,31 @@ instance via the `view()` method with a set of ```javascript const table = await perspective.table({ - id: [1, 2, 3, 4], - name: ["a", "b", "c", "d"] + id: [1, 2, 3, 4], + name: ["a", "b", "c", "d"], }); // Create a view showing just the `name` column. const view = await table.view({ - columns: ["name"] + columns: ["name"], }); // Now you have a `View`! Get your data using ES6 async/await: const json = async () => await view.to_json(); // or using the Promise API -view.to_arrow().then(arrow => console.log(arrow)); +view.to_arrow().then((arrow) => console.log(arrow)); // Delete the Query! view.delete(); ``` `View` objects are immutable with respect to the arguments provided to the -`view()` method; to change these parameters, you must create a new `View` on -the same `Table`. However, `View`s are live with respect to the `Table`'s data, -and will (within a conflation window) update with the latest state as its -parent's state updates, including incrementally recalculating all aggregates, -pivots, filters, etc. +`view()` method; to change these parameters, you must create a new `View` on the +same `Table`. However, `View`s are live with respect to the `Table`'s data, and +will (within a conflation window) update with the latest state as its parent's +state updates, including incrementally recalculating all aggregates, pivots, +filters, etc. ### Schema @@ -287,13 +288,12 @@ for example, will return `"integer"` in the `View`'s `schema`. ### `on_update` -The `on_update` callback allows users to execute an action immediately after -the `View`'s underlying `Table` has been updated. This is useful in situations -where updating the `Table` leads to UI changes, recalculations, or any other -actions that need to be triggered. Multiple `on_update` callbacks can be -specified, and they will run in the order in which they were set. If a callback -is no longer needed to run, use the `remove_update` method on the `View` -instance. +The `on_update` callback allows users to execute an action immediately after the +`View`'s underlying `Table` has been updated. This is useful in situations where +updating the `Table` leads to UI changes, recalculations, or any other actions +that need to be triggered. Multiple `on_update` callbacks can be specified, and +they will run in the order in which they were set. If a callback is no longer +needed to run, use the `remove_update` method on the `View` instance. ### `on_delete` @@ -312,11 +312,11 @@ instance must be explicitly deleted before the `Table` can be deleted. ## `View` Query Parameters The `View`'s query parameters present a powerful and composable interface for -data query and transformation. All parameters can be applied in conjunction -with each other, and there is no limit to the number of pivots, filters, etc. -that can be applied. +data query and transformation. All parameters can be applied in conjunction with +each other, and there is no limit to the number of pivots, filters, etc. that +can be applied. -*All* examples in this section are live—feel free to play around with each +_All_ examples in this section are live—feel free to play around with each `` instance to see how different query parameters affect what you see. @@ -325,9 +325,9 @@ what you see. A row pivot _groups_ the dataset by the unique values of each column used as a row pivot - a close analogue in SQL would be the `GROUP BY` statement. -The underlying dataset is aggregated to show the values belonging to -each group, and a total row is calculated for each group, showing the currently -selected aggregated value (e.g. `sum`) of the column. Row pivots are useful for +The underlying dataset is aggregated to show the values belonging to each group, +and a total row is calculated for each group, showing the currently selected +aggregated value (e.g. `sum`) of the column. Row pivots are useful for hierarchies, categorizing data and attributing values, i.e. showing the number of units sold based on State and City. @@ -335,19 +335,22 @@ In Perspective, row pivots are represented as an array of string column names which will be applied as row pivots: ```javascript -const view = await table.view({row_pivots: ["a", "c"]}); +const view = await table.view({ row_pivots: ["a", "c"] }); ``` -Pivots are applied in the order provided -by the user, which allows for "drilling down" into data; for example, a row -pivot of `["State", "City", "Neighborhood"]` shows the values for each -neighborhood, which is grouped by city, which is in turn grouped by state. This -allows for extremely fine-grained analysis applied over a large dataset. +Pivots are applied in the order provided by the user, which allows for "drilling +down" into data; for example, a row pivot of `["State", "City", "Neighborhood"]` +shows the values for each neighborhood, which is grouped by city, which is in +turn grouped by state. This allows for extremely fine-grained analysis applied +over a large dataset. #### Example ```html - + ```
@@ -360,15 +363,15 @@ allows for extremely fine-grained analysis applied over a large dataset. A column pivot _splits_ the dataset by the unique values of each column used as a column pivot. The underlying dataset is not aggregated, and a new column is created for each unique value of the column pivot. Each newly created column -contains the parts of the dataset that correspond to the column header, i.e. -a `View` that has `["State"]` as its column pivot will have a new column for -each state. +contains the parts of the dataset that correspond to the column header, i.e. a +`View` that has `["State"]` as its column pivot will have a new column for each +state. In Perspective, column pivots are represented as an array of string column names which will be applied as column pivots: ```javascript -const view = await table.view({column_pivots: ["a", "c"]}); +const view = await table.view({ column_pivots: ["a", "c"] }); ``` Row pivots and column pivots can be used in conjunction to categorize and @@ -379,9 +382,10 @@ column pivots are also easily transposable in `perspective-viewer`. ```html + row-pivots='["Category"]' + column-pivots='["Region"]' + columns='["Sales", "Profit"]' +> ```
@@ -400,15 +404,15 @@ aggregates based on column type: - "count" for all other columns Perspective provides a large selection of aggregate functions that can be -applied to columns in the `View` constructor using a dictionary of column -name to aggregate function name: +applied to columns in the `View` constructor using a dictionary of column name +to aggregate function name: ```javascript const view = await table.view({ - aggregates: { - a: "avg", - b: "distinct count" - } + aggregates: { + a: "avg", + b: "distinct count", + }, }); ``` @@ -416,9 +420,10 @@ const view = await table.view({ ```html + aggregates='{"Sales": "avg", "Profit", "median"}' + row-pivots='["State", "City"]' + columns='["Sales", "Profit"]' +> ```
@@ -437,14 +442,16 @@ the `View` constructor: ```javascript const view = await table.view({ - columns: ["a"] + columns: ["a"], }); ``` #### Example ```html - + ```
@@ -455,22 +462,25 @@ const view = await table.view({ ### Sort The `sort` property specifies columns on which the query should be sorted, -analogous to `ORDER BY` in SQL. A column can be sorted regardless of its -data type, and sorts can be applied in ascending or descending order. +analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data +type, and sorts can be applied in ascending or descending order. Perspective represents `sort` as an array of arrays, with the values of each inner array being a string column name and a string sort direction: ```javascript const view = await table.view({ - sort: [["a", "asc"]] + sort: [["a", "asc"]], }); ``` #### Example ```html - + ```
@@ -481,10 +491,10 @@ const view = await table.view({ ### Filter The `filter` property specifies columns on which the query can be filtered, -returning rows that pass the specified filter condition. This is analogous -to the `WHERE` clause in SQL. There is no limit on the number of columns -where `filter` is applied, but the resulting dataset is one that passes -all the filter conditions, i.e. the filters are joined with an `AND` condition. +returning rows that pass the specified filter condition. This is analogous to +the `WHERE` clause in SQL. There is no limit on the number of columns where +`filter` is applied, but the resulting dataset is one that passes all the filter +conditions, i.e. the filters are joined with an `AND` condition. Perspective represents `filter` as an array of arrays, with the values of each inner array being a string column name, a string filter operator, and a filter @@ -492,17 +502,19 @@ operand in the type of the column: ```javascript const view = await table.view({ - filter: [["a", "<", 100]] + filter: [["a", "<", 100]], }); ``` #### Example -Note: Use the `filters` attribute on `` instead of -`filter`. +Note: Use the `filters` attribute on `` instead of `filter`. ```html - + ```
diff --git a/docs/md/development.md b/docs/md/development.md index 2bc8a85608..2dc757354b 100644 --- a/docs/md/development.md +++ b/docs/md/development.md @@ -24,14 +24,19 @@ automatically call the correct build and test tools. ### System Dependencies -`Perspective.js` and `perspective-python` **require** the following system dependencies to be installed: +`Perspective.js` and `perspective-python` **require** the following system +dependencies to be installed: - [CMake](https://cmake.org/) (version 3.15.4 or higher) -- [Boost](https://www.boost.org/) (version 1.67 or higher, must be built - not header-only) +- [Boost](https://www.boost.org/) (version 1.67 or higher, must be built - not + header-only) - [Flatbuffers](https://google.github.io/flatbuffers/flatbuffers_guide_building.html) + ## Build -Make sure you have the system dependencies installed. For specifics depending on your OS, check the [system-specific instructions](#system-specific-instructions) below. +Make sure you have the system dependencies installed. For specifics depending on +your OS, check the [system-specific instructions](#system-specific-instructions) +below. To run a build, use @@ -65,8 +70,8 @@ dependencies by running `yarn`. #### Building via local EMSDK -To build using an Emscripten install on your local system and not the -Emscripten bundled with Perspective in its `package.json`, +To build using an Emscripten install on your local system and not the Emscripten +bundled with Perspective in its `package.json`, [install](https://emscripten.org/docs/getting_started/downloads.html) the Emscripten SDK, then activate and export the latest `emsdk` environment via [`emsdk_env.sh`](https://github.com/juj/emsdk): @@ -90,8 +95,7 @@ To install this specific version of Emscripten: ### `perspective-jupyterlab` To install the Jupyterlab plugin from your local working directory, give -`jupyter labextension install` the path to the `perspective-jupyterlab` -package: +`jupyter labextension install` the path to the `perspective-jupyterlab` package: ```bash jupyter labextension install ./packages/perspective-jupyterlab @@ -101,19 +105,20 @@ Afterwards, you should see it listed as a "local extension" when you run `jupyter labextension list`. Because we do not inline Perspective into the Jupyterlab plugin, your local -changes will not show up in Jupyterlab **unless** you use `yarn link` -according to the directions below: +changes will not show up in Jupyterlab **unless** you use `yarn link` according +to the directions below: 1. Ensure that your Jupyterlab is built by running `jupyter lab build`. -2. Inside each directory in `packages`, run [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link). -This will create a symlink to your local build that we will use inside Jupyterlab. +2. Inside each directory in `packages`, run + [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link). This will create + a symlink to your local build that we will use inside Jupyterlab. 3. From the Perspective root, run `yarn jlab_link`. This is a script that will -find your Jupyterlab installation and tell Jupyterlab to use these symlinks -when it looks for Perspective packages, instead of fetching them from NPM. + find your Jupyterlab installation and tell Jupyterlab to use these symlinks + when it looks for Perspective packages, instead of fetching them from NPM. 4. When you make a local change, make sure you run `yarn build` **and** -`jupyter lab build` so that it fetches the newest changes. + `jupyter lab build` so that it fetches the newest changes. 5. Whenever you run `jupyter lab clean`, you will need to run `yarn jlab_link` -again to re-register the symlinks. + again to re-register the symlinks. --- @@ -136,6 +141,7 @@ environments are provided for the Python libraries. To build Perspective using Docker, select the option in `yarn setup`. --- + ## System-Specific Instructions ### MacOS/OSX @@ -149,7 +155,8 @@ brew install flatbuffers ``` On M1 (Apple Silicon) systems, make sure your brew-installed dependencies are in -`/opt/homebrew` (the default location), and that `/opt/homebrew/bin` is on the `PATH`. +`/opt/homebrew` (the default location), and that `/opt/homebrew/bin` is on the +`PATH`. ### Windows 10 @@ -261,8 +268,8 @@ Verbosity in the tests can be enabled with the `--verbose` flag. ### Troubleshooting installation from source -If you are installing from a source distribution (sdist), make sure you have -the [System Dependencies](#system-dependencies) installed. +If you are installing from a source distribution (sdist), make sure you have the +[System Dependencies](#system-dependencies) installed. Try installing in verbose mode: @@ -275,11 +282,12 @@ The most common culprits are: - CMake version too old - Boost headers are missing or too old - Flatbuffers not installed prior to installing Perspective + #### Timezones in Python Tests -Python tests are configured to use the `UTC` time zone. If running tests locally, -you might find that datetime-related tests fail to assert the correct values. To -correct this, run tests with the `TZ=UTC`, i.e. +Python tests are configured to use the `UTC` time zone. If running tests +locally, you might find that datetime-related tests fail to assert the correct +values. To correct this, run tests with the `TZ=UTC`, i.e. ```bash TZ=UTC yarn test --verbose diff --git a/docs/md/installation.md b/docs/md/installation.md index a810cea1a8..d2023da3f7 100644 --- a/docs/md/installation.md +++ b/docs/md/installation.md @@ -24,12 +24,12 @@ $ yarn add @finos/perspective-viewer @finos/perspective-viewer-d3fc @finos/persp All uses of Perspective from NPM require the browser to have access to Perspective's `.worker.*.js` and `.wasm` assets _in addition_ to the bundled -`.js` scripts. By default, Perspective [inlines](https://github.com/finos/perspective/pull/870) -these assets into the `.js` scripts, and delivers them in one file. This has no -performance impact, but does increase asset load time. Any non-trivial application -should make use of `@finos/perspective-webpack-plugin`, which automatically -splits the assets into separate files and downloads them when the bundling -step runs. +`.js` scripts. By default, Perspective +[inlines](https://github.com/finos/perspective/pull/870) these assets into the +`.js` scripts, and delivers them in one file. This has no performance impact, +but does increase asset load time. Any non-trivial application should make use +of `@finos/perspective-webpack-plugin`, which automatically splits the assets +into separate files and downloads them when the bundling step runs. ### Webpack Plugin @@ -42,12 +42,12 @@ Perspective's additional assets, and is easy to set up in your `webpack.config`: const PerspectivePlugin = require("@finos/perspective-webpack-plugin"); module.exports = { - entry: "./in.js", - output: { - filename: "out.js", - path: "build" - }, - plugins: [new PerspectivePlugin()] + entry: "./in.js", + output: { + filename: "out.js", + path: "build", + }, + plugins: [new PerspectivePlugin()], }; ``` @@ -55,9 +55,10 @@ module.exports = { Perspective can be loaded directly from [unpkg.com](https://unpkg.com/@finos/perspective-viewer), which is the easiest -way to get started with Perspective in the browser, and absolutely perfect -for spinning up quick instances of `perspective-viewer`. An example is -demonstrated in [`superstore-arrow.html`](https://github.com/finos/perspective/blob/master/examples/simple/superstore-arrow.html), +way to get started with Perspective in the browser, and absolutely perfect for +spinning up quick instances of `perspective-viewer`. An example is demonstrated +in +[`superstore-arrow.html`](https://github.com/finos/perspective/blob/master/examples/simple/superstore-arrow.html), which loads a dataset stored in the Apache Arrow format using the `Fetch` API. Add these scripts to your `.html`'s `` section: @@ -74,19 +75,20 @@ Once added to your page, you can access the Javascript API through the ```javascript const worker = perspective.worker(); -const table = await worker.table({A: [1, 2, 3]}); -const view = await table.view({sort: [["A", "desc"]]}); +const table = await worker.table({ A: [1, 2, 3] }); +const view = await table.view({ sort: [["A", "desc"]] }); ``` Or create a `` in HTML: ```html -` +` -The `Table` is Perspective's columnar data frame, analogous to a -Pandas `DataFrame` or Apache Arrow. `Table` supports appending data, in-place -updates, removal by index, and notifications on update. +The `Table` is Perspective's columnar data frame, analogous to a Pandas +`DataFrame` or Apache Arrow. `Table` supports appending data, in-place updates, +removal by index, and notifications on update. A `Table` contains columns, each of which have a unique name, are strongly and consistently typed, and contains rows of data conforming to the column's type. -Each column in a `Table` must have the same number of rows, though not every -row must contain data; null-values are used to indicate missing values in the +Each column in a `Table` must have the same number of rows, though not every row +must contain data; null-values are used to indicate missing values in the dataset. The columns of a `Table` are _immutable after creation_, which means their names and data types cannot be changed after the `Table` has been created. Columns -cannot be added or deleted after creation, but a `View` can be used to select -an arbitrary set of columns from the `Table`. +cannot be added or deleted after creation, but a `View` can be used to select an +arbitrary set of columns from the `Table`. ```javascript var data = [ - {x: 1, y: "a", z: true}, - {x: 2, y: "b", z: false}, - {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + { x: 1, y: "a", z: true }, + { x: 2, y: "b", z: false }, + { x: 3, y: "c", z: true }, + { x: 4, y: "d", z: false }, ]; const table1 = await worker.table(data); @@ -46,16 +46,16 @@ table1 = perspective.Table(data) ## Schema and Types The mapping of a `Table`'s column names to data types is referred to as a -`schema`. Each column has a unique name and a single data type, and -data types are expressed with a common vocabulary of across all supported host -languages. In Python, you may alternatively use native types over their String +`schema`. Each column has a unique name and a single data type, and data types +are expressed with a common vocabulary of across all supported host languages. +In Python, you may alternatively use native types over their String counterparts: ```javascript var schema = { - x: "integer", - y: "string", - z: "boolean" + x: "integer", + y: "string", + z: "boolean", }; const table2 = await worker.table(schema); @@ -76,29 +76,30 @@ schema = { table2 = perspective.Table(schema) ``` -When passing data directly to the `table()` constructor, the type of each -column is inferred automatically. In some cases, the inference algorithm may -not return exactly what you'd like. For example, a column may be interpreted -as a `datetime` when you intended it to be a `string`, or a column may have no -values at all (yet), as it will be updated with values from a real-time data -source later on. In these cases, create a `table()` with a _schema_. +When passing data directly to the `table()` constructor, the type of each column +is inferred automatically. In some cases, the inference algorithm may not return +exactly what you'd like. For example, a column may be interpreted as a +`datetime` when you intended it to be a `string`, or a column may have no values +at all (yet), as it will be updated with values from a real-time data source +later on. In these cases, create a `table()` with a _schema_. Once the `Table` has been created with a schema, further `update()` calls will -conver data types to conform with the schema; a column that is typed as a -`datetime`, for example, can be updated -with `date` objects, `datetime` objects, `pandas.Timestamp`, `numpy.datetime64`, -and even valid millisecond/seconds from epoch timestamps. Similarly, updating -string columns with integer data will cause a cast to string, updating floats -with ints will cast to float, and etc. Type conversion can also leverage Python -converters, such as `__int__`, `__float__`, etc. +conver data types to conform with the schema; a column that is typed as a +`datetime`, for example, can be updated with `date` objects, `datetime` objects, +`pandas.Timestamp`, `numpy.datetime64`, and even valid millisecond/seconds from +epoch timestamps. Similarly, updating string columns with integer data will +cause a cast to string, updating floats with ints will cast to float, and etc. +Type conversion can also leverage Python converters, such as `__int__`, +`__float__`, etc. ## Index and Limit Initializing a `Table` with an `index` tells Perspective to treat a column as -the primary key, allowing in-place updates of rows. Only a single column -(of any type) can be used as an `index`. Indexed `Table` instances allow: +the primary key, allowing in-place updates of rows. Only a single column (of any +type) can be used as an `index`. Indexed `Table` instances allow: -- In-place _updates_ whenever a new row shares an `index` values with an existing row +- In-place _updates_ whenever a new row shares an `index` values with an + existing row - _Partial updates_ when such a row leaves some column values `undefined` - _Removes_ to delete a row by `index`. @@ -106,7 +107,7 @@ To create an indexed `Table`, provide the `index` property with a string column name to be used as an index: ```javascript -const indexed_table = await perspective.table(data, {index: "a"}); +const indexed_table = await perspective.table(data, { index: "a" }); ``` ```python @@ -114,13 +115,13 @@ indexed_table = perspective.Table(data, index="a"); ``` Initializing a `Table` with a `limit` sets the total number of rows the `Table` -is allowed to have. When the `Table` is updated, and the resulting size of -the `Table` would exceed its `limit`, rows that exceed `limit` overwrite the -oldest rows in the `Table`. To create a `Table` with a `limit`, provide the -`limit` property with an integer indicating the maximum rows: +is allowed to have. When the `Table` is updated, and the resulting size of the +`Table` would exceed its `limit`, rows that exceed `limit` overwrite the oldest +rows in the `Table`. To create a `Table` with a `limit`, provide the `limit` +property with an integer indicating the maximum rows: ```javascript -const limit_table = await perspective.table(data, {limit: 1000}); +const limit_table = await perspective.table(data, { limit: 1000 }); ``` ```python @@ -132,13 +133,13 @@ limit_table = perspective.Table(data, limit=1000); ## Update and Remove Once a `Table` has been created, it can be updated with new data conforming to -the `Table`'s `schema`. The dataset used for `update()` must conform with the +the `Table`'s `schema`. The dataset used for `update()` must conform with the formats supported by Perspective. ```javascript const schema = { - a: "integer", - b: "float" + a: "integer", + b: "float", }; const table = await perspective.table(schema); @@ -153,11 +154,12 @@ table.update(new_data) ``` Without an `index` set, calls to `update()` _append_ new data to the end of the -`Table`. Otherwise, Perspective allows [_partial updates_ (in-place)](#index-and-limit) -using the `index` to determine which rows to update: +`Table`. Otherwise, Perspective allows +[_partial updates_ (in-place)](#index-and-limit) using the `index` to determine +which rows to update: ```javascript -indexed_table.update({id: [1, 4], name: ["x", "y"]}); +indexed_table.update({ id: [1, 4], name: ["x", "y"] }); ``` ```python @@ -165,15 +167,15 @@ indexed_table.update({"id": [1, 4], "name": ["x", "y"]}) ``` Any value on a `table()` can be unset using the value `null` (Javascript) or -`None` (Python). Values may be unset on construction, as any `null` in the -dataset will be treated as an unset value, and can be explicitly unset -via `update()` on a `table()` with `index` applied. `update()` calls do not -need values for _all columns_ in the `table()` schema; -Missing keys (or keys with values set to `undefined` in Javascript), will be -omitted from `table()`s with `index` set, and become `null`: +`None` (Python). Values may be unset on construction, as any `null` in the +dataset will be treated as an unset value, and can be explicitly unset via +`update()` on a `table()` with `index` applied. `update()` calls do not need +values for _all columns_ in the `table()` schema; Missing keys (or keys with +values set to `undefined` in Javascript), will be omitted from `table()`s with +`index` set, and become `null`: ```javascript -table.update([{x: 3, y: null}]); // `z` missing +table.update([{ x: 3, y: null }]); // `z` missing ``` ```python @@ -190,10 +192,10 @@ indexed_table.remove([1, 4]); indexed_table.remove([1, 4]) ``` -Calling `clear()` will remove all data from the underlying -`Table`. Calling `replace(data)` with new data will clear the `Table`, and -update it with a new dataset that conforms to Perspective's data types and the -existing schema on the `Table`. +Calling `clear()` will remove all data from the underlying `Table`. Calling +`replace(data)` with new data will clear the `Table`, and update it with a new +dataset that conforms to Perspective's data types and the existing schema on the +`Table`. ```javascript table.clear(); diff --git a/docs/md/view.md b/docs/md/view.md index 4a60840d49..467856f82f 100644 --- a/docs/md/view.md +++ b/docs/md/view.md @@ -13,11 +13,11 @@ instance via the `view()` method with a set of ```javascript const table = await perspective.table({ - id: [1, 2, 3, 4], - name: ["a", "b", "c", "d"] + id: [1, 2, 3, 4], + name: ["a", "b", "c", "d"], }); -const view = await table.view({columns: ["name"]}); +const view = await table.view({ columns: ["name"] }); const json = await view.to_json(); view.delete(); @@ -34,32 +34,31 @@ arrow = view.to_arrow(); ``` `View` objects are immutable with respect to the arguments provided to the -`view()` method; to change these parameters, you must create a new `View` on -the same `Table`. However, each `View` is _live_ with respect to the `Table`'s -data, and will (within a conflation window) update with the latest state as its -parent `Table` updates, including incrementally recalculating all -aggregates, pivots, filters, etc. `View` query parameters are composable, -in that each parameter works independently _and_ in conjunction -with each other, and there is no limit to the number of pivots, filters, etc. -which can be applied. +`view()` method; to change these parameters, you must create a new `View` on the +same `Table`. However, each `View` is _live_ with respect to the `Table`'s data, +and will (within a conflation window) update with the latest state as its parent +`Table` updates, including incrementally recalculating all aggregates, pivots, +filters, etc. `View` query parameters are composable, in that each parameter +works independently _and_ in conjunction with each other, and there is no limit +to the number of pivots, filters, etc. which can be applied. > Examples in this section are live — play around with each -`` instance to see how different query parameters affect -what you see! +> `` instance to see how different query parameters affect +> what you see! ### Querying data with `view()` To query the table, create a `view()` on the table instance with an optional -configuration object. A `table()` can have as many `view()`s associated with -it as you need - Perspective conserves memory by relying on a single `table()` -to power multiple `view()`s concurrently: +configuration object. A `table()` can have as many `view()`s associated with it +as you need - Perspective conserves memory by relying on a single `table()` to +power multiple `view()`s concurrently: ```javascript const view = await table.view({ - columns: ["Sales"], - aggregates: {Sales: "sum"}, - row_pivot: ["Region", "Country"], - filter: [["Category", "in", ["Furniture", "Technology"]]] + columns: ["Sales"], + aggregates: { Sales: "sum" }, + row_pivot: ["Region", "Country"], + filter: [["Category", "in", ["Furniture", "Technology"]]], }); ``` @@ -73,22 +72,22 @@ view = table.view( ``` See the [View API documentation](/docs/obj/perspective.html) for more details. + ## Row Pivots A row pivot _groups_ the dataset by the unique values of each column used as a -row pivot - a close analogue in SQL to the `GROUP BY` statement. The underlying +row pivot - a close analogue in SQL to the `GROUP BY` statement. The underlying dataset is aggregated to show the values belonging to each group, and a total row is calculated for each group, showing the currently selected aggregated -value (e.g. `sum`) of the column. Row pivots are useful for -hierarchies, categorizing data and attributing values, i.e. showing the number -of units sold based on State and City. In Perspective, row pivots are -represented as an array of string column names to pivot, are applied in the -order provided; For example, a row -pivot of `["State", "City", "Neighborhood"]` shows the values for each +value (e.g. `sum`) of the column. Row pivots are useful for hierarchies, +categorizing data and attributing values, i.e. showing the number of units sold +based on State and City. In Perspective, row pivots are represented as an array +of string column names to pivot, are applied in the order provided; For example, +a row pivot of `["State", "City", "Neighborhood"]` shows the values for each neighborhood, which are grouped by City, which are in turn grouped by State. ```javascript -const view = await table.view({row_pivots: ["a", "c"]}); +const view = await table.view({ row_pivots: ["a", "c"] }); ``` ```python @@ -99,8 +98,9 @@ view = table.view(row_pivots=["a", "c"]) ```html + row-pivots='["State", "City"]' + columns='["Sales", "Profit"]' +> ``` @@ -114,13 +114,13 @@ view = table.view(row_pivots=["a", "c"]) A column pivot _splits_ the dataset by the unique values of each column used as a column pivot. The underlying dataset is not aggregated, and a new column is created for each unique value of the column pivot. Each newly created column -contains the parts of the dataset that correspond to the column header, i.e. -a `View` that has `["State"]` as its column pivot will have a new column for -each state. In Perspective, column pivots are represented as an array of string +contains the parts of the dataset that correspond to the column header, i.e. a +`View` that has `["State"]` as its column pivot will have a new column for each +state. In Perspective, column pivots are represented as an array of string column names to pivot: ```javascript -const view = await table.view({column_pivots: ["a", "c"]}); +const view = await table.view({ column_pivots: ["a", "c"] }); ``` ```python @@ -131,9 +131,10 @@ view = table.view(column_pivots=["a", "c"]) ```html + row-pivots='["Category"]' + column-pivots='["Region"]' + columns='["Sales", "Profit"]' +> ``` @@ -152,16 +153,16 @@ aggregates based on column type: - "sum" for `integer` and `float` columns - "count" for all other columns -Perspective provides a selection of aggregate functions that can be -applied to columns in the `View` constructor using a dictionary of column -name to aggregate function name: +Perspective provides a selection of aggregate functions that can be applied to +columns in the `View` constructor using a dictionary of column name to aggregate +function name: ```javascript const view = await table.view({ - aggregates: { - a: "avg", - b: "distinct count" - } + aggregates: { + a: "avg", + b: "distinct count", + }, }); ``` @@ -178,9 +179,10 @@ view = table.view( ```html + aggregates='{"Sales": "avg", "Profit", "median"}' + row-pivots='["State", "City"]' + columns='["Sales", "Profit"]' +> ``` @@ -193,12 +195,12 @@ view = table.view( The `columns` property specifies which columns should be included in the `View`'s output. This allows users to show or hide a specific subset of columns, -as well as control the order in which columns appear to the user. This is +as well as control the order in which columns appear to the user. This is represented in Perspective as an array of string column names: ```javascript const view = await table.view({ - columns: ["a"] + columns: ["a"], }); ``` @@ -209,8 +211,7 @@ view = table.view(columns=["a"]) #### Example ```html - + ``` @@ -222,16 +223,16 @@ view = table.view(columns=["a"]) ## Sort The `sort` property specifies columns on which the query should be sorted, -analogous to `ORDER BY` in SQL. A column can be sorted regardless of its -data type, and sorts can be applied in ascending or descending order. -Perspective represents `sort` as an array of arrays, with the values of each -inner array being a string column name and a string sort direction. -When `column-pivots` are applied, the additional sort directions `"col asc"` and -`"col desc"` will determine the order of pivot columns groups. +analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data +type, and sorts can be applied in ascending or descending order. Perspective +represents `sort` as an array of arrays, with the values of each inner array +being a string column name and a string sort direction. When `column-pivots` are +applied, the additional sort directions `"col asc"` and `"col desc"` will +determine the order of pivot columns groups. ```javascript const view = await table.view({ - sort: [["a", "asc"]] + sort: [["a", "asc"]], }); ``` @@ -242,9 +243,7 @@ view = table.view(sort=[["a", "asc"]]) #### Example ```html - + ``` @@ -256,10 +255,10 @@ view = table.view(sort=[["a", "asc"]]) ## Filter The `filter` property specifies columns on which the query can be filtered, -returning rows that pass the specified filter condition. This is analogous -to the `WHERE` clause in SQL. There is no limit on the number of columns -where `filter` is applied, but the resulting dataset is one that passes -all the filter conditions, i.e. the filters are joined with an `AND` condition. +returning rows that pass the specified filter condition. This is analogous to +the `WHERE` clause in SQL. There is no limit on the number of columns where +`filter` is applied, but the resulting dataset is one that passes all the filter +conditions, i.e. the filters are joined with an `AND` condition. Perspective represents `filter` as an array of arrays, with the values of each inner array being a string column name, a string filter operator, and a filter @@ -267,7 +266,7 @@ operand in the type of the column: ```javascript const view = await table.view({ - filter: [["a", "<", 100]] + filter: [["a", "<", 100]], }); ``` @@ -281,8 +280,9 @@ Use the `filters` attribute on `` instead of `filter`. ```html + columns='["Sales", "Profit"]' + filters='[["State", "==", "Texas"]]' +> ``` @@ -295,24 +295,25 @@ Use the `filters` attribute on `` instead of `filter`. The `expressions` attribute specifies _new_ columns in Perspective that are created using existing column values or arbitary scalar values defined within -the expression. In ``, expressions are added using the -"New Column" button in the side panel. +the expression. In ``, expressions are added using the "New +Column" button in the side panel. -A custom name can be added to an expression by making the first line a -comment: +A custom name can be added to an expression by making the first line a comment: ```javascript // new column -("Sales" * "Profit") - 15 +"Sales" * "Profit" - 15; ``` To add expressions using the API: + #### Example ```html + columns='["new expression"]' + expressions='["//new expression\n"\"Sales\" + \"Profit\" * 50 / sqrt(\"Sales\")"]' +> ``` @@ -323,20 +324,20 @@ To add expressions using the API: ## Flattening a `view()` into a `table()` -In Javascript, a `table()` can be constructed on a `view()` instance, which -will return a new `table()` based on the `view()`'s dataset, and all future -updates that affect the `view()` will be forwarded to the new `table()`. This -is particularly useful for implementing a +In Javascript, a `table()` can be constructed on a `view()` instance, which will +return a new `table()` based on the `view()`'s dataset, and all future updates +that affect the `view()` will be forwarded to the new `table()`. This is +particularly useful for implementing a [Client/Server Replicated](docs/md/server.html#clientserver-replicated) design, by serializing the `View` to an arrow and setting up an `on_update` callback: ```javascript const worker1 = perspective.worker(); const table = await worker.table(data); -const view = await table.view({filter: [["State", "==", "Texas"]]}); +const view = await table.view({ filter: [["State", "==", "Texas"]] }); const table2 = await worker.table(view); -table.update([{State: "Texas", City: "Austin"}]); +table.update([{ State: "Texas", City: "Austin" }]); ``` ```python @@ -350,4 +351,4 @@ def updater(port, delta): view.on_update(updater, mode="Row") table.update([{"State": "Texas", "City": "Austin"}]) -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index 42a3dfe3db..fc443799bc 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@rollup/plugin-node-resolve": "^11.1.1", "@rollup/plugin-typescript": "^8.2.5", "@types/ws": "^7.2.2", - "@typescript-eslint/eslint-plugin": "^2.4.0", - "@typescript-eslint/parser": "^2.4.0", + "@typescript-eslint/eslint-plugin": "^4.31.0", + "@typescript-eslint/parser": "^4.31.0", "arraybuffer-loader": "^1.0.2", "babel-eslint": "^10.0.3", "babel-jest": "^25.1.0", @@ -49,10 +49,10 @@ "cssnano": "^4.1.10", "cssnano-preset-lite": "^1.0.1", "dotenv": "^8.1.0", - "eslint": "^6.6.0", - "eslint-config-prettier": "^3.0.1", - "eslint-plugin-markdown": "^1.0.2", - "eslint-plugin-prettier": "^2.6.2", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-markdown": "^2.2.0", + "eslint-plugin-prettier": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^8.1.0", "html-loader": "^0.5.1", @@ -75,7 +75,7 @@ "postcss": "^8.2.6", "postcss-loader": "^4.1.0", "pre-commit": "^1.2.2", - "prettier": "^1.19.1", + "prettier": "^2.4.0", "puppeteer": "^10.2.0", "rimraf": "^2.6.2", "rollup": "^2.38.5", @@ -131,19 +131,17 @@ "test_python": "node scripts/test_python.js", "clean": "node scripts/clean.js", "start": "lerna run start --stream --scope", - "precommit": "npm run lint", - "lint": "npm-run-all lint:*", - "lint:eslint": "eslint \"packages/*/src/**/*.js\" \"packages/*/test/**/*.js\" \"examples/*/*.js\"", - "lint:typescript": "eslint \"packages/perspective-jupyterlab/src/ts/*.ts\"", - "lint:python": "node scripts/lint_python.js", + "precommit": "npm-run-all lint_js lint_python", + "lint_js": "npm-run-all lint:*", + "lint:eslint": "eslint \"scripts/*.js\" \"rust/**/*.ts\" \"rust/**/*.js\" \"packages/**/*.js\" \"cpp/**/*.js\"", + "lint_python": "node scripts/lint_python.js", "fix:es": "npm run lint:eslint -- --fix", "fix:md": "prettier docs/md/*.md --prose-wrap=always --write", "fix:cpp": "node scripts/fix_cpp.js", - "fix:less": "prettier --tab-width 4 --write packages/**/src/less/*.less", - "fix:html": "html-beautify packages/**/src/html/*.html -r", - "fix:json": "prettier --tab-width 4 --write **/package.json", + "fix:less": "prettier --tab-width 4 --write packages/**/less/*.less", + "fix:html": "prettier --tab-width 4 --write packages/**/html/*.html -r", + "fix:json": "prettier --tab-width 4 --write packages/**/package.json rust/**/package.json examples/**/package.json docs/package.json", "fix:python": "node scripts/lint_python.js --fix", - "fix:rust": "lerna run fix --scope=@finos/perspective-rust-internal", "fix": "npm-run-all --silent fix:*", "toggle_puppeteer": "node scripts/toggle_puppeteer.js", "version": "python3 python/perspective/scripts/write_version.py && git add python/perspective/perspective/core/_version.py", diff --git a/packages/perspective-bench/bench/perspective.benchmark.js b/packages/perspective-bench/bench/perspective.benchmark.js index 1bfcdc6edc..897e0d5e91 100644 --- a/packages/perspective-bench/bench/perspective.benchmark.js +++ b/packages/perspective-bench/bench/perspective.benchmark.js @@ -13,58 +13,165 @@ * */ -const CSV = "https://unpkg.com/@jpmorganchase/perspective-examples@0.2.0-beta.2/build/superstore.csv"; -const ARROW = "https://unpkg.com/@jpmorganchase/perspective-examples@0.2.0-beta.2/build/superstore.arrow"; - -const AGG_OPTIONS = [[{column: "Sales", op: "sum"}], [{column: "State", op: "dominant"}], [{column: "Order Date", op: "dominant"}]]; +const CSV = + "https://unpkg.com/@jpmorganchase/perspective-examples@0.2.0-beta.2/build/superstore.csv"; +const ARROW = + "https://unpkg.com/@jpmorganchase/perspective-examples@0.2.0-beta.2/build/superstore.arrow"; + +const AGG_OPTIONS = [ + [{column: "Sales", op: "sum"}], + [{column: "State", op: "dominant"}], + [{column: "Order Date", op: "dominant"}], +]; const COLUMN_PIVOT_OPTIONS = [[], ["Sub-Category"]]; const ROW_PIVOT_OPTIONS = [[], ["State"], ["State", "City"]]; -const COLUMN_TYPES = {Sales: "number", "Order Date": "datetime", State: "string"}; - -const COMPUTED_FUNCS = ["+", "-", "*", "/", "pow2", "sqrt", "uppercase", "concat_comma", "week_bucket"]; +const COLUMN_TYPES = { + Sales: "number", + "Order Date": "datetime", + State: "string", +}; -const COMPUTED_CONFIG = {computed_function_name: "+", column: "computed", inputs: ["Sales", "Profit"]}; +const COMPUTED_FUNCS = [ + "+", + "-", + "*", + "/", + "pow2", + "sqrt", + "uppercase", + "concat_comma", + "week_bucket", +]; + +const COMPUTED_CONFIG = { + computed_function_name: "+", + column: "computed", + inputs: ["Sales", "Profit"], +}; const COMPLEX_COMPUTED_CONFIGS = { numeric: [ - {computed_function_name: "*", column: "computed", inputs: ["Sales", "Profit"]}, - {computed_function_name: "sqrt", column: "computed2", inputs: ["computed"]}, - {computed_function_name: "+", column: "computed3", inputs: ["computed2", "Sales"]}, - {computed_function_name: "*", column: "computed4", inputs: ["Profit", "Quantity"]}, - {computed_function_name: "abs", column: "computed5", inputs: ["computed4"]}, - {computed_function_name: "-", column: "computed6", inputs: ["computed5", "computed"]} + { + computed_function_name: "*", + column: "computed", + inputs: ["Sales", "Profit"], + }, + { + computed_function_name: "sqrt", + column: "computed2", + inputs: ["computed"], + }, + { + computed_function_name: "+", + column: "computed3", + inputs: ["computed2", "Sales"], + }, + { + computed_function_name: "*", + column: "computed4", + inputs: ["Profit", "Quantity"], + }, + { + computed_function_name: "abs", + column: "computed5", + inputs: ["computed4"], + }, + { + computed_function_name: "-", + column: "computed6", + inputs: ["computed5", "computed"], + }, ], string: [ - {computed_function_name: "uppercase", column: "computed", inputs: ["City"]}, - {computed_function_name: "lowercase", column: "computed2", inputs: ["Customer ID"]}, - {computed_function_name: "lowercase", column: "computed3", inputs: ["Order ID"]}, - {computed_function_name: "is", column: "computed4", inputs: ["computed", "computed2"]}, - {computed_function_name: "concat_comma", column: "computed5", inputs: ["computed2", "computed3"]}, - {computed_function_name: "concat_space", column: "computed6", inputs: ["State", "City"]} + { + computed_function_name: "uppercase", + column: "computed", + inputs: ["City"], + }, + { + computed_function_name: "lowercase", + column: "computed2", + inputs: ["Customer ID"], + }, + { + computed_function_name: "lowercase", + column: "computed3", + inputs: ["Order ID"], + }, + { + computed_function_name: "is", + column: "computed4", + inputs: ["computed", "computed2"], + }, + { + computed_function_name: "concat_comma", + column: "computed5", + inputs: ["computed2", "computed3"], + }, + { + computed_function_name: "concat_space", + column: "computed6", + inputs: ["State", "City"], + }, ], datetime: [ - {computed_function_name: "day_bucket", column: "computed", inputs: ["Order Date"]}, - {computed_function_name: "second_bucket", column: "computed2", inputs: ["Order Date"]}, - {computed_function_name: "day_of_week", column: "computed3", inputs: ["Order Date"]}, - {computed_function_name: "month_of_year", column: "computed4", inputs: ["Ship Date"]}, - {computed_function_name: "year_bucket", column: "computed5", inputs: ["Ship Date"]}, - {computed_function_name: "minute_bucket", column: "computed6", inputs: ["Ship Date"]} - ] + { + computed_function_name: "day_bucket", + column: "computed", + inputs: ["Order Date"], + }, + { + computed_function_name: "second_bucket", + column: "computed2", + inputs: ["Order Date"], + }, + { + computed_function_name: "day_of_week", + column: "computed3", + inputs: ["Order Date"], + }, + { + computed_function_name: "month_of_year", + column: "computed4", + inputs: ["Ship Date"], + }, + { + computed_function_name: "year_bucket", + column: "computed5", + inputs: ["Ship Date"], + }, + { + computed_function_name: "minute_bucket", + column: "computed6", + inputs: ["Ship Date"], + }, + ], }; const SIMPLE_EXPRESSION = ['// computed \n "Sales" + "Profit"']; const COMPLEX_EXPRESSIONS = { - numeric: ['sqrt("Sales" * "Profit") + "Sales"', 'abs("Profit" * "Quantity")', 'abs("Profit" * "Quantity") - (sqrt("Sales" * "Profit") + "Sales")'], + numeric: [ + 'sqrt("Sales" * "Profit") + "Sales"', + 'abs("Profit" * "Quantity")', + 'abs("Profit" * "Quantity") - (sqrt("Sales" * "Profit") + "Sales")', + ], string: [ 'upper("City")', 'lower("Customer ID")', 'lower("Order ID")', 'upper("City") == lower("Customer ID")', `concat(lower("Customer ID"), ', ', lower("Order ID"))`, - `concat("State", ' ', "City")` + `concat("State", ' ', "City")`, + ], + datetime: [ + `bucket("Order Date", 'D')`, + `bucket("Order Date", 's')`, + `day_of_week("Order Date")`, + `month_of_year("Ship Date")`, + `bucket("Ship Date", 'Y')`, + `bucket("Ship Date", 'm')`, ], - datetime: [`bucket("Order Date", 'D')`, `bucket("Order Date", 's')`, `day_of_week("Order Date")`, `month_of_year("Ship Date")`, `bucket("Ship Date", 'Y')`, `bucket("Ship Date", 'm')`] }; /****************************************************************************** @@ -103,7 +210,9 @@ describe("Table", async () => { describe("table", () => { benchmark(name, async () => { let test = data[name]; - table = await worker.table(test.slice ? test.slice() : test); + table = await worker.table( + test.slice ? test.slice() : test + ); await table.size(); }); }); @@ -135,7 +244,9 @@ describe("Update", async () => { let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await table.size(); } }); @@ -145,11 +256,15 @@ describe("Update", async () => { table = await worker.table(data.arrow.slice()); view = await table.view(); - const test_data = await static_view[`to_${name}`]({end_row: 500}); + const test_data = await static_view[`to_${name}`]({ + end_row: 500, + }); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -158,14 +273,18 @@ describe("Update", async () => { describe("ctx0 sorted", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ - sort: [["Customer Name", "desc"]] + sort: [["Customer Name", "desc"]], }); - const test_data = await static_view[`to_${name}`]({end_row: 500}); + const test_data = await static_view[`to_${name}`]({ + end_row: 500, + }); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -177,14 +296,18 @@ describe("Update", async () => { view = await table.view({ sort: [ ["Customer Name", "desc"], - ["Order Date", "asc"] - ] + ["Order Date", "asc"], + ], }); - const test_data = await static_view[`to_${name}`]({end_row: 500}); + const test_data = await static_view[`to_${name}`]({ + end_row: 500, + }); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -193,13 +316,15 @@ describe("Update", async () => { describe("ctx1", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ - row_pivots: ["State"] + row_pivots: ["State"], }); let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -208,12 +333,14 @@ describe("Update", async () => { describe("ctx1 deep", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ - row_pivots: ["State", "City"] + row_pivots: ["State", "City"], }); let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -223,12 +350,14 @@ describe("Update", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ row_pivots: ["State"], - column_pivots: ["Sub-Category"] + column_pivots: ["Sub-Category"], }); let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -238,12 +367,14 @@ describe("Update", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ row_pivots: ["State", "City"], - column_pivots: ["Sub-Category"] + column_pivots: ["Sub-Category"], }); let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -252,12 +383,14 @@ describe("Update", async () => { describe("ctx1.5", async () => { table = await worker.table(data.arrow.slice()); view = await table.view({ - column_pivots: ["Sub-Category"] + column_pivots: ["Sub-Category"], }); let test_data = await static_view[`to_${name}`]({end_row: 500}); benchmark(name, async () => { for (let i = 0; i < 5; i++) { - table.update(test_data.slice ? test_data.slice() : test_data); + table.update( + test_data.slice ? test_data.slice() : test_data + ); await view.num_rows(); } }); @@ -415,7 +548,7 @@ describe("View", async () => { aggregate, row_pivot, column_pivot, - sort: [["Sales", "asc"]] + sort: [["Sales", "asc"]], }); await view.schema(); }); @@ -425,7 +558,7 @@ describe("View", async () => { aggregate, row_pivot, column_pivot, - sort: [["Sales", "asc"]] + sort: [["Sales", "asc"]], }); await view.schema(); }); @@ -435,7 +568,7 @@ describe("View", async () => { aggregate, row_pivot, column_pivot, - sort: [["Customer Name", "asc"]] + sort: [["Customer Name", "asc"]], }); await view.schema(); }); @@ -445,7 +578,7 @@ describe("View", async () => { aggregate, row_pivot, column_pivot, - sort: [["Customer Name", "desc"]] + sort: [["Customer Name", "desc"]], }); await view.schema(); }); @@ -541,7 +674,7 @@ describe("Expression/Computed Column", async () => { benchmark(`sort computed: \`${computed_func}\``, async () => { let config = { - sort: [["computed", "desc"]] + sort: [["computed", "desc"]], }; if (table.validate_expressions) { @@ -569,23 +702,26 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`row pivot computed: \`${computed_func}\``, async () => { - let config = { - row_pivots: ["computed"] - }; - - if (table.validate_expressions) { - // New expressions API - config.expressions = SIMPLE_EXPRESSION; - } else { - // Old computed_columns API - config.computed_columns = [COMPUTED_CONFIG]; - } + benchmark( + `row pivot computed: \`${computed_func}\``, + async () => { + let config = { + row_pivots: ["computed"], + }; + + if (table.validate_expressions) { + // New expressions API + config.expressions = SIMPLE_EXPRESSION; + } else { + // Old computed_columns API + config.computed_columns = [COMPUTED_CONFIG]; + } - view = await table.view(config); + view = await table.view(config); - await table.size(); - }); + await table.size(); + } + ); }); describe("ctx2", async () => { @@ -599,24 +735,27 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`row and column pivot computed: \`${computed_func}\``, async () => { - let config = { - row_pivots: ["computed"], - column_pivots: ["computed"] - }; - - if (table.validate_expressions) { - // New expressions API - config.expressions = SIMPLE_EXPRESSION; - } else { - // Old computed_columns API - config.computed_columns = [COMPUTED_CONFIG]; - } + benchmark( + `row and column pivot computed: \`${computed_func}\``, + async () => { + let config = { + row_pivots: ["computed"], + column_pivots: ["computed"], + }; + + if (table.validate_expressions) { + // New expressions API + config.expressions = SIMPLE_EXPRESSION; + } else { + // Old computed_columns API + config.computed_columns = [COMPUTED_CONFIG]; + } - view = await table.view(config); + view = await table.view(config); - await table.size(); - }); + await table.size(); + } + ); }); describe("ctx1.5", async () => { @@ -630,23 +769,26 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`column pivot computed: \`${computed_func}\``, async () => { - let config = { - column_pivots: ["computed"] - }; - - if (table.validate_expressions) { - // New expressions API - config.expressions = SIMPLE_EXPRESSION; - } else { - // Old computed_columns API - config.computed_columns = [COMPUTED_CONFIG]; - } + benchmark( + `column pivot computed: \`${computed_func}\``, + async () => { + let config = { + column_pivots: ["computed"], + }; + + if (table.validate_expressions) { + // New expressions API + config.expressions = SIMPLE_EXPRESSION; + } else { + // Old computed_columns API + config.computed_columns = [COMPUTED_CONFIG]; + } - view = await table.view(config); + view = await table.view(config); - await table.size(); - }); + await table.size(); + } + ); }); }); } @@ -680,7 +822,8 @@ describe("Expression/Computed Column", async () => { config.expressions = COMPLEX_EXPRESSIONS[data_type]; } else { // Old computed_columns API - config.computed_columns = COMPLEX_COMPUTED_CONFIGS[data_type]; + config.computed_columns = + COMPLEX_COMPUTED_CONFIGS[data_type]; } view = await table.view(config); @@ -688,23 +831,29 @@ describe("Expression/Computed Column", async () => { await table.size(); }); - benchmark(`sort computed complex: \`${data_type}\``, async () => { - let config = {}; - - if (table.validate_expressions) { - // New expressions API - config.expressions = COMPLEX_EXPRESSIONS[data_type]; - config.sort = [[COMPLEX_EXPRESSIONS[data_type][0], "desc"]]; - } else { - // Old computed_columns API - config.computed_columns = COMPLEX_COMPUTED_CONFIGS[data_type]; - config.sort = [["computed", "desc"]]; - } + benchmark( + `sort computed complex: \`${data_type}\``, + async () => { + let config = {}; + + if (table.validate_expressions) { + // New expressions API + config.expressions = COMPLEX_EXPRESSIONS[data_type]; + config.sort = [ + [COMPLEX_EXPRESSIONS[data_type][0], "desc"], + ]; + } else { + // Old computed_columns API + config.computed_columns = + COMPLEX_COMPUTED_CONFIGS[data_type]; + config.sort = [["computed", "desc"]]; + } - view = await table.view(config); + view = await table.view(config); - await table.size(); - }); + await table.size(); + } + ); }); describe("ctx1", async () => { @@ -718,22 +867,28 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`row pivot computed complex: \`${data_type}\``, async () => { - let config = {}; + benchmark( + `row pivot computed complex: \`${data_type}\``, + async () => { + let config = {}; + + if (table.validate_expressions) { + // New expressions API + config.expressions = COMPLEX_EXPRESSIONS[data_type]; + config.row_pivots = [ + COMPLEX_EXPRESSIONS[data_type][0], + ]; + } else { + // Old computed_columns API + config.computed_columns = + COMPLEX_COMPUTED_CONFIGS[data_type]; + config.row_pivots = ["computed"]; + } - if (table.validate_expressions) { - // New expressions API - config.expressions = COMPLEX_EXPRESSIONS[data_type]; - config.row_pivots = [COMPLEX_EXPRESSIONS[data_type][0]]; - } else { - // Old computed_columns API - config.computed_columns = COMPLEX_COMPUTED_CONFIGS[data_type]; - config.row_pivots = ["computed"]; + view = await table.view(config); + await table.size(); } - - view = await table.view(config); - await table.size(); - }); + ); }); describe("ctx2", async () => { @@ -747,24 +902,32 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`row and column pivot computed complex: \`${data_type}\``, async () => { - let config = {}; + benchmark( + `row and column pivot computed complex: \`${data_type}\``, + async () => { + let config = {}; + + if (table.validate_expressions) { + // New expressions API + config.expressions = COMPLEX_EXPRESSIONS[data_type]; + config.row_pivots = [ + COMPLEX_EXPRESSIONS[data_type][0], + ]; + config.column_pivots = [ + COMPLEX_EXPRESSIONS[data_type][1], + ]; + } else { + // Old computed_columns API + config.computed_columns = + COMPLEX_COMPUTED_CONFIGS[data_type]; + config.row_pivots = ["computed"]; + config.column_pivots = ["computed2"]; + } - if (table.validate_expressions) { - // New expressions API - config.expressions = COMPLEX_EXPRESSIONS[data_type]; - config.row_pivots = [COMPLEX_EXPRESSIONS[data_type][0]]; - config.column_pivots = [COMPLEX_EXPRESSIONS[data_type][1]]; - } else { - // Old computed_columns API - config.computed_columns = COMPLEX_COMPUTED_CONFIGS[data_type]; - config.row_pivots = ["computed"]; - config.column_pivots = ["computed2"]; + view = await table.view(config); + await table.size(); } - - view = await table.view(config); - await table.size(); - }); + ); }); describe("ctx1.5", async () => { @@ -778,22 +941,28 @@ describe("Expression/Computed Column", async () => { table = await worker.table(data.arrow.slice()); - benchmark(`column pivot computed complex: \`${data_type}\``, async () => { - let config = {}; + benchmark( + `column pivot computed complex: \`${data_type}\``, + async () => { + let config = {}; + + if (table.validate_expressions) { + // New expressions API + config.expressions = COMPLEX_EXPRESSIONS[data_type]; + config.column_pivots = [ + COMPLEX_EXPRESSIONS[data_type][0], + ]; + } else { + // Old computed_columns API + config.computed_columns = + COMPLEX_COMPUTED_CONFIGS[data_type]; + config.column_pivots = ["computed"]; + } - if (table.validate_expressions) { - // New expressions API - config.expressions = COMPLEX_EXPRESSIONS[data_type]; - config.column_pivots = [COMPLEX_EXPRESSIONS[data_type][0]]; - } else { - // Old computed_columns API - config.computed_columns = COMPLEX_COMPUTED_CONFIGS[data_type]; - config.column_pivots = ["computed"]; + view = await table.view(config); + await table.size(); } - - view = await table.view(config); - await table.size(); - }); + ); }); }); } @@ -805,31 +974,34 @@ describe("Expression/Computed Column", async () => { * */ -const wait_for_perspective = () => new Promise(resolve => window.addEventListener("perspective-ready", resolve)); +const wait_for_perspective = () => + new Promise((resolve) => + window.addEventListener("perspective-ready", resolve) + ); function to_name({aggregate, row_pivot, column_pivot}) { const type = COLUMN_TYPES[aggregate[0].column]; const sides = { "00": "ctx0", - "10": "ctx1", - "20": "ctx1 deep", - "21": "ctx2 deep", - "11": "ctx2", - "01": "ctx1.5" + 10: "ctx1", + 20: "ctx1 deep", + 21: "ctx2 deep", + 11: "ctx2", + "01": "ctx1.5", }[row_pivot.length.toString() + column_pivot.length.toString()]; return [`${sides}`, type]; // return `${COLUMN_TYPES[aggregate[0].column]},${row_pivot.join("/") || "-"}*${column_pivot.join("/") || "-"}`; } -const load_dynamic_node = name => { - return new Promise(resolve => { +const load_dynamic_node = (name) => { + return new Promise((resolve) => { window.perspective = require("@finos/perspective"); resolve(); }); }; const load_dynamic_browser = (name, url) => { - return new Promise(resolve => { + return new Promise((resolve) => { const existingScript = document.getElementById(name); if (!existingScript) { const script = document.createElement("script"); @@ -872,7 +1044,10 @@ async function get_data_node(worker) { const fs = require("fs"); const path = require("path"); - const ARROW_FILE = path.resolve(__dirname, "../../../../examples/simple/superstore.arrow"); + const ARROW_FILE = path.resolve( + __dirname, + "../../../../examples/simple/superstore.arrow" + ); console.log("Loading Arrow"); const arrow = fs.readFileSync(ARROW_FILE, null).buffer; diff --git a/packages/perspective-bench/bench/versions.js b/packages/perspective-bench/bench/versions.js index bb786a869f..4e2db208f9 100644 --- a/packages/perspective-bench/bench/versions.js +++ b/packages/perspective-bench/bench/versions.js @@ -9,21 +9,49 @@ const PerspectiveBench = require("@finos/perspective-bench"); -const VERSIONS = ["0.8.3", "0.8.2", "0.8.1", "0.8.0", "0.7.0", "0.6.0", "0.5.6", "0.5.5", "0.5.4", "0.5.3", "0.5.2", "0.5.1", "0.5.0"]; +const VERSIONS = [ + "0.8.3", + "0.8.2", + "0.8.1", + "0.8.0", + "0.7.0", + "0.6.0", + "0.5.6", + "0.5.5", + "0.5.4", + "0.5.3", + "0.5.2", + "0.5.1", + "0.5.0", +]; async function run() { - await PerspectiveBench.run("master", "bench/perspective.benchmark.js", `http://${process.env.PSP_DOCKER_PUPPETEER ? `localhost` : `host.docker.internal`}:8080/perspective.js`, { - output: "dist/benchmark", - puppeteer: true - }); + await PerspectiveBench.run( + "master", + "bench/perspective.benchmark.js", + `http://${ + process.env.PSP_DOCKER_PUPPETEER + ? `localhost` + : `host.docker.internal` + }:8080/perspective.js`, + { + output: "dist/benchmark", + puppeteer: true, + } + ); for (const version of VERSIONS) { const url = `https://unpkg.com/@finos/perspective@${version}/dist/umd/perspective.js`; - await PerspectiveBench.run(version, "bench/perspective.benchmark.js", url, { - output: "dist/benchmark", - read: true, - puppeteer: true - }); + await PerspectiveBench.run( + version, + "bench/perspective.benchmark.js", + url, + { + output: "dist/benchmark", + read: true, + puppeteer: true, + } + ); } } diff --git a/packages/perspective-bench/src/html/benchmark.html b/packages/perspective-bench/src/html/benchmark.html index 0f49478048..d9ae7cdb2f 100644 --- a/packages/perspective-bench/src/html/benchmark.html +++ b/packages/perspective-bench/src/html/benchmark.html @@ -9,48 +9,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/packages/perspective-bench/src/js/bench.js b/packages/perspective-bench/src/js/bench.js index 91a47ff694..b4be00845e 100644 --- a/packages/perspective-bench/src/js/bench.js +++ b/packages/perspective-bench/src/js/bench.js @@ -18,13 +18,16 @@ const execSync = require("child_process").execSync; chalk.enabled = true; chalk.level = 1; -const BROWSER_RUNTIME = arg1 => +const BROWSER_RUNTIME = (arg1) => fs .readFileSync(path.join(__dirname, "browser_runtime.js")) .toString() .replace("__PLACEHOLDER__", arg1); -const to_url = (arg1, arg2) => ``; +const to_url = (arg1, arg2) => + ``; function color(string) { string = [string]; @@ -35,15 +38,17 @@ function color(string) { async function run_version(browser, args, run_test) { let page = await browser.newPage(); // TODO silence these - page.on("console", msg => { + page.on("console", (msg) => { if (msg.type() !== "warning") { console.log(color(msg.text())); } }); - page.on("pageerror", msg => console.log(` -> ${msg.message}`)); + page.on("pageerror", (msg) => console.log(` -> ${msg.message}`)); await page.setContent(to_url(JSON.stringify(args), run_test)); - let results = await page.evaluate(async () => await window.PerspectiveBench.run()); + let results = await page.evaluate( + async () => await window.PerspectiveBench.run() + ); await page.close(); return results; @@ -51,7 +56,7 @@ async function run_version(browser, args, run_test) { async function run_node_version(args, run_test) { const script = BROWSER_RUNTIME(JSON.stringify(args)); - console.warn = function() {}; + console.warn = function () {}; const old_log = console.log; console.log = (...args) => old_log(...args.map(color)); const js = eval(`"use strict";${script};${run_test};PerspectiveBench`); @@ -64,7 +69,9 @@ exports.run = async function run(version, benchmark, ...cmdArgs) { let benchmark_name = options.output || "benchmark"; - console.log(chalk`\n{whiteBright Running v.${version} (${cmdArgs.join(",")})}`); + console.log( + chalk`\n{whiteBright Running v.${version} (${cmdArgs.join(",")})}` + ); let version_index = 1; let table = undefined; @@ -95,8 +102,8 @@ exports.run = async function run(version, benchmark, ...cmdArgs) { '--proxy-server="direct://"', "--proxy-bypass-list=*", "--disable-web-security", - "--allow-file-access" - ] + "--allow-file-access", + ], }); execSync(`renice -n -20 ${browser.process().pid}`, {stdio: "inherit"}); @@ -105,11 +112,13 @@ exports.run = async function run(version, benchmark, ...cmdArgs) { await browser.close(); - console.log(`Benchmark suite has finished running - results are in ${benchmark_name}.html.`); + console.log( + `Benchmark suite has finished running - results are in ${benchmark_name}.html.` + ); } else { bins = await run_node_version(cmdArgs, RUN_TEST); } - bins = bins.map(result => ({...result, version, version_index})); + bins = bins.map((result) => ({...result, version, version_index})); version_index++; if (table === undefined) { table = await perspective.table(bins); @@ -119,18 +128,41 @@ exports.run = async function run(version, benchmark, ...cmdArgs) { const view = await table.view(); const arrow = await view.to_arrow(); view.delete(); - fs.writeFileSync(path.join(process.cwd(), `${benchmark_name}.arrow`), new Buffer(arrow), "binary"); - fs.writeFileSync(path.join(process.cwd(), `${benchmark_name}.html`), fs.readFileSync(path.join(__dirname, "..", "html", `benchmark.html`)).toString()); + fs.writeFileSync( + path.join(process.cwd(), `${benchmark_name}.arrow`), + new Buffer(arrow), + "binary" + ); + fs.writeFileSync( + path.join(process.cwd(), `${benchmark_name}.html`), + fs + .readFileSync(path.join(__dirname, "..", "html", `benchmark.html`)) + .toString() + ); }; exports.registerCmd = function registerCmd() { program - .version(JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json")).toString()).version) + .version( + JSON.parse( + fs + .readFileSync( + path.join(__dirname, "..", "..", "package.json") + ) + .toString() + ).version + ) .arguments("[suite] [...cmdArgs]") .description("Run the benchmark suite") - .option("-o, --output ", "Filename to write to, defaults to `benchmark`") + .option( + "-o, --output ", + "Filename to write to, defaults to `benchmark`" + ) .option("-r, --read", "Read from disk or overwrite") - .option("-p, --puppeteer", "Should run the suite in Puppeteer (Headless)") + .option( + "-p, --puppeteer", + "Should run the suite in Puppeteer (Headless)" + ) .action((...args) => { const options = args.splice(args.length - 1, 1)[0]; diff --git a/packages/perspective-bench/src/js/browser_runtime.js b/packages/perspective-bench/src/js/browser_runtime.js index e7c57895a6..219a75c56a 100644 --- a/packages/perspective-bench/src/js/browser_runtime.js +++ b/packages/perspective-bench/src/js/browser_runtime.js @@ -41,7 +41,7 @@ function quotechalk(x) { } function to_table(col_lengths, padding = 0) { - return function(...row) { + return function (...row) { try { let result = "".padStart(padding - 2, " "); for (let c = 0; c < row.length; ++c) { @@ -63,7 +63,8 @@ function to_table(col_lengths, padding = 0) { col_length = real_cell.length; col_lengths[c] = [method, col_length]; } - const cell_length = cell.length + Math.max(0, col_length - real_cell.length); + const cell_length = + cell.length + Math.max(0, col_length - real_cell.length); result += " " + cell[method].call(cell, cell_length, " "); } console.log(result); @@ -76,7 +77,17 @@ function to_table(col_lengths, padding = 0) { let PRINTER, LAST_PATH; class Benchmark { - constructor(desc, path, body, indent, categories, after_each, iterations, timeout, toss) { + constructor( + desc, + path, + body, + indent, + categories, + after_each, + iterations, + timeout, + toss + ) { this._desc = quotechalk(desc); this._body = body; this._path = path.slice(0, path.length - 2); @@ -93,12 +104,14 @@ class Benchmark { PRINTER || to_table( [ - ...(OUTPUT_MODE === "tree" ? [...path.map(() => undefined), undefined] : []), + ...(OUTPUT_MODE === "tree" + ? [...path.map(() => undefined), undefined] + : []), ["padStart", iter_length], ["padStart", per_length], ["padStart", undefined], ["padStart", undefined], - ["padStart", undefined] + ["padStart", undefined], ], OUTPUT_MODE === "grouped" ? (indent - 1) * INDENT_LEVEL : " " ); @@ -107,14 +120,31 @@ class Benchmark { log(reps, totals) { const t = totals.reduce((x, y) => x + y); const mean = t / reps; - const stddev = Math.sqrt(totals.map(x => Math.pow(x - mean, 2)).reduce((x, y) => x + y) / reps) / mean; + const stddev = + Math.sqrt( + totals + .map((x) => Math.pow(x - mean, 2)) + .reduce((x, y) => x + y) / reps + ) / mean; const completed = reps; const total = this._iterations(); const time = t / 1000; const completed_per = completed / total; const time_per = time / completed; - const color = completed_per >= 1 ? "white" : completed_per < 0.33 ? "redBright" : completed_per < 0.66 ? "yellowBright" : "greenBright"; - const stddev_color = stddev < 0.25 ? "greenBright" : stddev < 0.5 ? "yellowBright" : "redBright"; + const color = + completed_per >= 1 + ? "white" + : completed_per < 0.33 + ? "redBright" + : completed_per < 0.66 + ? "yellowBright" + : "greenBright"; + const stddev_color = + stddev < 0.25 + ? "greenBright" + : stddev < 0.5 + ? "yellowBright" + : "redBright"; const path = [...this._path.reverse(), this._desc]; const last_path = path.slice(); if (LAST_PATH) { @@ -136,7 +166,9 @@ class Benchmark { `{${color} ${(100 * completed_per).toFixed(2)}}{whiteBright %)}`, `{whiteBright ${time.toFixed(3)}}s`, `{whiteBright ${time_per.toFixed(2)}}secs/op`, - `{${stddev_color} ${(100 * stddev).toFixed(2)}}{whiteBright %} σ/mean`, + `{${stddev_color} ${(100 * stddev).toFixed( + 2 + )}}{whiteBright %} σ/mean`, ...(OUTPUT_MODE === "flat" ? this._path : []), ...(OUTPUT_MODE === "tree" ? [] : [`{whiteBright ${this._desc}}`]) ); @@ -144,7 +176,13 @@ class Benchmark { *case_iter() { let x, start, now; - for (x = 0; !start || performance.now() - start < MIN_TIMEOUT || x < this._iterations() + this._toss(); x++) { + for ( + x = 0; + !start || + performance.now() - start < MIN_TIMEOUT || + x < this._iterations() + this._toss(); + x++ + ) { if (x >= this._toss() && start == undefined) { start = performance.now(); } @@ -172,7 +210,7 @@ class Benchmark { yield { test: this._desc, time: stop, - ...categories + ...categories, }; } if (this._after_each) { @@ -196,7 +234,15 @@ function unwind(stack) { } class Suite { - constructor(name, body, context, indent = 0, iterations = ITERATIONS, timeout = ITERATION_TIME, toss = TOSS_ITERATIONS) { + constructor( + name, + body, + context, + indent = 0, + iterations = ITERATIONS, + timeout = ITERATION_TIME, + toss = TOSS_ITERATIONS + ) { this._benchmarks = []; this._indent = indent; this._promises = []; @@ -215,7 +261,7 @@ class Suite { context._benchmarks.push( new Benchmark( desc, - this._context.map(x => x._name), + this._context.map((x) => x._name), body, context._indent, () => unwind(stack), @@ -257,7 +303,15 @@ class Suite { describe(description, body) { // todo closures here like Benchmark - const suite = new Suite(description, body, this._context, this._context[0]._indent + INDENT_LEVEL, this._context[0]._iterations, this._context[0]._timeout, this._context[0]._toss); + const suite = new Suite( + description, + body, + this._context, + this._context[0]._indent + INDENT_LEVEL, + this._context[0]._iterations, + this._context[0]._timeout, + this._context[0]._toss + ); this._context[0]._benchmarks.push(suite); } @@ -269,7 +323,9 @@ class Suite { async *run_all_cases() { this._context.unshift(this); if (this._name && OUTPUT_MODE === "grouped") { - console.log(`${" ".repeat(this._indent)}{whiteBright ${this._name}}`); + console.log( + `${" ".repeat(this._indent)}{whiteBright ${this._name}}` + ); } if (this._body) { await this._body(); @@ -301,12 +357,12 @@ class Suite { try { for await (let c of this.run_all_cases()) { results.push(c); - Object.keys(c).map(x => columns.add(x)); + Object.keys(c).map((x) => columns.add(x)); } } catch (e) { console.error(e.message); } - return results.map(x => { + return results.map((x) => { // TODO perspective bug :( for (const col of columns) { x[col] = x[col] === undefined ? "-" : x[col]; @@ -318,6 +374,12 @@ class Suite { window = window || global || {}; const mod = (window.PerspectiveBench = new Suite("perspective")); -for (const key of ["beforeAll", "afterAll", "afterEach", "describe", "benchmark"]) { +for (const key of [ + "beforeAll", + "afterAll", + "afterEach", + "describe", + "benchmark", +]) { window[key] = mod[key].bind(mod); } diff --git a/packages/perspective-cli/babel.config.js b/packages/perspective-cli/babel.config.js index bfbe1d8ec4..0c844d3485 100644 --- a/packages/perspective-cli/babel.config.js +++ b/packages/perspective-cli/babel.config.js @@ -8,14 +8,18 @@ module.exports = { node: "8", ios: "12", safari: "12", - edge: "44" + edge: "44", }, modules: false, useBuiltIns: "usage", - corejs: 3 - } - ] + corejs: 3, + }, + ], ], sourceType: "unambiguous", - plugins: ["lodash", ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes"] + plugins: [ + "lodash", + ["@babel/plugin-proposal-decorators", {legacy: true}], + "transform-custom-element-classes", + ], }; diff --git a/packages/perspective-cli/src/html/index.html b/packages/perspective-cli/src/html/index.html index 86eb19982f..38a4e7354a 100644 --- a/packages/perspective-cli/src/html/index.html +++ b/packages/perspective-cli/src/html/index.html @@ -1,58 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/packages/perspective-cli/src/js/index.js b/packages/perspective-cli/src/js/index.js index bd8f5154aa..8b6a37c385 100755 --- a/packages/perspective-cli/src/js/index.js +++ b/packages/perspective-cli/src/js/index.js @@ -84,21 +84,46 @@ async function host(filename, options) { } program - .version(JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json")).toString()).version) - .description("A convenient command-line client for Perspective.js. Can convert between Perspective supported format, or host a local web server.") + .version( + JSON.parse( + fs + .readFileSync(path.join(__dirname, "..", "package.json")) + .toString() + ).version + ) + .description( + "A convenient command-line client for Perspective.js. Can convert between Perspective supported format, or host a local web server." + ) .action(() => program.help()); program .command("convert [filename]") - .description("Convert a file into a new format. Reads from STDIN if no filename is provided.") - .option("-f, --format ", "Which output format to use: arrow, csv, columns, json.", /^(arrow|json|columns|csv)$/i, "arrow") - .option("-o, --output ", "Filename to write to. If not supplied, writes to STDOUT.") + .description( + "Convert a file into a new format. Reads from STDIN if no filename is provided." + ) + .option( + "-f, --format ", + "Which output format to use: arrow, csv, columns, json.", + /^(arrow|json|columns|csv)$/i, + "arrow" + ) + .option( + "-o, --output ", + "Filename to write to. If not supplied, writes to STDOUT." + ) .action(convert); program .command("host [filename]") - .description("Host a file on a local Websocket/HTTP server using a server-side Perspective. Reads from STDIN if no filename is provided") - .option("-p, --port ", "Which port to bind to.", x => parseInt(x), 8080) + .description( + "Host a file on a local Websocket/HTTP server using a server-side Perspective. Reads from STDIN if no filename is provided" + ) + .option( + "-p, --port ", + "Which port to bind to.", + (x) => parseInt(x), + 8080 + ) .option("-a, --assets ", "Host from a working directory") .option("-o, --open", "Open a browser automagically.") .action(host); diff --git a/packages/perspective-cli/src/js/utils.js b/packages/perspective-cli/src/js/utils.js index eb0e100ee3..3af8b8f517 100644 --- a/packages/perspective-cli/src/js/utils.js +++ b/packages/perspective-cli/src/js/utils.js @@ -10,7 +10,7 @@ const exec = require("child_process").exec; const {table} = require("@finos/perspective"); -const OPEN = port => ` +const OPEN = (port) => ` if which xdg-open > /dev/null then xdg-open http://localhost:${port}/ @@ -42,16 +42,16 @@ function infer_table(buffer) { module.exports.read_stdin = function read_stdin() { const ret = []; let len = 0; - return new Promise(resolve => { + return new Promise((resolve) => { process.stdin - .on("readable", function() { + .on("readable", function () { let chunk; while ((chunk = process.stdin.read())) { ret.push(Buffer.from(chunk)); len += chunk.length; } }) - .on("end", function() { + .on("end", function () { const buffer = Buffer.concat(ret, len); resolve(infer_table(buffer)); }); @@ -59,7 +59,7 @@ module.exports.read_stdin = function read_stdin() { }; module.exports.execute = function execute(command, callback) { - exec(command, function(error, stdout) { + exec(command, function (error, stdout) { if (callback) { callback(stdout); } diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index 58dfe20488..def261e6c7 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -47,13 +47,10 @@ "devDependencies": { "@finos/perspective-test": "^0.10.3", "@jupyter-widgets/base-manager": "^1.0.0-alpha.0", - "@types/jest": "^23.3.9", - "@types/node": "^11.11.0", "identity-obj-proxy": "^3.0.0", "isomorphic-fetch": "^2.2.1", "jest-transform-css": "^2.0.0", - "source-map-support": "^0.5.9", - "ts-jest": "^25.1.0" + "source-map-support": "^0.5.9" }, "jupyterlab": { "extension": true diff --git a/packages/perspective-jupyterlab/src/config/plugin.config.js b/packages/perspective-jupyterlab/src/config/plugin.config.js index 2834e2a377..0e6b643838 100644 --- a/packages/perspective-jupyterlab/src/config/plugin.config.js +++ b/packages/perspective-jupyterlab/src/config/plugin.config.js @@ -11,60 +11,68 @@ const path = require("path"); const PerspectivePlugin = require("@finos/perspective-webpack-plugin"); module.exports = { - mode: process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG ? "development" : process.env.NODE_ENV || "production", + mode: + process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG + ? "development" + : process.env.NODE_ENV || "production", entry: { - index: "./src/ts/index.ts" + index: "./src/js/index.js", }, resolve: { extensions: [".ts", ".js"], fallback: { path: false, fs: false, - crypto: false - } + crypto: false, + }, }, plugins: [new PerspectivePlugin({inline: true})], performance: { hints: false, maxEntrypointSize: 512000, - maxAssetSize: 512000 + maxAssetSize: 512000, }, externals: [/\@jupyter|\@lumino/], - stats: {modules: false, hash: false, version: false, builtAt: false, entrypoints: false}, + stats: { + modules: false, + hash: false, + version: false, + builtAt: false, + entrypoints: false, + }, module: { rules: [ { test: /\.less$/, exclude: /node_modules/, - use: [{loader: "style-loader"}, {loader: "css-loader"}, {loader: "less-loader"}] + use: [ + {loader: "style-loader"}, + {loader: "css-loader"}, + {loader: "less-loader"}, + ], }, { test: /\.css$/, exclude: /node_modules/, - use: [{loader: "css-loader"}] + use: [{loader: "css-loader"}], }, { test: /\.(html)$/, exclude: /node_modules/, use: { loader: "html-loader", - options: {} - } + options: {}, + }, }, - { - test: /\.ts$/, - exclude: /node_modules/, - loader: "ts-loader" - } - ] + ], }, externalsPresets: {web: false, webAsync: true}, output: { filename: "[name].js", library: { - type: "umd" + type: "umd", }, publicPath: "", - path: path.resolve(__dirname, "../../dist") - } + path: path.resolve(__dirname, "../../dist"), + }, }; diff --git a/packages/perspective-jupyterlab/src/config/webpack.config.js b/packages/perspective-jupyterlab/src/config/webpack.config.js index 277d8a4db3..5346179755 100644 --- a/packages/perspective-jupyterlab/src/config/webpack.config.js +++ b/packages/perspective-jupyterlab/src/config/webpack.config.js @@ -11,59 +11,67 @@ const path = require("path"); const PerspectivePlugin = require("@finos/perspective-webpack-plugin"); module.exports = { - mode: process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG ? "development" : process.env.NODE_ENV || "production", + mode: + process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG + ? "development" + : process.env.NODE_ENV || "production", entry: { - index: "./src/ts/psp_widget.ts" + index: "./src/js/psp_widget.js", }, resolve: { extensions: [".ts", ".js"], fallback: { path: false, fs: false, - crypto: false - } + crypto: false, + }, }, plugins: [new PerspectivePlugin({})], performance: { hints: false, maxEntrypointSize: 512000, - maxAssetSize: 512000 + maxAssetSize: 512000, + }, + stats: { + modules: false, + hash: false, + version: false, + builtAt: false, + entrypoints: false, }, - stats: {modules: false, hash: false, version: false, builtAt: false, entrypoints: false}, module: { rules: [ { test: /\.less$/, exclude: /node_modules/, - use: [{loader: "style-loader"}, {loader: "css-loader"}, {loader: "less-loader"}] + use: [ + {loader: "style-loader"}, + {loader: "css-loader"}, + {loader: "less-loader"}, + ], }, { test: /\.css$/, exclude: /node_modules/, - use: [{loader: "css-loader"}] + use: [{loader: "css-loader"}], }, { test: /\.(html)$/, exclude: /node_modules/, use: { loader: "html-loader", - options: {} - } + options: {}, + }, }, - { - test: /\.ts$/, - exclude: /node_modules/, - loader: "ts-loader" - } - ] + ], }, experiments: { - syncWebAssembly: true + syncWebAssembly: true, }, output: { filename: "lumino.js", libraryTarget: "umd", library: "PerspectiveLumino", - path: path.resolve(__dirname, "../../dist/umd") - } + path: path.resolve(__dirname, "../../dist/umd"), + }, }; diff --git a/packages/perspective-jupyterlab/src/ts/client.ts b/packages/perspective-jupyterlab/src/js/client.js similarity index 81% rename from packages/perspective-jupyterlab/src/ts/client.ts rename to packages/perspective-jupyterlab/src/js/client.js index 0fd00e29fe..b197d4b449 100644 --- a/packages/perspective-jupyterlab/src/ts/client.ts +++ b/packages/perspective-jupyterlab/src/js/client.js @@ -7,20 +7,8 @@ * */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import {DOMWidgetView} from "@jupyter-widgets/base"; import {Client} from "@finos/perspective/dist/esm/api/client"; -/** - * The schema for a message passed to and from `PerspectiveJupyterClient`. - */ -export interface PerspectiveJupyterMessage { - id: number; - type: string; - data: any; -} - /** * `PerspectiveJupyterClient` acts as a message bus between the frontend and * backend, passing messages from `perspective-viewer` (method calls, @@ -31,15 +19,14 @@ export interface PerspectiveJupyterMessage { * `@finos/perspective/api`. */ export class PerspectiveJupyterClient extends Client { - view: DOMWidgetView; - /** * Create a new instance of the client. * * @param view {DOMWidgetView} the plugin view that can send messages to the * Python backend. */ - constructor(view: DOMWidgetView) { + + constructor(view) { super(); this.view = view; } @@ -52,10 +39,15 @@ export class PerspectiveJupyterClient extends Client { * * @param msg {any} the message to pass to the `PerspectiveManager`. */ - send(msg: any): void { + + send(msg) { // Handle calls to `update` with a binary by setting `binary_length` // to true, so the kernel knows to handle the arraybuffer properly. - if (msg.method === "update" && msg.args.length === 2 && msg.args[0] instanceof ArrayBuffer) { + if ( + msg.method === "update" && + msg.args.length === 2 && + msg.args[0] instanceof ArrayBuffer + ) { const binary_msg = msg.args[0]; const buffers = [binary_msg]; @@ -66,7 +58,6 @@ export class PerspectiveJupyterClient extends Client { // Remove the arraybuffer from the message args, so it can be // passed along in `buffers`. msg.args.shift(); - const serialized = JSON.stringify(msg); // Send the first update message over the Jupyter comm with @@ -74,7 +65,7 @@ export class PerspectiveJupyterClient extends Client { this.view.send({ id: msg.id, type: "cmd", - data: serialized + data: serialized, }); // Send the second message with buffers. @@ -84,7 +75,7 @@ export class PerspectiveJupyterClient extends Client { this.view.send({ id: msg.id, type: "cmd", - data: JSON.stringify(msg) + data: JSON.stringify(msg), }); } } diff --git a/packages/perspective-jupyterlab/src/ts/index.ts b/packages/perspective-jupyterlab/src/js/index.js similarity index 99% rename from packages/perspective-jupyterlab/src/ts/index.ts rename to packages/perspective-jupyterlab/src/js/index.js index fac509d7b1..cc95a0677f 100644 --- a/packages/perspective-jupyterlab/src/ts/index.ts +++ b/packages/perspective-jupyterlab/src/js/index.js @@ -15,10 +15,8 @@ export * from "./widget"; /* css */ import "!!style-loader!css-loader!less-loader!../less/index.less"; - import "@finos/perspective-viewer-datagrid"; import "@finos/perspective-viewer-d3fc"; - import {perspectiveRenderers} from "./renderer"; import {PerspectiveJupyterPlugin} from "./plugin"; diff --git a/packages/perspective-jupyterlab/src/ts/model.ts b/packages/perspective-jupyterlab/src/js/model.js similarity index 70% rename from packages/perspective-jupyterlab/src/ts/model.ts rename to packages/perspective-jupyterlab/src/js/model.js index 246f71fc5f..cdcf4eede0 100644 --- a/packages/perspective-jupyterlab/src/ts/model.ts +++ b/packages/perspective-jupyterlab/src/js/model.js @@ -7,7 +7,7 @@ * */ -import {DOMWidgetModel, ISerializers} from "@jupyter-widgets/base"; +import {DOMWidgetModel} from "@jupyter-widgets/base"; import {PERSPECTIVE_VERSION} from "./version"; /** @@ -15,6 +15,7 @@ import {PERSPECTIVE_VERSION} from "./version"; */ export class PerspectiveModel extends DOMWidgetModel { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + defaults() { return { ...super.defaults(), @@ -24,7 +25,6 @@ export class PerspectiveModel extends DOMWidgetModel { _view_name: PerspectiveModel.view_name, _view_module: PerspectiveModel.view_module, _view_module_version: PerspectiveModel.view_module_version, - plugin: "datagrid", columns: [], row_pivots: [], @@ -37,19 +37,19 @@ export class PerspectiveModel extends DOMWidgetModel { dark: false, editable: false, server: false, - client: false + client: false, }; } +} - static serializers: ISerializers = { - ...DOMWidgetModel.serializers - // Add any extra serializers here - }; +PerspectiveModel.serializers = { + ...DOMWidgetModel.serializers, + // Add any extra serializers here +}; - static model_name = "PerspectiveModel"; - static model_module = "@finos/perspective-jupyterlab"; - static model_module_version = PERSPECTIVE_VERSION; - static view_name = "PerspectiveView"; - static view_module = "@finos/perspective-jupyterlab"; - static view_module_version = PERSPECTIVE_VERSION; -} +PerspectiveModel.model_name = "PerspectiveModel"; +PerspectiveModel.model_module = "@finos/perspective-jupyterlab"; +PerspectiveModel.model_module_version = PERSPECTIVE_VERSION; +PerspectiveModel.view_name = "PerspectiveView"; +PerspectiveModel.view_module = "@finos/perspective-jupyterlab"; +PerspectiveModel.view_module_version = PERSPECTIVE_VERSION; diff --git a/packages/perspective-jupyterlab/src/ts/plugin.ts b/packages/perspective-jupyterlab/src/js/plugin.js similarity index 73% rename from packages/perspective-jupyterlab/src/ts/plugin.ts rename to packages/perspective-jupyterlab/src/js/plugin.js index 2dd1f0caac..8bd8e1d84d 100644 --- a/packages/perspective-jupyterlab/src/ts/plugin.ts +++ b/packages/perspective-jupyterlab/src/js/plugin.js @@ -7,37 +7,29 @@ * */ -import {Application, IPlugin} from "@lumino/application"; - -import {Widget} from "@lumino/widgets"; - import {IJupyterWidgetRegistry} from "@jupyter-widgets/base"; - import {PerspectiveModel} from "./model"; - import {PerspectiveView} from "./view"; - import {PERSPECTIVE_VERSION} from "./version"; - const EXTENSION_ID = "@finos/perspective-jupyterlab"; /** * PerspectiveJupyterPlugin Defines the Jupyterlab plugin, and registers `PerspectiveModel` and `PerspectiveView` * to be called on initialization. */ -export const PerspectiveJupyterPlugin: IPlugin, void> = { +export const PerspectiveJupyterPlugin = { id: EXTENSION_ID, // @ts-ignore requires: [IJupyterWidgetRegistry], - activate: (app: Application, registry: IJupyterWidgetRegistry): void => { + activate: (app, registry) => { registry.registerWidget({ name: EXTENSION_ID, version: PERSPECTIVE_VERSION, exports: { PerspectiveModel: PerspectiveModel, - PerspectiveView: PerspectiveView - } + PerspectiveView: PerspectiveView, + }, }); }, - autoStart: true + autoStart: true, }; diff --git a/packages/perspective-jupyterlab/src/ts/psp_widget.ts b/packages/perspective-jupyterlab/src/js/psp_widget.js similarity index 64% rename from packages/perspective-jupyterlab/src/ts/psp_widget.ts rename to packages/perspective-jupyterlab/src/js/psp_widget.js index 02d1b2b1f0..a016849ddc 100644 --- a/packages/perspective-jupyterlab/src/ts/psp_widget.ts +++ b/packages/perspective-jupyterlab/src/js/psp_widget.js @@ -8,46 +8,31 @@ */ import "@finos/perspective-viewer"; - -import {Table, TableData, Aggregate, Sort, Expression, Filter, ColumnName} from "@finos/perspective"; -import {Message} from "@lumino/messaging"; import {Widget} from "@lumino/widgets"; -import {MIME_TYPE, PSP_CLASS, PSP_CONTAINER_CLASS, PSP_CONTAINER_CLASS_DARK} from "./utils"; - -import {PerspectiveViewerElement, PerspectiveViewerConfig} from "@finos/perspective-viewer"; +import { + MIME_TYPE, + PSP_CLASS, + PSP_CONTAINER_CLASS, + PSP_CONTAINER_CLASS_DARK, +} from "./utils"; let _increment = 0; -export interface PerspectiveWidgetOptions extends PerspectiveViewerConfig { - dark?: boolean; - client?: boolean; - server?: boolean; - title?: string; - bindto?: HTMLElement; - - // these shouldn't exist, PerspectiveViewerOptions should be sufficient e.g. - // ["row-pivots"] - column_pivots?: Array; - row_pivots?: Array; - expressions?: Array; - editable?: boolean; -} - /** * Class for perspective lumino widget. * * @class PerspectiveWidget (name) TODO: document */ export class PerspectiveWidget extends Widget { - constructor(name = "Perspective", options: PerspectiveWidgetOptions = {}) { - super({node: options.bindto || document.createElement("div")}); - this._viewer = PerspectiveWidget.createNode(this.node as HTMLDivElement); - + constructor(name = "Perspective", options = {}) { + super({ + node: options.bindto || document.createElement("div"), + }); + this._viewer = PerspectiveWidget.createNode(this.node); this.title.label = name; this.title.caption = `${name}`; this.id = `${name}-` + _increment; _increment += 1; - this._set_attributes(options); } @@ -56,22 +41,23 @@ export class PerspectiveWidget extends Widget { * * @param options */ - _set_attributes(options: PerspectiveViewerConfig & PerspectiveWidgetOptions): void { - const plugin: string = options.plugin || "datagrid"; - const columns: ColumnName[] = options.columns || []; - const row_pivots: ColumnName[] = options.row_pivots || options.row_pivots || []; - const column_pivots: ColumnName[] = options.column_pivots || options.column_pivots || []; - const aggregates: {[column: string]: Aggregate} = options.aggregates || {}; - const sort: Sort[] = options.sort || []; - const filter: Filter[] = options.filter || []; - const expressions: Expression[] = options.expressions || options.expressions || []; - const plugin_config: object = options.plugin_config || {}; - const dark: boolean = options.dark || false; - const editable: boolean = options.editable || false; - const server: boolean = options.server || false; - const client: boolean = options.client || false; - // const selectable: boolean = options.selectable || false; + _set_attributes(options) { + const plugin = options.plugin || "datagrid"; + const columns = options.columns || []; + const row_pivots = options.row_pivots || options.row_pivots || []; + const column_pivots = + options.column_pivots || options.column_pivots || []; + const aggregates = options.aggregates || {}; + const sort = options.sort || []; + const filter = options.filter || []; + const expressions = options.expressions || options.expressions || []; + const plugin_config = options.plugin_config || {}; + const dark = options.dark || false; + const editable = options.editable || false; + const server = options.server || false; + const client = options.client || false; + // const selectable: boolean = options.selectable || false; this.server = server; this.client = client; this.dark = dark; @@ -85,9 +71,8 @@ export class PerspectiveWidget extends Widget { columns, aggregates, expressions, - filter + filter, }; - // this.plugin_config = plugin_config; // this.selectable = selectable; } @@ -95,12 +80,12 @@ export class PerspectiveWidget extends Widget { /**********************/ /* Lumino Overrides */ /**********************/ - /** * Lumino: after visible * */ - onAfterShow(msg: Message): void { + + onAfterShow(msg) { this.notifyResize(); super.onAfterShow(msg); } @@ -109,31 +94,32 @@ export class PerspectiveWidget extends Widget { * Lumino: widget resize * */ - onResize(msg: Widget.ResizeMessage): void { + + onResize(msg) { this.notifyResize(); super.onResize(msg); } - protected onActivateRequest(msg: Message): void { + onActivateRequest(msg) { if (this.isAttached) { this.viewer.focus(); } super.onActivateRequest(msg); } - async notifyResize(): Promise { + async notifyResize() { await this.viewer.notifyResize(); } - async toggleConfig(): Promise { + async toggleConfig() { await this.viewer.toggleConfig(); } - async save(): Promise { + async save() { return await this.viewer.save(); } - async restore(config: PerspectiveViewerConfig): Promise { + async restore(config) { return await this.viewer.restore(config); } @@ -142,7 +128,8 @@ export class PerspectiveWidget extends Widget { * * @param table A `perspective.table` object. */ - async load(table: Promise): Promise { + + async load(table) { this.viewer.load(table); await this.viewer.restore(this._viewer_config); } @@ -152,7 +139,8 @@ export class PerspectiveWidget extends Widget { * * @param data */ - async _update(data: TableData): Promise { + + async _update(data) { const table = await this.viewer.getTable(); await table.update(data); } @@ -160,7 +148,8 @@ export class PerspectiveWidget extends Widget { /** * Removes all rows from the viewer's table. Does not reset viewer state. */ - async clear(): Promise { + + async clear() { const table = await this.viewer.getTable(); await table.clear(); } @@ -171,7 +160,8 @@ export class PerspectiveWidget extends Widget { * * @param data */ - async replace(data: TableData): Promise { + + async replace(data) { const table = await this.viewer.getTable(); await table.replace(data); } @@ -181,7 +171,8 @@ export class PerspectiveWidget extends Widget { * user state). This (or the underlying `perspective.table`'s equivalent * method) must be called in order for its memory to be reclaimed. */ - delete(): void { + + delete() { this.viewer.delete(); } @@ -189,11 +180,12 @@ export class PerspectiveWidget extends Widget { * Returns a promise that resolves to the element's edit port ID, used * internally when edits are made using datagrid in client/server mode. */ - async getEditPort(): Promise { + + async getEditPort() { return await this.viewer.getEditPort(); } - async getTable(): Promise
{ + async getTable() { return await this.viewer.getTable(); } @@ -202,13 +194,13 @@ export class PerspectiveWidget extends Widget { * Getters * */ - /** * Returns the underlying `PerspectiveViewer` instance. * * @returns {PerspectiveViewer} The widget's viewer instance. */ - get viewer(): PerspectiveViewerElement { + + get viewer() { return this._viewer; } @@ -217,7 +209,8 @@ export class PerspectiveWidget extends Widget { * * @returns {string} the widget name - "Perspective" if not set by the user. */ - get name(): string { + + get name() { return this.title.label; } @@ -226,15 +219,19 @@ export class PerspectiveWidget extends Widget { // pass in a plugin config and have it applied to the viewer, but they // cannot read the current `plugin_config` of the viewer if it has not // already been set from Python. - get plugin_config(): object { + + get plugin_config() { return this._plugin_config; } - set plugin_config(plugin_config: object) { + + set plugin_config(plugin_config) { this._plugin_config = plugin_config; // Allow plugin configs passed from Python to take effect on the viewer if (this._plugin_config) { - this.viewer.restore({plugin_config: this._plugin_config}); + this.viewer.restore({ + plugin_config: this._plugin_config, + }); } } @@ -242,10 +239,12 @@ export class PerspectiveWidget extends Widget { * True if the widget is in client-only mode, i.e. the browser has ownership * of the widget's data. */ - get client(): boolean { + + get client() { return this._client; } - set client(client: boolean) { + + set client(client) { this._client = client; } @@ -254,20 +253,24 @@ export class PerspectiveWidget extends Widget { * full ownership of the widget's data, and the widget does not have a * `perspective.Table` of its own. */ - get server(): boolean { + + get server() { return this._server; } - set server(server: boolean) { + + set server(server) { this._server = server; } /** * Enable or disable dark mode by re-rendering the viewer. */ - get dark(): boolean { + + get dark() { return this._dark; } - set dark(dark: boolean) { + + set dark(dark) { this._dark = dark; if (this._dark) { this.node.classList.add(PSP_CONTAINER_CLASS_DARK); @@ -281,10 +284,11 @@ export class PerspectiveWidget extends Widget { } } - get editable(): boolean { + get editable() { return this._editable; } - set editable(editable: boolean) { + + set editable(editable) { this._editable = editable; if (this._editable) { this.viewer.setAttribute("editable", ""); @@ -293,11 +297,11 @@ export class PerspectiveWidget extends Widget { } } - get selectable(): boolean { + get selectable() { return this.viewer.hasAttribute("selectable"); } - set selectable(row_selection: boolean) { + set selectable(row_selection) { if (row_selection) { this.viewer.setAttribute("selectable", ""); } else { @@ -305,13 +309,12 @@ export class PerspectiveWidget extends Widget { } } - static createNode(node: HTMLDivElement): PerspectiveViewerElement { + static createNode(node) { node.classList.add("p-Widget"); node.classList.add(PSP_CONTAINER_CLASS); const viewer = document.createElement("perspective-viewer"); viewer.classList.add(PSP_CLASS); viewer.setAttribute("type", MIME_TYPE); - while (node.lastChild) { node.removeChild(node.lastChild); } @@ -319,33 +322,31 @@ export class PerspectiveWidget extends Widget { node.appendChild(viewer); // allow perspective's event handlers to do their work - viewer.addEventListener("contextmenu", event => event.stopPropagation(), false); + viewer.addEventListener( + "contextmenu", + (event) => event.stopPropagation(), + false + ); const div = document.createElement("div"); div.style.setProperty("display", "flex"); div.style.setProperty("flex-direction", "row"); node.appendChild(div); - if (!viewer.notifyResize) { console.warn("Warning: not bound to real element"); } else { - const resize_observer = new MutationObserver(mutations => { - if (mutations.some(x => x.attributeName === "style")) { + const resize_observer = new MutationObserver((mutations) => { + if (mutations.some((x) => x.attributeName === "style")) { viewer.notifyResize.call(viewer); } }); - resize_observer.observe(node, {attributes: true}); + + resize_observer.observe(node, { + attributes: true, + }); viewer.toggleConfig(); } return viewer; } - - private _viewer: PerspectiveViewerElement; - private _plugin_config: object; - private _client: boolean; - private _server: boolean; - private _dark: boolean; - private _editable: boolean; - private _viewer_config: PerspectiveViewerConfig; } diff --git a/packages/perspective-jupyterlab/src/ts/renderer.ts b/packages/perspective-jupyterlab/src/js/renderer.js similarity index 63% rename from packages/perspective-jupyterlab/src/ts/renderer.ts rename to packages/perspective-jupyterlab/src/js/renderer.js index 42055d84f7..151f1fabff 100644 --- a/packages/perspective-jupyterlab/src/ts/renderer.ts +++ b/packages/perspective-jupyterlab/src/js/renderer.js @@ -8,13 +8,20 @@ */ import {ActivityMonitor} from "@jupyterlab/coreutils"; -import {ILayoutRestorer, JupyterFrontEnd, JupyterFrontEndPlugin} from "@jupyterlab/application"; -import {IThemeManager, WidgetTracker, Dialog, showDialog} from "@jupyterlab/apputils"; -import {ABCWidgetFactory, DocumentRegistry, IDocumentWidget, DocumentWidget} from "@jupyterlab/docregistry"; +import {ILayoutRestorer} from "@jupyterlab/application"; +import { + IThemeManager, + WidgetTracker, + Dialog, + showDialog, +} from "@jupyterlab/apputils"; + +import {ABCWidgetFactory, DocumentWidget} from "@jupyterlab/docregistry"; import {PerspectiveWidget} from "./psp_widget"; // eslint-disable-next-line @typescript-eslint/no-var-requires -const perspective = require("@finos/perspective").default; +// const perspective = require("@finos/perspective").default; +import perspective from "@finos/perspective"; /** * The name of the factories that creates widgets. @@ -22,42 +29,48 @@ const perspective = require("@finos/perspective").default; const FACTORY_CSV = "CSVPerspective"; const FACTORY_JSON = "JSONPerspective"; const FACTORY_ARROW = "ArrowPerspective"; - const RENDER_TIMEOUT = 1000; -type IPerspectiveDocumentType = "csv" | "json" | "arrow"; - // create here to reuse for exception handling -const baddialog = (): void => { +const baddialog = () => { showDialog({ body: "Perspective could not render the data", - buttons: [Dialog.okButton({label: "Dismiss"})], + buttons: [ + Dialog.okButton({ + label: "Dismiss", + }), + ], focusNodeSelector: "input", - title: "Error" + title: "Error", }); }; const WORKER = perspective.worker(); +export class PerspectiveDocumentWidget extends DocumentWidget { + constructor(options, type = "csv") { + super({ + content: new PerspectiveWidget("Perspective", { + editable: true, + }), + context: options.context, + reveal: options.reveal, + }); -export class PerspectiveDocumentWidget extends DocumentWidget { - constructor(options: DocumentWidget.IOptionsOptionalContent, type: IPerspectiveDocumentType = "csv") { - super({content: new PerspectiveWidget("Perspective", {editable: true}), context: options.context, reveal: options.reveal}); - + this._monitor = null; this._psp = this.content; this._type = type; this._context = options.context; - this._context.ready.then(() => { this._update(); this._monitor = new ActivityMonitor({ signal: this.context.model.contentChanged, - timeout: RENDER_TIMEOUT + timeout: RENDER_TIMEOUT, }); this._monitor.activityStopped.connect(this._update, this); }); } - private async _update(): Promise { + async _update() { try { let data; if (this._type === "csv") { @@ -65,12 +78,15 @@ export class PerspectiveDocumentWidget extends DocumentWidget data = this._context.model.toString(); } else if (this._type === "arrow") { // load arrow directly - data = Uint8Array.from(atob(this._context.model.toString()), c => c.charCodeAt(0)).buffer; + data = Uint8Array.from( + atob(this._context.model.toString()), + (c) => c.charCodeAt(0) + ).buffer; } else if (this._type === "json") { data = this._context.model.toJSON(); if (Array.isArray(data) && data.length > 0) { // already is records form, load directly - data = data as Array; + data = data; } else { // Column-oriented or single records JSON // don't handle for now, just need to implement @@ -82,7 +98,6 @@ export class PerspectiveDocumentWidget extends DocumentWidget // don't handle other mimetypes for now throw "Not handled"; } - try { const table = await this._psp.viewer.getTable(); table.replace(data); @@ -98,21 +113,29 @@ export class PerspectiveDocumentWidget extends DocumentWidget const view = await table.view(); view.on_update(async () => { if (this._type === "csv") { - const result: string = await view.to_csv(); + const result = await view.to_csv(); this.context.model.fromString(result); this.context.save(); } else if (this._type === "arrow") { - const result: ArrayBuffer = await view.to_arrow(); - const resultAsB64 = btoa(new Uint8Array(result).reduce((acc, i) => (acc += String.fromCharCode.apply(null, [i])), "")); + const result = await view.to_arrow(); + const resultAsB64 = btoa( + new Uint8Array(result).reduce( + (acc, i) => + (acc += String.fromCharCode.apply(null, [ + i, + ])), + "" + ) + ); this.context.model.fromString(resultAsB64); this.context.save(); } else if (this._type === "json") { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result: any = await view.to_json(); + const result = await view.to_json(); this.context.model.fromJSON(result); this.context.save(); } - }); + }); } } catch (e) { baddialog(); @@ -120,10 +143,11 @@ export class PerspectiveDocumentWidget extends DocumentWidget } // pickup theme from env - this._psp.dark = document.body.getAttribute("data-jp-theme-light") === "false"; + this._psp.dark = + document.body.getAttribute("data-jp-theme-light") === "false"; } - dispose(): void { + dispose() { if (this._monitor) { this._monitor.dispose(); } @@ -131,59 +155,70 @@ export class PerspectiveDocumentWidget extends DocumentWidget super.dispose(); } - public get psp(): PerspectiveWidget { + get psp() { return this._psp; } - - private _type: IPerspectiveDocumentType; - private _context: DocumentRegistry.Context; - private _psp: PerspectiveWidget; - private _monitor: ActivityMonitor | null = null; } /** * A widget factory for CSV widgets. */ -export class PerspectiveCSVFactory extends ABCWidgetFactory> { - protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget { - return new PerspectiveDocumentWidget({context}, "csv"); +export class PerspectiveCSVFactory extends ABCWidgetFactory { + createNewWidget(context) { + return new PerspectiveDocumentWidget( + { + context, + }, + "csv" + ); } } /** * A widget factory for JSON widgets. */ -export class PerspectiveJSONFactory extends ABCWidgetFactory> { - protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget { - return new PerspectiveDocumentWidget({context}, "json"); +export class PerspectiveJSONFactory extends ABCWidgetFactory { + createNewWidget(context) { + return new PerspectiveDocumentWidget( + { + context, + }, + "json" + ); } } /** * A widget factory for arrow widgets. */ -export class PerspectiveArrowFactory extends ABCWidgetFactory> { - protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget { - return new PerspectiveDocumentWidget({context}, "arrow"); +export class PerspectiveArrowFactory extends ABCWidgetFactory { + createNewWidget(context) { + return new PerspectiveDocumentWidget( + { + context, + }, + "arrow" + ); } } /** * Activate cssviewer extension for CSV files */ -function activate(app: JupyterFrontEnd, restorer: ILayoutRestorer | null, themeManager: IThemeManager | null): void { + +function activate(app, restorer, themeManager) { const factorycsv = new PerspectiveCSVFactory({ name: FACTORY_CSV, fileTypes: ["csv"], defaultFor: ["csv"], - readOnly: true + readOnly: true, }); const factoryjson = new PerspectiveJSONFactory({ name: FACTORY_JSON, fileTypes: ["json", "jsonl"], defaultFor: ["json", "jsonl"], - readOnly: true + readOnly: true, }); try { @@ -193,9 +228,9 @@ function activate(app: JupyterFrontEnd, restorer: ILayoutRestorer | null, themeM extensions: [".arrow"], mimeTypes: ["application/octet-stream"], contentType: "file", - fileFormat: "base64" + fileFormat: "base64", }); - } catch { + } catch (_a) { // do nothing } @@ -204,50 +239,57 @@ function activate(app: JupyterFrontEnd, restorer: ILayoutRestorer | null, themeM fileTypes: ["arrow"], defaultFor: ["arrow"], readOnly: true, - modelName: "base64" + modelName: "base64", }); - const trackercsv = new WidgetTracker>({ - namespace: "csvperspective" + const trackercsv = new WidgetTracker({ + namespace: "csvperspective", }); - const trackerjson = new WidgetTracker>({ - namespace: "jsonperspective" + const trackerjson = new WidgetTracker({ + namespace: "jsonperspective", }); - const trackerarrow = new WidgetTracker>({ - namespace: "arrowperspective" + const trackerarrow = new WidgetTracker({ + namespace: "arrowperspective", }); if (restorer) { // Handle state restoration. void restorer.restore(trackercsv, { command: "docmanager:open", - args: widget => ({path: widget.context.path, factory: FACTORY_CSV}), - name: widget => widget.context.path + args: (widget) => ({ + path: widget.context.path, + factory: FACTORY_CSV, + }), + name: (widget) => widget.context.path, }); void restorer.restore(trackerjson, { command: "docmanager:open", - args: widget => ({path: widget.context.path, factory: FACTORY_JSON}), - name: widget => widget.context.path + args: (widget) => ({ + path: widget.context.path, + factory: FACTORY_JSON, + }), + name: (widget) => widget.context.path, }); void restorer.restore(trackerarrow, { command: "docmanager:open", - args: widget => ({path: widget.context.path, factory: FACTORY_ARROW}), - name: widget => widget.context.path + args: (widget) => ({ + path: widget.context.path, + factory: FACTORY_ARROW, + }), + name: (widget) => widget.context.path, }); } app.docRegistry.addWidgetFactory(factorycsv); app.docRegistry.addWidgetFactory(factoryjson); app.docRegistry.addWidgetFactory(factoryarrow); - const ftcsv = app.docRegistry.getFileType("csv"); const ftjson = app.docRegistry.getFileType("json"); const ftarrow = app.docRegistry.getFileType("arrow"); - factorycsv.widgetCreated.connect((sender, widget) => { // Track the widget. void trackercsv.add(widget); @@ -294,15 +336,21 @@ function activate(app: JupyterFrontEnd, restorer: ILayoutRestorer | null, themeM }); // Keep the themes up-to-date. - const updateThemes = (): void => { - const isLight = themeManager && themeManager.theme ? themeManager.isLight(themeManager.theme) : true; - trackercsv.forEach((pspDocWidget: PerspectiveDocumentWidget) => { + const updateThemes = () => { + const isLight = + themeManager && themeManager.theme + ? themeManager.isLight(themeManager.theme) + : true; + + trackercsv.forEach((pspDocWidget) => { pspDocWidget.psp.dark = !isLight; }); - trackerjson.forEach((pspDocWidget: PerspectiveDocumentWidget) => { + + trackerjson.forEach((pspDocWidget) => { pspDocWidget.psp.dark = !isLight; }); - trackerarrow.forEach((pspDocWidget: PerspectiveDocumentWidget) => { + + trackerarrow.forEach((pspDocWidget) => { pspDocWidget.psp.dark = !isLight; }); }; @@ -315,10 +363,10 @@ function activate(app: JupyterFrontEnd, restorer: ILayoutRestorer | null, themeM /** * The perspective extension for files */ -export const perspectiveRenderers: JupyterFrontEndPlugin = { +export const perspectiveRenderers = { activate: activate, id: "@finos/perspective-jupyterlab:renderers", requires: [], optional: [ILayoutRestorer, IThemeManager], - autoStart: true + autoStart: true, }; diff --git a/packages/perspective-jupyterlab/src/ts/utils.ts b/packages/perspective-jupyterlab/src/js/utils.js similarity index 96% rename from packages/perspective-jupyterlab/src/ts/utils.ts rename to packages/perspective-jupyterlab/src/js/utils.js index 53c06d1f9c..9ed1ff2b7b 100644 --- a/packages/perspective-jupyterlab/src/ts/utils.ts +++ b/packages/perspective-jupyterlab/src/js/utils.js @@ -7,11 +7,7 @@ * */ -/* defines */ export const MIME_TYPE = "application/psp+json"; - export const PSP_CLASS = "PSPViewer"; - export const PSP_CONTAINER_CLASS = "PSPContainer"; - export const PSP_CONTAINER_CLASS_DARK = "PSPContainer-dark"; diff --git a/packages/perspective-jupyterlab/src/ts/version.ts b/packages/perspective-jupyterlab/src/js/version.js similarity index 86% rename from packages/perspective-jupyterlab/src/ts/version.ts rename to packages/perspective-jupyterlab/src/js/version.js index 077d8ba5a9..a58e7163d8 100644 --- a/packages/perspective-jupyterlab/src/ts/version.ts +++ b/packages/perspective-jupyterlab/src/js/version.js @@ -7,7 +7,5 @@ * */ -// eslint-disable-next-line @typescript-eslint/no-var-requires const pkg_json = require("../../package.json"); - export const PERSPECTIVE_VERSION = pkg_json.version; diff --git a/packages/perspective-jupyterlab/src/ts/view.ts b/packages/perspective-jupyterlab/src/js/view.js similarity index 75% rename from packages/perspective-jupyterlab/src/ts/view.ts rename to packages/perspective-jupyterlab/src/js/view.js index 450678ffe7..a01f8f8dc2 100644 --- a/packages/perspective-jupyterlab/src/ts/view.ts +++ b/packages/perspective-jupyterlab/src/js/view.js @@ -8,13 +8,10 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ - import {isEqual} from "underscore"; import {DOMWidgetView} from "@jupyter-widgets/base"; -import {PerspectiveWorker, Table, View} from "@finos/perspective"; - import {PerspectiveJupyterWidget} from "./widget"; -import {PerspectiveJupyterClient, PerspectiveJupyterMessage} from "./client"; +import {PerspectiveJupyterClient} from "./client"; // eslint-disable-next-line @typescript-eslint/no-var-requires const perspective = require("@finos/perspective").default; @@ -24,21 +21,7 @@ const perspective = require("@finos/perspective").default; * the DOM. */ export class PerspectiveView extends DOMWidgetView { - // @ts-ignore - pWidget: PerspectiveJupyterWidget; // this should be pWidget, but temporarily calling it pWidget for widgets incompatibilities - perspective_client: PerspectiveJupyterClient; - - // The message ID that is expecting a binary as a follow-up message. - _pending_binary: number; - - // if there is a port_id, join it with the pending binary so on_update - // callbacks work correctly - _pending_port_id: number; - - // If client mode, the WebWorker reference. - _client_worker: PerspectiveWorker; - - _createElement(): HTMLElement { + _createElement() { this.pWidget = new PerspectiveJupyterWidget(undefined, { plugin: this.model.get("plugin"), columns: this.model.get("columns"), @@ -53,19 +36,19 @@ export class PerspectiveView extends DOMWidgetView { client: this.model.get("client"), dark: this.model.get("dark") === null // only set if its a bool, otherwise inherit - ? document.body.getAttribute("data-jp-theme-light") === "false" + ? document.body.getAttribute("data-jp-theme-light") === + "false" : this.model.get("dark"), editable: this.model.get("editable"), bindto: this.el, - // @ts-ignore - view: this + view: this, }); - // @ts-ignore + this.perspective_client = new PerspectiveJupyterClient(this); return this.pWidget.node; } - _setElement(el: HTMLElement): void { + _setElement(el) { if (this.el || el !== this.pWidget.node) { // Do not allow the view to be reassigned to a different element. throw new Error("Cannot reset the DOM element."); @@ -78,17 +61,24 @@ export class PerspectiveView extends DOMWidgetView { * * @param mutations */ - _synchronize_state(mutations: any): void { + + _synchronize_state(mutations) { for (const mutation of mutations) { const name = mutation.attributeName.replace(/-/g, "_"); - let new_value = this.pWidget.viewer.getAttribute(mutation.attributeName); - const current_value = this.model.get(name); + let new_value = this.pWidget.viewer.getAttribute( + mutation.attributeName + ); + const current_value = this.model.get(name); if (typeof new_value === "undefined") { continue; } - if (new_value && typeof new_value === "string" && name !== "plugin") { + if ( + new_value && + typeof new_value === "string" && + name !== "plugin" + ) { new_value = JSON.parse(new_value); } @@ -105,9 +95,9 @@ export class PerspectiveView extends DOMWidgetView { * Attach event handlers, and watch the DOM for state changes in order to * reflect them back to Python. */ - render(): void { - super.render(); + render() { + super.render(); this.model.on("msg:custom", this._handle_message, this); this.model.on("change:plugin", this.plugin_changed, this); this.model.on("change:columns", this.columns_changed, this); @@ -120,16 +110,25 @@ export class PerspectiveView extends DOMWidgetView { this.model.on("change:plugin_config", this.plugin_config_changed, this); this.model.on("change:dark", this.dark_changed, this); this.model.on("change:editable", this.editable_changed, this); - // Watch the viewer DOM so that widget state is always synchronized with // DOM attributes. - const observer = new MutationObserver(this._synchronize_state.bind(this)); + const observer = new MutationObserver( + this._synchronize_state.bind(this) + ); observer.observe(this.pWidget.viewer, { attributes: true, - attributeFilter: ["plugin", "columns", "row-pivots", "column-pivots", "aggregates", "sort", "filters", "expressions"], - subtree: false + attributeFilter: [ + "plugin", + "columns", + "row-pivots", + "column-pivots", + "aggregates", + "sort", + "filters", + "expressions", + ], + subtree: false, }); - /** * Request a table from the manager. If a table has been loaded, proxy * it and kick off subsequent operations. @@ -139,7 +138,7 @@ export class PerspectiveView extends DOMWidgetView { */ this.perspective_client.send({ id: -2, - cmd: "table" + cmd: "table", }); } @@ -150,12 +149,12 @@ export class PerspectiveView extends DOMWidgetView { * * @param msg {PerspectiveJupyterMessage} */ - _handle_message(msg: PerspectiveJupyterMessage, buffers: Array): void { + + _handle_message(msg, buffers) { if (this._pending_binary && buffers.length === 1) { // Handle binary messages from the widget, which (unlike the // tornado handler), does not send messages in chunks. const binary = buffers[0].buffer.slice(0); - // make sure on_update callbacks are called with a `port_id` // AND the transferred binary. if (this._pending_port_id !== undefined) { @@ -167,24 +166,23 @@ export class PerspectiveView extends DOMWidgetView { id: this._pending_binary, data: { port_id: this._pending_port_id, - delta: binary - } - } + delta: binary, + }, + }, }); } else { this.perspective_client._handle({ id: this._pending_binary, data: { id: this._pending_binary, - data: binary - } + data: binary, + }, }); } this._pending_port_id = undefined; this._pending_binary = undefined; return; } - if (msg.type === "table") { // If in client-only mode (no Table on the python widget), // message.data is an object containing "data" and "options". @@ -196,7 +194,6 @@ export class PerspectiveView extends DOMWidgetView { this.pWidget.delete(); return; } - if (this.pWidget.client === true) { // In client mode, we need to directly call the methods on the // viewer @@ -213,24 +210,24 @@ export class PerspectiveView extends DOMWidgetView { // same comm, so mutations on `msg` affect subsequent message // handlers. const message = JSON.parse(JSON.stringify(msg)); - delete message.type; if (typeof message.data === "string") { message.data = JSON.parse(message.data); } - if (message.data["binary_length"]) { // If the `binary_length` flag is set, the worker expects // the next message to be a transferable object. This sets // the `_pending_binary` flag, which triggers a special // handler for the ArrayBuffer containing binary data. this._pending_binary = message.data.id; - // Check whether the message also contains a `port_id`, // indicating that we are in an `on_update` callback and // the pending binary needs to be joined with the port_id // for on_update handlers to work properly. - if (message.data.data && message.data.data.port_id !== undefined) { + if ( + message.data.data && + message.data.data.port_id !== undefined + ) { this._pending_port_id = message.data.data.port_id; } } else { @@ -240,7 +237,7 @@ export class PerspectiveView extends DOMWidgetView { } } - get client_worker(): PerspectiveWorker { + get client_worker() { if (!this._client_worker) { this._client_worker = perspective.worker(); } @@ -253,16 +250,16 @@ export class PerspectiveView extends DOMWidgetView { * * @param {PerspectiveJupyterMessage} msg */ - _handle_load_message(msg: PerspectiveJupyterMessage): void { - const table_options = msg.data["options"] || {}; + _handle_load_message(msg) { + const table_options = msg.data["options"] || {}; if (this.pWidget.client) { /** * In client mode, retrieve the serialized data and the options * passed by the user, and create a new table on the client end. */ const data = msg.data["data"]; - const client_table: Promise
= this.client_worker.table(data, table_options); + const client_table = this.client_worker.table(data, table_options); this.pWidget.load(client_table); } else { if (this.pWidget.server && msg.data["table_name"]) { @@ -270,74 +267,94 @@ export class PerspectiveView extends DOMWidgetView { * Get a remote table handle, and load the remote table in * the client for server mode Perspective. */ - const table = this.perspective_client.open_table(msg.data["table_name"]); + const table = this.perspective_client.open_table( + msg.data["table_name"] + ); this.pWidget.load(table); } else if (msg.data["table_name"]) { // Get a remote table handle from the Jupyter kernel, and mirror // the table on the client, setting up editing if necessary. - const kernel_table: Table = this.perspective_client.open_table(msg.data["table_name"]); - const kernel_view: Promise = kernel_table.view(); - kernel_view.then(kernel_view => { - kernel_view.to_arrow().then(async (arrow: ArrayBuffer) => { + const kernel_table = this.perspective_client.open_table( + msg.data["table_name"] + ); + const kernel_view = kernel_table.view(); + kernel_view.then((kernel_view) => { + kernel_view.to_arrow().then(async (arrow) => { // Create a client side table - const client_table = this.client_worker.table(arrow, table_options); //.then(client_table => { + const client_table = this.client_worker.table( + arrow, + table_options + ); //.then(client_table => { const client_table2 = await client_table; if (this.pWidget.editable) { // Set up client/server editing - const client_view = await client_table2.view(); // table.view().then(client_view => { - let client_edit_port: number, server_edit_port: number; - + let client_edit_port, server_edit_port; // Create ports on the client and kernel. - Promise.all([this.pWidget.load(client_table), this.pWidget.getEditPort(), kernel_table.make_port()]).then(outs => { + Promise.all([ + this.pWidget.load(client_table), + this.pWidget.getEditPort(), + kernel_table.make_port(), + ]).then((outs) => { client_edit_port = outs[1]; server_edit_port = outs[2]; }); - // When the client updates, if the update // comes through the edit port then forward // it to the server. client_view.on_update( - updated => { + (updated) => { if (updated.port_id === client_edit_port) { kernel_table.update(updated.delta, { - port_id: server_edit_port + port_id: server_edit_port, }); } }, - {mode: "row"} + { + mode: "row", + } ); - // If the server updates, and the edit is // not coming from the server edit port, // then synchronize state with the client. kernel_view.on_update( - updated => { + (updated) => { if (updated.port_id !== server_edit_port) { client_table2.update(updated.delta); // any port, we dont care } }, - {mode: "row"} + { + mode: "row", + } ); // }); } else { // Load the table and mirror updates from the // kernel. this.pWidget.load(client_table); - kernel_view.on_update(updated => client_table2.update(updated.delta), {mode: "row"}); + kernel_view.on_update( + (updated) => + client_table2.update(updated.delta), + { + mode: "row", + } + ); } // }); }); }); } else { - throw new Error(`PerspectiveWidget cannot load data from kernel message: ${JSON.stringify(msg)}`); + throw new Error( + `PerspectiveWidget cannot load data from kernel message: ${JSON.stringify( + msg + )}` + ); } - // Only call `init` after the viewer has a table. this.perspective_client.send({ id: -1, - cmd: "init" + cmd: "init", }); } } @@ -346,7 +363,8 @@ export class PerspectiveView extends DOMWidgetView { * When the View is removed after the widget terminates, clean up the * client viewer and Web Worker. */ - remove(): void { + + remove() { this.pWidget.delete(); this.client_worker.terminate(); } @@ -356,47 +374,64 @@ export class PerspectiveView extends DOMWidgetView { * the front-end viewer. `client` and `server` are not included, as they * are not properties in ``. */ - plugin_changed(): void { - this.pWidget.restore({plugin: this.model.get("plugin")}); + + plugin_changed() { + this.pWidget.restore({ + plugin: this.model.get("plugin"), + }); } - columns_changed(): void { - this.pWidget.restore({columns: this.model.get("columns")}); + columns_changed() { + this.pWidget.restore({ + columns: this.model.get("columns"), + }); } - row_pivots_changed(): void { - this.pWidget.restore({row_pivots: this.model.get("row_pivots")}); + row_pivots_changed() { + this.pWidget.restore({ + row_pivots: this.model.get("row_pivots"), + }); } - column_pivots_changed(): void { - this.pWidget.restore({column_pivots: this.model.get("column_pivots")}); + column_pivots_changed() { + this.pWidget.restore({ + column_pivots: this.model.get("column_pivots"), + }); } - aggregates_changed(): void { - this.pWidget.restore({aggregates: this.model.get("aggregates")}); + aggregates_changed() { + this.pWidget.restore({ + aggregates: this.model.get("aggregates"), + }); } - sort_changed(): void { - this.pWidget.restore({sort: this.model.get("sort")}); + sort_changed() { + this.pWidget.restore({ + sort: this.model.get("sort"), + }); } - filters_changed(): void { - this.pWidget.restore({filter: this.model.get("filter")}); + filters_changed() { + this.pWidget.restore({ + filter: this.model.get("filter"), + }); } - expressions_changed(): void { - this.pWidget.restore({expressions: this.model.get("expressions")}); + expressions_changed() { + this.pWidget.restore({ + expressions: this.model.get("expressions"), + }); } - plugin_config_changed(): void { + plugin_config_changed() { this.pWidget.plugin_config = this.model.get("plugin_config"); } - dark_changed(): void { + dark_changed() { this.pWidget.dark = this.model.get("dark"); } - editable_changed(): void { + editable_changed() { this.pWidget.editable = this.model.get("editable"); } } diff --git a/packages/perspective-jupyterlab/src/ts/widget.ts b/packages/perspective-jupyterlab/src/js/widget.js similarity index 68% rename from packages/perspective-jupyterlab/src/ts/widget.ts rename to packages/perspective-jupyterlab/src/js/widget.js index 7058cb9564..ebeec52b1d 100644 --- a/packages/perspective-jupyterlab/src/ts/widget.ts +++ b/packages/perspective-jupyterlab/src/js/widget.js @@ -7,21 +7,13 @@ * */ -import {Message} from "@lumino/messaging"; -import {DOMWidgetView} from "@jupyter-widgets/base"; - -import {PerspectiveViewerConfig} from "@finos/perspective-viewer"; -import {PerspectiveWidget, PerspectiveWidgetOptions} from "./psp_widget"; - -export type PerspectiveJupyterWidgetOptions = { - view: DOMWidgetView; -}; +import {PerspectiveWidget} from "./psp_widget"; /** * PerspectiveJupyterWidget is the ipywidgets front-end for the Perspective Jupyterlab plugin. */ export class PerspectiveJupyterWidget extends PerspectiveWidget { - constructor(name = "Perspective", options: PerspectiveViewerConfig & PerspectiveJupyterWidgetOptions & PerspectiveWidgetOptions) { + constructor(name = "Perspective", options) { const view = options.view; delete options.view; super(name, options); @@ -34,7 +26,8 @@ export class PerspectiveJupyterWidget extends PerspectiveWidget { * Any custom lumino widget used inside a Jupyter widget should override * the processMessage function like this. */ - processMessage(msg: Message): void { + + processMessage(msg) { super.processMessage(msg); this._view.processPhosphorMessage(msg); } @@ -44,19 +37,17 @@ export class PerspectiveJupyterWidget extends PerspectiveWidget { * * This causes the view to be destroyed as well with 'remove' */ - dispose(): void { + + dispose() { if (this.isDisposed) { return; } super.dispose(); - if (this._view) { this._view.remove(); } this._view = null; } - - private _view: DOMWidgetView; } diff --git a/packages/perspective-jupyterlab/src/less/index.less b/packages/perspective-jupyterlab/src/less/index.less index 77e0e00da5..81a87f0ae1 100644 --- a/packages/perspective-jupyterlab/src/less/index.less +++ b/packages/perspective-jupyterlab/src/less/index.less @@ -1,6 +1,7 @@ @import "~@finos/perspective-viewer/src/less/fonts.less"; @import "~@finos/perspective-viewer/src/themes/material-dense.less"; -@import (reference) "~@finos/perspective-viewer/src/themes/material-dense.dark.less"; +@import (reference) + "~@finos/perspective-viewer/src/themes/material-dense.dark.less"; div.PSPContainer, div.PSPContainer-dark { diff --git a/packages/perspective-jupyterlab/src/ts/.eslintrc.json b/packages/perspective-jupyterlab/src/ts/.eslintrc.json deleted file mode 100644 index f028410ee0..0000000000 --- a/packages/perspective-jupyterlab/src/ts/.eslintrc.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "plugins": [ - "prettier", - "@typescript-eslint/eslint-plugin" - ], - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true, - "jasmine": true, - "jest": true - }, - "parserOptions": { - "ecmaVersion": 2017, - "ecmaFeatures": {}, - "sourceType": "module", - "experimentalObjectRestSpread": true - }, - "rules": { - "prettier/prettier": ["error", { - "printWidth": 200, - "tabWidth": 4, - "bracketSpacing": false - }], - - "max-len": ["error", 200], - "no-const-assign": "error", - "no-this-before-super": "error", - "no-undef": "error", - "no-unreachable": "error", - "no-unused-vars": "error", - "constructor-super": "error", - "valid-typeof": "error", - - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/ban-ts-ignore": "off" - } -} \ No newline at end of file diff --git a/packages/perspective-jupyterlab/src/ts/arraybuffer.d.ts b/packages/perspective-jupyterlab/src/ts/arraybuffer.d.ts deleted file mode 100644 index 8e3a2abb9f..0000000000 --- a/packages/perspective-jupyterlab/src/ts/arraybuffer.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2018, the Perspective Authors. - * - * This file is part of the Perspective library, distributed under the terms of - * the Apache License 2.0. The full license can be found in the LICENSE file. - * - */ - -declare module "@finos/perspective/dist/esm/api/client"; -declare module "@finos/perspective/dist/umd/psp.async.wasm"; -declare module "arraybuffer-loader!*"; diff --git a/packages/perspective-jupyterlab/test/config/jest.config.js b/packages/perspective-jupyterlab/test/config/jest.config.js index 1d91b8b846..b6cca8e70b 100644 --- a/packages/perspective-jupyterlab/test/config/jest.config.js +++ b/packages/perspective-jupyterlab/test/config/jest.config.js @@ -12,7 +12,9 @@ const main_config = require("../../../perspective-test/jest.config.js"); // and works with the main `perspective-test` Jest config. module.exports = Object.assign(main_config, { transform: Object.assign(main_config.transform, { - "^.+\\.ts?$": "ts-jest" + "^.+\\.ts?$": "ts-jest", }), - transformIgnorePatterns: ["/node_modules/(?!(lit-html|@jupyter-widgets)/).+\\.js"] + transformIgnorePatterns: [ + "/node_modules/(?!(lit-html|@jupyter-widgets)/).+\\.js", + ], }); diff --git a/packages/perspective-jupyterlab/test/config/jupyter/globalSetup.js b/packages/perspective-jupyterlab/test/config/jupyter/globalSetup.js index e88f2930df..401a43e408 100644 --- a/packages/perspective-jupyterlab/test/config/jupyter/globalSetup.js +++ b/packages/perspective-jupyterlab/test/config/jupyter/globalSetup.js @@ -8,7 +8,7 @@ */ const {start_jlab, kill_jlab} = require("./jlab_start"); -module.exports = async function() { +module.exports = async function () { await start_jlab(); // At this point, Jupyterlab has already been started by the main test diff --git a/packages/perspective-jupyterlab/test/config/jupyter/jest.config.js b/packages/perspective-jupyterlab/test/config/jupyter/jest.config.js index 73f12f8039..5ddd89bff8 100644 --- a/packages/perspective-jupyterlab/test/config/jupyter/jest.config.js +++ b/packages/perspective-jupyterlab/test/config/jupyter/jest.config.js @@ -12,5 +12,5 @@ module.exports = Object.assign(main_config, { globalSetup: "/test/config/jupyter/globalSetup.js", setupFilesAfterEnv: ["/test/config/jupyter/teardown.js"], testMatch: ["/test/jupyter/*.spec.js"], - roots: ["test"] + roots: ["test"], }); diff --git a/packages/perspective-jupyterlab/test/config/jupyter/jlab_start.js b/packages/perspective-jupyterlab/test/config/jupyter/jlab_start.js index aa49833573..6e2b9cc405 100644 --- a/packages/perspective-jupyterlab/test/config/jupyter/jlab_start.js +++ b/packages/perspective-jupyterlab/test/config/jupyter/jlab_start.js @@ -26,19 +26,24 @@ exports.kill_jlab = kill_jlab; /** * Block until the Jupyterlab server is ready. */ -const wait_for_jlab = async function() { +const wait_for_jlab = async function () { let num_errors = 0; let loaded = false; while (!loaded) { - get(`http://127.0.0.1:${process.env.__JUPYTERLAB_PORT__}/lab?`, res => { - if (res.statusCode !== 200) { - throw new Error(`${res.statusCode} not 200!`); - } + get( + `http://127.0.0.1:${process.env.__JUPYTERLAB_PORT__}/lab?`, + (res) => { + if (res.statusCode !== 200) { + throw new Error(`${res.statusCode} not 200!`); + } - console.log(`Jupyterlab server has started on ${process.env.__JUPYTERLAB_PORT__}`); - loaded = true; - }).on("error", err => { + console.log( + `Jupyterlab server has started on ${process.env.__JUPYTERLAB_PORT__}` + ); + loaded = true; + } + ).on("error", (err) => { if (num_errors > 50) { kill_jlab(); throw new Error(`Could not launch Jupyterlab: ${err}`); @@ -47,18 +52,29 @@ const wait_for_jlab = async function() { num_errors++; }); - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 500)); } }; -exports.start_jlab = function() { +exports.start_jlab = function () { /* * Spawn the Jupyterlab server. */ try { // Does not alter the global env, only the env for this process - process.env.JUPYTER_CONFIG_DIR = path.join(PACKAGE_ROOT, "test", "config", "jupyter"); - process.env.JUPYTERLAB_SETTINGS_DIR = path.join(PACKAGE_ROOT, "test", "config", "jupyter", "user_settings"); + process.env.JUPYTER_CONFIG_DIR = path.join( + PACKAGE_ROOT, + "test", + "config", + "jupyter" + ); + process.env.JUPYTERLAB_SETTINGS_DIR = path.join( + PACKAGE_ROOT, + "test", + "config", + "jupyter", + "user_settings" + ); // Start jupyterlab with a root to dist/umd where the notebooks will be. process.chdir(path.join(PACKAGE_ROOT, "dist", "umd")); @@ -68,12 +84,21 @@ exports.start_jlab = function() { // Jupyterlab is spawned with the default $PYTHONPATH of the shell it // is running in. For local testing during devlopment you may need to // run it with the $PYTHONPATH set to ./python/perspective - const proc = spawn("jupyter", ["lab", "--no-browser", `--port=${process.env.__JUPYTERLAB_PORT__}`, `--config=${process.env.JUPYTER_CONFIG_DIR}/jupyter_notebook_config.json`], { - env: { - ...process.env - }, - stdio: "inherit" - }); + const proc = spawn( + "jupyter", + [ + "lab", + "--no-browser", + `--port=${process.env.__JUPYTERLAB_PORT__}`, + `--config=${process.env.JUPYTER_CONFIG_DIR}/jupyter_notebook_config.json`, + ], + { + env: { + ...process.env, + }, + stdio: "inherit", + } + ); // Wait for Jupyterlab to start up return wait_for_jlab().then(() => { diff --git a/packages/perspective-jupyterlab/test/html/resize.html b/packages/perspective-jupyterlab/test/html/resize.html index 8050776428..871ac1b0fe 100644 --- a/packages/perspective-jupyterlab/test/html/resize.html +++ b/packages/perspective-jupyterlab/test/html/resize.html @@ -9,33 +9,34 @@ - - - - - - - - - - - - - - -
- - - - - \ No newline at end of file + + + + + + + + + + +
+ + + + diff --git a/packages/perspective-jupyterlab/test/js/resize.spec.js b/packages/perspective-jupyterlab/test/js/resize.spec.js index 2251aa4ab2..33eae27acd 100644 --- a/packages/perspective-jupyterlab/test/js/resize.spec.js +++ b/packages/perspective-jupyterlab/test/js/resize.spec.js @@ -14,7 +14,7 @@ utils.with_server({}, () => { describe.page( "resize.html", () => { - test.capture("Config should show by default", async page => { + test.capture("Config should show by default", async (page) => { await page.waitForFunction(() => !!window.__WIDGET__); // await page.waitForSelector("perspective-viewer:not([updating])"); // await page.waitForSelector("perspective-viewer[settings]"); @@ -25,25 +25,46 @@ utils.with_server({}, () => { }); }); - test.capture("Resize the container causes the widget to resize", async page => { - // await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - // await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(async () => { - document.querySelector(".PSPContainer").style = "position:absolute;top:0;left:0;width:300px;height:300px"; - await document.querySelector("perspective-viewer").notifyResize(); - }); + test.capture( + "Resize the container causes the widget to resize", + async (page) => { + // await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + // await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate(async () => { + document.querySelector(".PSPContainer").style = + "position:absolute;top:0;left:0;width:300px;height:300px"; + await document + .querySelector("perspective-viewer") + .notifyResize(); + }); - return await page.evaluate(async () => { - document.querySelector(".PSPContainer").style = "position:absolute;top:0;left:0;width:800px;height:600px"; - await document.querySelector("perspective-viewer").notifyResize(); - return window.__WIDGET__.viewer.innerHTML; - }); - }); + return await page.evaluate(async () => { + document.querySelector(".PSPContainer").style = + "position:absolute;top:0;left:0;width:800px;height:600px"; + await document + .querySelector("perspective-viewer") + .notifyResize(); + return window.__WIDGET__.viewer.innerHTML; + }); + } + ); - test.capture("row_pivots traitlet works", async page => { - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); + test.capture("row_pivots traitlet works", async (page) => { + await page.waitForSelector( + "perspective-viewer:not([updating])" + ); + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); return await page.evaluate(async () => { await window.__WIDGET__.restore({row_pivots: ["State"]}); return window.__WIDGET__.viewer.innerHTML; diff --git a/packages/perspective-jupyterlab/test/jupyter/utils.js b/packages/perspective-jupyterlab/test/jupyter/utils.js index d2dcc99d17..106a21ba4b 100644 --- a/packages/perspective-jupyterlab/test/jupyter/utils.js +++ b/packages/perspective-jupyterlab/test/jupyter/utils.js @@ -39,8 +39,14 @@ const generate_notebook = (notebook_name, cells) => { execution_count: null, metadata: {}, outputs: [], - source: ["import perspective\n", "import pandas as pd\n", "import numpy as np\n", "arrow_data = None\n", "with open('test.arrow', 'rb') as arrow: \n arrow_data = arrow.read()"] - } + source: [ + "import perspective\n", + "import pandas as pd\n", + "import numpy as np\n", + "arrow_data = None\n", + "with open('test.arrow', 'rb') as arrow: \n arrow_data = arrow.read()", + ], + }, ]; // Cells defined in the test as an array of arrays - each inner array @@ -51,7 +57,7 @@ const generate_notebook = (notebook_name, cells) => { execution_count: null, metadata: {}, outputs: [], - source: cell + source: cell, }); } @@ -71,7 +77,11 @@ describe.jupyter = (body, {name, root} = {}) => { // URL is null because each test.capture_jupyterlab will have its own // unique notebook generated. - return describe.page(null, body, {check_results: false, name: name, root: root}); + return describe.page(null, body, { + check_results: false, + name: name, + root: root, + }); }; /** @@ -85,17 +95,19 @@ test.jupyterlab = async (name, cells, body, args = {}) => { const notebook_name = `${name.replace(/[ \.']/g, "_")}.ipynb`; generate_notebook(notebook_name, cells); args = Object.assign(args, { - url: `doc/tree/${notebook_name}` + url: `doc/tree/${notebook_name}`, }); await test.run(name, body, args); }; module.exports = { - execute_all_cells: async page => { + execute_all_cells: async (page) => { await page.waitForFunction(async () => !!document.title); await page.waitForSelector(".p-Widget", {visible: true}); - await page.waitForSelector(".jp-NotebookPanel-toolbar", {visible: true}); + await page.waitForSelector(".jp-NotebookPanel-toolbar", { + visible: true, + }); await page.waitForTimeout(1000); // Use our custom keyboard shortcut to run all cells @@ -103,5 +115,5 @@ module.exports = { await page.keyboard.press("R"); await page.evaluate(() => (document.scrollTop = 0)); - } + }, }; diff --git a/packages/perspective-jupyterlab/test/jupyter/widget.spec.js b/packages/perspective-jupyterlab/test/jupyter/widget.spec.js index 6eacf11465..684512c8ae 100644 --- a/packages/perspective-jupyterlab/test/jupyter/widget.spec.js +++ b/packages/perspective-jupyterlab/test/jupyter/widget.spec.js @@ -11,10 +11,13 @@ const path = require("path"); const utils = require("@finos/perspective-test"); const {execute_all_cells} = require("./utils"); -const default_body = async page => { +const default_body = async (page) => { await execute_all_cells(page); - const viewer = await page.waitForSelector(".jp-OutputArea-output perspective-viewer", {visible: true}); - await viewer.evaluate(async viewer => await viewer.flush()); + const viewer = await page.waitForSelector( + ".jp-OutputArea-output perspective-viewer", + {visible: true} + ); + await viewer.evaluate(async (viewer) => await viewer.flush()); return viewer; }; @@ -23,17 +26,26 @@ utils.with_jupyterlab(process.env.__JUPYTERLAB_PORT__, () => { () => { test.jupyterlab( "Loads a table", - [["table = perspective.Table(arrow_data)", "w = perspective.PerspectiveWidget(table, columns=['f64', 'str', 'datetime'])"].join("\n"), "w"], - async page => { + [ + [ + "table = perspective.Table(arrow_data)", + "w = perspective.PerspectiveWidget(table, columns=['f64', 'str', 'datetime'])", + ].join("\n"), + "w", + ], + async (page) => { const viewer = await default_body(page); - const num_columns = await viewer.evaluate(async viewer => { - const tbl = viewer.querySelector("regular-table"); - return tbl.querySelector("thead tr").childElementCount; - }); + const num_columns = await viewer.evaluate( + async (viewer) => { + const tbl = viewer.querySelector("regular-table"); + return tbl.querySelector("thead tr") + .childElementCount; + } + ); expect(num_columns).toEqual(3); - const num_rows = await viewer.evaluate(async viewer => { + const num_rows = await viewer.evaluate(async (viewer) => { const tbl = viewer.querySelector("regular-table"); return tbl.querySelectorAll("tbody tr").length; }); diff --git a/packages/perspective-jupyterlab/tsconfig.json b/packages/perspective-jupyterlab/tsconfig.json deleted file mode 100644 index 1a2e367ddc..0000000000 --- a/packages/perspective-jupyterlab/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": true, - "noEmitOnError": true, - "noUnusedLocals": true, - "module": "commonjs", - "moduleResolution": "node", - "allowJs": true, - "target": "es2016", - "outDir": "./build", - "lib": [ - "es2016", - "ES2015.Promise", - "DOM" - ], - "types":[ - "node" - ] - }, - "include": ["package.json", "src/ts/*"] -} diff --git a/packages/perspective-test/babel.config.js b/packages/perspective-test/babel.config.js index 52dd438155..5c7e5ffc0a 100644 --- a/packages/perspective-test/babel.config.js +++ b/packages/perspective-test/babel.config.js @@ -8,13 +8,13 @@ module.exports = { node: "8", ios: "12", safari: "12", - edge: "44" + edge: "44", }, modules: false, useBuiltIns: "usage", - corejs: 3 - } - ] + corejs: 3, + }, + ], ], sourceType: "unambiguous", plugins: [ @@ -22,6 +22,6 @@ module.exports = { ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-optional-chaining", - "@babel/plugin-proposal-class-properties" - ] + "@babel/plugin-proposal-class-properties", + ], }; diff --git a/packages/perspective-test/jest.all.config.js b/packages/perspective-test/jest.all.config.js index 5c0cf9ce65..9cd29f749d 100644 --- a/packages/perspective-test/jest.all.config.js +++ b/packages/perspective-test/jest.all.config.js @@ -5,7 +5,7 @@ module.exports = { "packages/perspective-viewer-datagrid/test/js", "packages/perspective-viewer-d3fc/test/js", "packages/perspective-workspace/test/js", - "packages/perspective-jupyterlab/test/js" + "packages/perspective-jupyterlab/test/js", ], verbose: true, testURL: "http://localhost/", @@ -13,7 +13,7 @@ module.exports = { ".js$": "@finos/perspective-test/src/js/transform.js", ".html$": "html-loader-jest", // Transform typescript for perspective-jupyterlab - ".ts": "ts-jest" + ".ts": "ts-jest", }, collectCoverage: true, collectCoverageFrom: ["packages/perspective/dist/cjs/**"], @@ -21,10 +21,16 @@ module.exports = { coverageReporters: ["cobertura", "text"], // perspective-jupyterlab tests mock `@jupyter-widgets`, which is in // Typescript. - transformIgnorePatterns: ["/node_modules/(?!(lit-html|@jupyter-widgets)/).+$"], + transformIgnorePatterns: [ + "/node_modules/(?!(lit-html|@jupyter-widgets)/).+$", + ], automock: false, setupFiles: ["@finos/perspective-test/src/js/beforeEachSpec.js"], - reporters: ["default", "@finos/perspective-test/src/js/reporter.js", "jest-junit"], + reporters: [ + "default", + "@finos/perspective-test/src/js/reporter.js", + "jest-junit", + ], globalSetup: "@finos/perspective-test/src/js/globalSetup.js", - globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js" + globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js", }; diff --git a/packages/perspective-test/jest.config.js b/packages/perspective-test/jest.config.js index b001a02ea3..e4f9a93506 100644 --- a/packages/perspective-test/jest.config.js +++ b/packages/perspective-test/jest.config.js @@ -13,12 +13,12 @@ module.exports = { testURL: "http://localhost/", transform: { ".js$": "@finos/perspective-test/src/js/transform.js", - ".html$": "html-loader-jest" + ".html$": "html-loader-jest", }, transformIgnorePatterns: ["/node_modules/(?!lit-html).+\\.js"], automock: false, setupFiles: ["@finos/perspective-test/src/js/beforeEachSpec.js"], reporters: ["default", "@finos/perspective-test/src/js/reporter.js"], globalSetup: "@finos/perspective-test/src/js/globalSetup.js", - globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js" + globalTeardown: "@finos/perspective-test/src/js/globalTeardown.js", }; diff --git a/packages/perspective-test/package.json b/packages/perspective-test/package.json index 1a2fc93c25..f4cd4dd4ee 100644 --- a/packages/perspective-test/package.json +++ b/packages/perspective-test/package.json @@ -25,4 +25,4 @@ "core-js": "^3.6.4", "xml-formatter": "2.4.0" } -} \ No newline at end of file +} diff --git a/packages/perspective-test/src/js/globalSetup.js b/packages/perspective-test/src/js/globalSetup.js index b26d4d773e..b19ef4b42e 100644 --- a/packages/perspective-test/src/js/globalSetup.js +++ b/packages/perspective-test/src/js/globalSetup.js @@ -8,7 +8,7 @@ */ const puppeteer = require("puppeteer"); -module.exports = async function() { +module.exports = async function () { let args = [ `--window-size=1280,1024`, "--disable-accelerated-2d-canvas", @@ -18,7 +18,7 @@ module.exports = async function() { "--disable-dev-shm-usage", "--font-render-hinting=medium", '--proxy-server="direct://"', - "--proxy-bypass-list=*" + "--proxy-bypass-list=*", ]; global.__BROWSER__ = await puppeteer.launch({ @@ -27,7 +27,7 @@ module.exports = async function() { // https://github.com/puppeteer/puppeteer/issues/1183 defaultViewport: null, - args + args, }); process.env.PSP_BROWSER_ENDPOINT = global.__BROWSER__.wsEndpoint(); }; diff --git a/packages/perspective-test/src/js/globalTeardown.js b/packages/perspective-test/src/js/globalTeardown.js index 64a6d16c97..00454415e7 100644 --- a/packages/perspective-test/src/js/globalTeardown.js +++ b/packages/perspective-test/src/js/globalTeardown.js @@ -6,6 +6,6 @@ * the Apache License 2.0. The full license can be found in the LICENSE file. * */ -module.exports = async function() { +module.exports = async function () { await global.__BROWSER__.close(); }; diff --git a/packages/perspective-test/src/js/index.js b/packages/perspective-test/src/js/index.js index d3253bdd82..f9028ccc12 100644 --- a/packages/perspective-test/src/js/index.js +++ b/packages/perspective-test/src/js/index.js @@ -23,7 +23,11 @@ const cp = require("child_process"); const {WebSocketServer} = require("@finos/perspective"); -const {IS_LOCAL_PUPPETEER, RESULTS_DEBUG_FILENAME, RESULTS_FILENAME} = require("./paths.js"); +const { + IS_LOCAL_PUPPETEER, + RESULTS_DEBUG_FILENAME, + RESULTS_FILENAME, +} = require("./paths.js"); let __PORT__; @@ -38,7 +42,7 @@ exports.with_server = function with_server({paths}, body) { port: 0, on_start: () => { __PORT__ = server._server.address().port; - } + }, }); }); @@ -69,12 +73,14 @@ async function get_new_page() { page = await browser.newPage(); // https://github.com/puppeteer/puppeteer/issues/1718 - await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"); + await page.setUserAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" + ); await page.setRequestInterception(true); // Webfonts cause tests to render inconcistently (or block) when run in a // firewalled environment, so for consistency abort these requests. - page.on("request", interceptedRequest => { + page.on("request", (interceptedRequest) => { if (interceptedRequest.url().indexOf("googleapis") > -1) { interceptedRequest.abort(); } else { @@ -84,13 +90,13 @@ async function get_new_page() { // Disable all alerts and dialogs, as Jupyterlab alerts when trying to // navigate off the page, which will block test completion. - page.on("dialog", async dialog => { + page.on("dialog", async (dialog) => { await dialog.accept(); }); page.track_mouse = track_mouse.bind(page); - page.shadow_click = async function(...path) { - await this.evaluate(path => { + page.shadow_click = async function (...path) { + await this.evaluate((path) => { let elem = document; while (path.length > 0) { if (elem.shadowRoot) { @@ -112,7 +118,7 @@ async function get_new_page() { }, path); }; - page.shadow_type = async function(content, is_incremental, ...path) { + page.shadow_type = async function (content, is_incremental, ...path) { if (typeof is_incremental !== "boolean") { path.unshift(is_incremental); is_incremental = false; @@ -133,7 +139,7 @@ async function get_new_page() { function triggerInputEvent(element) { const event = new Event("input", { bubbles: true, - cancelable: true + cancelable: true, }); element.dispatchEvent(event); @@ -157,7 +163,7 @@ async function get_new_page() { ); }; - page.shadow_blur = async function() { + page.shadow_blur = async function () { await this.evaluate(() => { let elem = document.activeElement; while (elem) { @@ -167,8 +173,8 @@ async function get_new_page() { }); }; - page.shadow_focus = async function(...path) { - await this.evaluate(path => { + page.shadow_focus = async function (...path) { + await this.evaluate((path) => { let elem = document; while (path.length > 0) { if (elem.shadowRoot) { @@ -184,23 +190,25 @@ async function get_new_page() { // CSS Animations break our screenshot tests, so set the // animation playback rate to something extreme. await page._client.send("Animation.setPlaybackRate", {playbackRate: 100.0}); - page.on("console", async msg => { + page.on("console", async (msg) => { if (msg.type() === "error" || msg.type() === "warn") { const args = await msg.args(); - args.forEach(async arg => { + args.forEach(async (arg) => { const val = await arg.jsonValue(); // value is serializable - if (JSON.stringify(val) !== JSON.stringify({})) console.log(val); + if (JSON.stringify(val) !== JSON.stringify({})) + console.log(val); // value is unserializable (or an empty oject) else { const {type, subtype, description} = arg._remoteObject; private_console.log(`${subtype}: ${description}`); - if (msg.type() === "error") errors.push(`${type}/${subtype}: ${description}`); + if (msg.type() === "error") + errors.push(`${type}/${subtype}: ${description}`); } }); } }); - page.on("pageerror", msg => { + page.on("pageerror", (msg) => { errors.push(msg.message); }); return page; @@ -216,9 +224,11 @@ function get_results(filename) { } } -beforeAll(async done => { +beforeAll(async (done) => { try { - browser = await puppeteer.connect({browserWSEndpoint: process.env.PSP_BROWSER_ENDPOINT}); + browser = await puppeteer.connect({ + browserWSEndpoint: process.env.PSP_BROWSER_ENDPOINT, + }); page = await get_new_page(); @@ -228,7 +238,9 @@ beforeAll(async done => { if (results.__GIT_COMMIT__) { const hash = execSync(`git cat-file -e ${results.__GIT_COMMIT__}`); if (!hash || hash.toString().length != 0) { - private_console.error(`-- WARNING - Test results generated from non-existent commit ${results.__GIT_COMMIT__}.`); + private_console.error( + `-- WARNING - Test results generated from non-existent commit ${results.__GIT_COMMIT__}.` + ); } } } catch (e) { @@ -250,9 +262,7 @@ function write_results(updated, filename) { for (let key of Object.keys(updated)) { results2[key] = updated[key]; } - results2.__GIT_COMMIT__ = execSync("git rev-parse HEAD") - .toString() - .trim(); + results2.__GIT_COMMIT__ = execSync("git rev-parse HEAD").toString().trim(); fs.writeFileSync(dir_name, JSON.stringify(results2, null, 4)); } @@ -283,7 +293,11 @@ function mkdirSyncRec(targetDir) { }, initDir); } -describe.page = (url, body, {reload_page = false, check_results = true, name, root} = {}) => { +describe.page = ( + url, + body, + {reload_page = false, check_results = true, name, root} = {} +) => { let _url = url ? url : page_url; test_root = root ? root : test_root; @@ -298,7 +312,14 @@ describe.page = (url, body, {reload_page = false, check_results = true, name, ro return result; }); - if (IS_LOCAL_PUPPETEER && !fs.existsSync(path.join(test_root, "test", "results", RESULTS_FILENAME)) && !process.env.WRITE_TESTS && check_results) { + if ( + IS_LOCAL_PUPPETEER && + !fs.existsSync( + path.join(test_root, "test", "results", RESULTS_FILENAME) + ) && + !process.env.WRITE_TESTS && + check_results + ) { throw new Error(` ERROR: Running in puppeteer tests without "${RESULTS_FILENAME}" @@ -316,31 +337,40 @@ expect.extend({ toNotError(received) { if (received.length > 0) { return { - message: () => `Errors emitted during evaluation: \n${received.map(x => ` ${x}`).join("\n")}`, - pass: false + message: () => + `Errors emitted during evaluation: \n${received + .map((x) => ` ${x}`) + .join("\n")}`, + pass: false, }; } return { message: () => ``, - pass: true + pass: true, }; - } + }, }); -test.run = function run(name, body, {url = page_url, timeout = 60000, viewport = null}) { +test.run = function run( + name, + body, + {url = page_url, timeout = 60000, viewport = null} +) { test( name, async () => { if (viewport !== null) { await page.setViewport({ width: viewport.width, - height: viewport.height + height: viewport.height, }); } await new Promise(setTimeout); await page.close(); page = await get_new_page(); - await page.goto(`http://127.0.0.1:${__PORT__}/${url}`, {waitUntil: "domcontentloaded"}); + await page.goto(`http://127.0.0.1:${__PORT__}/${url}`, { + waitUntil: "domcontentloaded", + }); await body(page); }, timeout @@ -366,14 +396,23 @@ function format_and_clean_xml(result) { } return true; - } + }, }); } catch (e) { return result; } } -test.capture = function capture(name, body, {url = page_url, timeout = 60000, viewport = null, fail_on_errors = true} = {}) { +test.capture = function capture( + name, + body, + { + url = page_url, + timeout = 60000, + viewport = null, + fail_on_errors = true, + } = {} +) { const spec = test( name, async () => { @@ -382,15 +421,25 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi if (viewport !== null) await page.setViewport({ width: viewport.width, - height: viewport.height + height: viewport.height, }); const iterations = process.env.PSP_SATURATE ? 10 : 1; const test_name = `${name.replace(/[ \.']/g, "_")}`; - const path_name = `${spec.result.fullName.replace(".html", "").replace(/[ \.']/g, "_")}`; - let dir_name = path.join(test_root, "test", "screenshots", path_name); - dir_name = dir_name.slice(0, dir_name.length - test_name.length - 1); + const path_name = `${spec.result.fullName + .replace(".html", "") + .replace(/[ \.']/g, "_")}`; + let dir_name = path.join( + test_root, + "test", + "screenshots", + path_name + ); + dir_name = dir_name.slice( + 0, + dir_name.length - test_name.length - 1 + ); const filename = path.join(dir_name, test_name); if (!fs.existsSync(dir_name)) { mkdirSyncRec(dir_name); @@ -400,14 +449,22 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi if (!OLD_SETTINGS[test_root + url]) { await page.close(); page = await get_new_page(); - await page.goto(`http://127.0.0.1:${__PORT__}/${url}#test=${encodeURIComponent(name)}`, {waitUntil: "domcontentloaded"}); + await page.goto( + `http://127.0.0.1:${__PORT__}/${url}#test=${encodeURIComponent( + name + )}`, + {waitUntil: "domcontentloaded"} + ); } else { - await page.evaluate(async x => { - const workspace = document.querySelector("perspective-workspace"); + await page.evaluate(async (x) => { + const workspace = document.querySelector( + "perspective-workspace" + ); if (workspace) { await workspace.restore(x); } else { - const viewer = document.querySelector("perspective-viewer"); + const viewer = + document.querySelector("perspective-viewer"); if (viewer) { viewer.restore(x); await viewer.notifyResize?.(); @@ -418,19 +475,26 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi } if (!OLD_SETTINGS[test_root + url]) { - OLD_SETTINGS[test_root + url] = await page.evaluate(async () => { - const workspace = document.querySelector("perspective-workspace"); - if (workspace) { - return await workspace.save(); - } else { - const viewer = document.querySelector("perspective-viewer"); - if (viewer) { - await viewer.getTable(); - await viewer.restore({}); - return await viewer.save(); + OLD_SETTINGS[test_root + url] = await page.evaluate( + async () => { + const workspace = document.querySelector( + "perspective-workspace" + ); + if (workspace) { + return await workspace.save(); + } else { + const viewer = + document.querySelector( + "perspective-viewer" + ); + if (viewer) { + await viewer.getTable(); + await viewer.restore({}); + return await viewer.save(); + } } } - }); + ); } // Move the mouse offscreen so prev tests dont get hover effects @@ -442,7 +506,9 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi if (process.env.PSP_PAUSE_ON_FAILURE) { if (!process.env.WRITE_TESTS) { private_console.error(`Failed ${name}, pausing`); - await prompt(`Failed ${name}, pausing. Press enter to continue ..`); + await prompt( + `Failed ${name}, pausing. Press enter to continue ..` + ); } } throw e; @@ -458,22 +524,33 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi if (hash === results[path_name]) { if (!fs.existsSync(filename + ".png")) { - const screenshot = await page.screenshot({captureBeyondViewport: false, fullPage: true}); + const screenshot = await page.screenshot({ + captureBeyondViewport: false, + fullPage: true, + }); fs.writeFileSync(filename + ".png", screenshot); } } else { - const screenshot = await page.screenshot({captureBeyondViewport: false, fullPage: true}); + const screenshot = await page.screenshot({ + captureBeyondViewport: false, + fullPage: true, + }); fs.writeFileSync(filename + ".failed.png", screenshot); if (fs.existsSync(filename + ".png")) { try { - cp.execSync(`compare ${filename}.png ${filename}.failed.png ${filename}.diff.png`); + cp.execSync( + `compare ${filename}.png ${filename}.failed.png ${filename}.diff.png` + ); } catch (e) { // exits 1 } } if (process.env.WRITE_TESTS) { - const screenshot = await page.screenshot({captureBeyondViewport: false, fullPage: true}); + const screenshot = await page.screenshot({ + captureBeyondViewport: false, + fullPage: true, + }); fs.writeFileSync(filename + ".png", screenshot); } } @@ -484,9 +561,14 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi new_debug_results[path_name] = result; if (process.env.PSP_PAUSE_ON_FAILURE) { - if (!process.env.WRITE_TESTS && (hash !== results[path_name] || errors.length > 0)) { + if ( + !process.env.WRITE_TESTS && + (hash !== results[path_name] || errors.length > 0) + ) { private_console.error(`Failed ${name}, pausing`); - await prompt(`Failed ${name}, pausing. Press enter to continue ..`); + await prompt( + `Failed ${name}, pausing. Press enter to continue ..` + ); } } if (fail_on_errors) { @@ -494,7 +576,9 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi } if (results_debug[path_name]) { - expect(new_debug_results[path_name]).toBe(results_debug[path_name]); + expect(new_debug_results[path_name]).toBe( + results_debug[path_name] + ); } expect(hash).toBe(results[path_name]); @@ -509,11 +593,11 @@ test.capture = function capture(name, body, {url = page_url, timeout = 60000, vi function prompt(query) { const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); - return new Promise(resolve => - rl.question(query, ans => { + return new Promise((resolve) => + rl.question(query, (ans) => { rl.close(); resolve(ans); }) @@ -527,9 +611,13 @@ exports.drag_drop = async function drag_drop(page, origin, target) { const element2 = await page.$(target); const box2 = await element2.boundingBox(); process.stdout.write(element2, box2); - await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, {steps: 100}); + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { + steps: 100, + }); await page.mouse.down(); await page.waitFor(1000); - await page.mouse.move(box2.x + box2.width / 2, box2.y + box2.height / 2, {steps: 100}); + await page.mouse.move(box2.x + box2.width / 2, box2.y + box2.height / 2, { + steps: 100, + }); await page.mouse.up(); }; diff --git a/packages/perspective-test/src/js/mouse_helper.js b/packages/perspective-test/src/js/mouse_helper.js index 6798e9fa9b..dc6f42152c 100644 --- a/packages/perspective-test/src/js/mouse_helper.js +++ b/packages/perspective-test/src/js/mouse_helper.js @@ -58,7 +58,7 @@ module.exports.track_mouse = function track_mouse() { document.body.appendChild(box); document.addEventListener( "mousemove", - event => { + (event) => { box.style.left = event.pageX + "px"; box.style.top = event.pageY + "px"; updateButtons(event.buttons); @@ -67,7 +67,7 @@ module.exports.track_mouse = function track_mouse() { ); document.addEventListener( "mousedown", - event => { + (event) => { updateButtons(event.buttons); box.classList.add("button-" + event.which); }, @@ -75,14 +75,15 @@ module.exports.track_mouse = function track_mouse() { ); document.addEventListener( "mouseup", - event => { + (event) => { updateButtons(event.buttons); box.classList.remove("button-" + event.which); }, true ); function updateButtons(buttons) { - for (let i = 0; i < 5; i++) box.classList.toggle("button-" + i, buttons & (1 << i)); + for (let i = 0; i < 5; i++) + box.classList.toggle("button-" + i, buttons & (1 << i)); } }); }; diff --git a/packages/perspective-test/src/js/paths.js b/packages/perspective-test/src/js/paths.js index e5642f6e38..284bf2504f 100644 --- a/packages/perspective-test/src/js/paths.js +++ b/packages/perspective-test/src/js/paths.js @@ -10,7 +10,9 @@ const fs = require("fs"); const path = require("path"); -module.exports.IS_LOCAL_PUPPETEER = fs.existsSync(path.join(__dirname, "..", "..", "..", "..", "node_modules", "puppeteer")); +module.exports.IS_LOCAL_PUPPETEER = fs.existsSync( + path.join(__dirname, "..", "..", "..", "..", "node_modules", "puppeteer") +); module.exports.RESULTS_TAGNAME = `results`; module.exports.RESULTS_FILENAME = `${module.exports.RESULTS_TAGNAME}.json`; module.exports.RESULTS_DEBUG_FILENAME = `${module.exports.RESULTS_TAGNAME}.debug.json`; diff --git a/packages/perspective-test/src/js/reporter.js b/packages/perspective-test/src/js/reporter.js index 915a42058f..09b9da9e6c 100644 --- a/packages/perspective-test/src/js/reporter.js +++ b/packages/perspective-test/src/js/reporter.js @@ -23,11 +23,13 @@ module.exports = class ImageViewerReporter { write_img(title, ancestors, filename) { if (fs.existsSync(filename)) { - process.stdout.write(`\n ${ancestors.join(" > ")} > ${title}\n\n `); + process.stdout.write( + `\n ${ancestors.join(" > ")} > ${title}\n\n ` + ); termimg(filename, { width: "640px", height: "480px", - fallback: () => this.write_img_fallback(filename) + fallback: () => this.write_img_fallback(filename), }); process.stdout.write("\n"); } @@ -40,20 +42,32 @@ module.exports = class ImageViewerReporter { for (const test of testResults.testResults) { if (test.status === "failed") { const name = test.title.replace(/[ \.']/g, "_"); - let desc = test.fullName.replace(".html", "").replace(/ /g, "_"); + let desc = test.fullName + .replace(".html", "") + .replace(/ /g, "_"); desc = desc.slice(0, desc.length - name.length - 1); const candidates = [ - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${paths.RESULTS_TAGNAME}/${desc}/${name}.diff.png`, + `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ + paths.RESULTS_TAGNAME + }/${desc}/${name}.diff.png`, `test/screenshots/${desc}/${name}.diff.png`, - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${paths.RESULTS_TAGNAME}/${desc}/${name}.failed.png`, + `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ + paths.RESULTS_TAGNAME + }/${desc}/${name}.failed.png`, `test/screenshots/${desc}/${name}.failed.png`, - `${testRunConfig.path.split("/test")[0]}/test/screenshots/${paths.RESULTS_TAGNAME}/${desc}/${name}.png`, - `test/screenshots/${desc}/${name}.png` + `${testRunConfig.path.split("/test")[0]}/test/screenshots/${ + paths.RESULTS_TAGNAME + }/${desc}/${name}.png`, + `test/screenshots/${desc}/${name}.png`, ]; for (const filename of candidates) { if (fs.existsSync(filename)) { - this.write_img(test.title, test.ancestorTitles, filename); + this.write_img( + test.title, + test.ancestorTitles, + filename + ); break; } } diff --git a/packages/perspective-viewer-d3fc/babel.config.js b/packages/perspective-viewer-d3fc/babel.config.js index b4fc2ffc80..74d4268de7 100644 --- a/packages/perspective-viewer-d3fc/babel.config.js +++ b/packages/perspective-viewer-d3fc/babel.config.js @@ -6,13 +6,13 @@ module.exports = { targets: { chrome: "70", node: "12", - ios: "13" + ios: "13", }, modules: process.env.BABEL_MODULE || false, useBuiltIns: "usage", - corejs: 3 - } - ] + corejs: 3, + }, + ], ], sourceType: "unambiguous", plugins: [ @@ -20,6 +20,6 @@ module.exports = { ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining" - ] + "@babel/plugin-proposal-optional-chaining", + ], }; diff --git a/packages/perspective-viewer-d3fc/package.json b/packages/perspective-viewer-d3fc/package.json index 90fe6d2b45..7a9a137644 100644 --- a/packages/perspective-viewer-d3fc/package.json +++ b/packages/perspective-viewer-d3fc/package.json @@ -62,4 +62,4 @@ "devDependencies": { "@finos/perspective-test": "^0.10.3" } -} \ No newline at end of file +} diff --git a/packages/perspective-viewer-d3fc/src/config/cjs.config.js b/packages/perspective-viewer-d3fc/src/config/cjs.config.js index aebcdea445..d28faf4564 100644 --- a/packages/perspective-viewer-d3fc/src/config/cjs.config.js +++ b/packages/perspective-viewer-d3fc/src/config/cjs.config.js @@ -1,7 +1,7 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/esm/index.js", externals: [/^[a-z0-9@]/], @@ -9,7 +9,7 @@ module.exports = common({}, config => filename: "perspective-viewer-d3fc.js", library: "perspective-view-d3fc", libraryTarget: "umd", - path: path.resolve(__dirname, "../../dist/cjs") - } + path: path.resolve(__dirname, "../../dist/cjs"), + }, }) ); diff --git a/packages/perspective-viewer-d3fc/src/config/d3fc.watch.config.js b/packages/perspective-viewer-d3fc/src/config/d3fc.watch.config.js index ea29319ef4..0b39fb47c3 100644 --- a/packages/perspective-viewer-d3fc/src/config/d3fc.watch.config.js +++ b/packages/perspective-viewer-d3fc/src/config/d3fc.watch.config.js @@ -4,12 +4,12 @@ const rules = []; //pluginConfig.module.rules.slice(0); rules.push({ test: /\.js$/, exclude: /node_modules/, - loader: "babel-loader" + loader: "babel-loader", }); module.exports = Object.assign({}, pluginConfig, { entry: "./src/js/index.js", module: Object.assign({}, pluginConfig.module, { - rules - }) + rules, + }), }); diff --git a/packages/perspective-viewer-d3fc/src/config/umd.config.js b/packages/perspective-viewer-d3fc/src/config/umd.config.js index c8d82d99d3..0992e4fd1c 100644 --- a/packages/perspective-viewer-d3fc/src/config/umd.config.js +++ b/packages/perspective-viewer-d3fc/src/config/umd.config.js @@ -1,14 +1,14 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/cjs/perspective-viewer-d3fc.js", output: { filename: "perspective-viewer-d3fc.js", library: "perspective-view-d3fc", libraryTarget: "umd", - path: path.resolve(__dirname, "../../dist/umd") - } + path: path.resolve(__dirname, "../../dist/umd"), + }, }) ); diff --git a/packages/perspective-viewer-d3fc/src/html/d3fc-chart.html b/packages/perspective-viewer-d3fc/src/html/d3fc-chart.html index 00e0205938..f06cee5fa7 100644 --- a/packages/perspective-viewer-d3fc/src/html/d3fc-chart.html +++ b/packages/perspective-viewer-d3fc/src/html/d3fc-chart.html @@ -1,4 +1,3 @@ - \ No newline at end of file + diff --git a/packages/perspective-viewer-d3fc/src/html/d3fc.html b/packages/perspective-viewer-d3fc/src/html/d3fc.html index aae9d23e9b..b08cc63130 100644 --- a/packages/perspective-viewer-d3fc/src/html/d3fc.html +++ b/packages/perspective-viewer-d3fc/src/html/d3fc.html @@ -1,5 +1,3 @@ \ No newline at end of file +
+ diff --git a/packages/perspective-viewer-d3fc/src/html/legend-controls.html b/packages/perspective-viewer-d3fc/src/html/legend-controls.html index de4f0f087b..38ce9737b7 100644 --- a/packages/perspective-viewer-d3fc/src/html/legend-controls.html +++ b/packages/perspective-viewer-d3fc/src/html/legend-controls.html @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/packages/perspective-viewer-d3fc/src/html/parent-controls.html b/packages/perspective-viewer-d3fc/src/html/parent-controls.html index 251b739a2d..06fc423384 100644 --- a/packages/perspective-viewer-d3fc/src/html/parent-controls.html +++ b/packages/perspective-viewer-d3fc/src/html/parent-controls.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/perspective-viewer-d3fc/src/html/tooltip.html b/packages/perspective-viewer-d3fc/src/html/tooltip.html index f2872cea3e..7fc46b9558 100644 --- a/packages/perspective-viewer-d3fc/src/html/tooltip.html +++ b/packages/perspective-viewer-d3fc/src/html/tooltip.html @@ -1 +1 @@ -
    \ No newline at end of file +
      diff --git a/packages/perspective-viewer-d3fc/src/html/zoom-controls.html b/packages/perspective-viewer-d3fc/src/html/zoom-controls.html index 39298ec93f..8d56f2dbf2 100644 --- a/packages/perspective-viewer-d3fc/src/html/zoom-controls.html +++ b/packages/perspective-viewer-d3fc/src/html/zoom-controls.html @@ -1,4 +1,4 @@ - \ No newline at end of file + diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js index 3a66de7f31..e8c81e284b 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js @@ -17,10 +17,10 @@ const axisTypes = { none, ordinal, time, - linear + linear, }; -export const axisFactory = settings => { +export const axisFactory = (settings) => { let excludeType = null; let orient = "horizontal"; let settingName = "crossValues"; @@ -30,7 +30,7 @@ export const axisFactory = settings => { const optionalParams = ["include", "paddingStrategy", "pad"]; const optional = {}; - const _factory = data => { + const _factory = (data) => { const useType = axisType(settings) .excludeType(excludeType) .settingName(settingName) @@ -39,13 +39,16 @@ export const axisFactory = settings => { const axis = axisTypes[useType]; const domainFunction = axis.domain().valueNames(valueNames); - optionalParams.forEach(p => { - if (optional[p] && domainFunction[p]) domainFunction[p](optional[p]); + optionalParams.forEach((p) => { + if (optional[p] && domainFunction[p]) + domainFunction[p](optional[p]); }); if (domainFunction.orient) domainFunction.orient(orient); const domain = domainFunction(data); - const component = axis.component ? createComponent(axis, domain, data) : defaultComponent(); + const component = axis.component + ? createComponent(axis, domain, data) + : defaultComponent(); return { scale: axis.scale(), @@ -56,12 +59,12 @@ export const axisFactory = settings => { bottom: component.bottom, left: component.left, top: component.top, - right: component.right + right: component.right, }, size: component.size, decorate: component.decorate, - label: settings[settingName].map(v => v.name).join(", "), - tickFormatFunction: axis.tickFormatFunction + label: settings[settingName].map((v) => v.name).join(", "), + tickFormatFunction: axis.tickFormatFunction, }; }; @@ -77,7 +80,7 @@ export const axisFactory = settings => { left: fc.axisLeft, top: fc.axisTop, right: fc.axisRight, - decorate: () => {} + decorate: () => {}, }); _factory.excludeType = (...args) => { @@ -128,7 +131,7 @@ export const axisFactory = settings => { return _factory; }; - optionalParams.forEach(p => { + optionalParams.forEach((p) => { _factory[p] = (...args) => { if (!args.length) { return optional[p]; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisLabel.js b/packages/perspective-viewer-d3fc/src/js/axis/axisLabel.js index 79babdf92b..fca2363ab3 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisLabel.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisLabel.js @@ -17,10 +17,10 @@ const labelFunctions = { none: noLabel, ordinal: ordinalLabel, time: timeLabel, - linear: linearLabel + linear: linearLabel, }; -export const labelFunction = settings => { +export const labelFunction = (settings) => { const base = axisType(settings); let valueName = "__ROW_PATH__"; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js b/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js index f7c2ce7d22..6009e59d20 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js @@ -8,43 +8,49 @@ */ import {splitterLabels} from "./splitterLabels"; -export const axisSplitter = (settings, sourceData, splitFn = dataSplitFunction) => { +export const axisSplitter = ( + settings, + sourceData, + splitFn = dataSplitFunction +) => { let color; let data; let altData; // splitMainValues is an array of main-value names to put into the alt-axis const splitMainValues = settings.splitMainValues || []; - const altValue = name => { + const altValue = (name) => { const split = name.split("|"); return splitMainValues.includes(split[split.length - 1]); }; - const haveSplit = settings["mainValues"].some(m => altValue(m.name)); + const haveSplit = settings["mainValues"].some((m) => altValue(m.name)); // Split the data into main and alt displays - data = haveSplit ? splitFn(sourceData, key => !altValue(key)) : sourceData; + data = haveSplit + ? splitFn(sourceData, (key) => !altValue(key)) + : sourceData; altData = haveSplit ? splitFn(sourceData, altValue) : null; // Renderer to show the special controls for moving between axes - const splitter = selection => { + const splitter = (selection) => { if (settings["mainValues"].length === 1) return; const labelsInfo = settings["mainValues"].map((v, i) => ({ index: i, - name: v.name + name: v.name, })); - const mainLabels = labelsInfo.filter(v => !altValue(v.name)); - const altLabels = labelsInfo.filter(v => altValue(v.name)); + const mainLabels = labelsInfo.filter((v) => !altValue(v.name)); + const altLabels = labelsInfo.filter((v) => altValue(v.name)); const labeller = () => splitterLabels(settings).color(color); - selection.select(".y-label-container>.y-label").call(labeller().labels(mainLabels)); - selection.select(".y2-label-container>.y-label").call( - labeller() - .labels(altLabels) - .alt(true) - ); + selection + .select(".y-label-container>.y-label") + .call(labeller().labels(mainLabels)); + selection + .select(".y2-label-container>.y-label") + .call(labeller().labels(altLabels).alt(true)); }; splitter.color = (...args) => { @@ -76,19 +82,19 @@ export const axisSplitter = (settings, sourceData, splitFn = dataSplitFunction) }; export const dataSplitFunction = (sourceData, isIncludedFn) => { - return sourceData.map(d => d.filter(v => isIncludedFn(v.key))); + return sourceData.map((d) => d.filter((v) => isIncludedFn(v.key))); }; export const dataBlankFunction = (sourceData, isIncludedFn) => { - return sourceData.map(series => { + return sourceData.map((series) => { if (!isIncludedFn(series.key)) { // Blank this data - return series.map(v => Object.assign({}, v, {mainValue: null})); + return series.map((v) => Object.assign({}, v, {mainValue: null})); } return series; }); }; export const groupedBlankFunction = (sourceData, isIncludedFn) => { - return sourceData.map(group => dataBlankFunction(group, isIncludedFn)); + return sourceData.map((group) => dataBlankFunction(group, isIncludedFn)); }; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisType.js b/packages/perspective-viewer-d3fc/src/js/axis/axisType.js index da34d09595..c11ae2a81a 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisType.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisType.js @@ -11,31 +11,39 @@ export const AXIS_TYPES = { none: "none", ordinal: "ordinal", time: "time", - linear: "linear" + linear: "linear", }; -export const axisType = settings => { +export const axisType = (settings) => { let settingName = "crossValues"; let settingValue = null; let excludeType = null; const getType = () => { - const checkTypes = types => { - const list = settingValue ? settings[settingName].filter(s => settingValue == s.name) : settings[settingName]; + const checkTypes = (types) => { + const list = settingValue + ? settings[settingName].filter((s) => settingValue == s.name) + : settings[settingName]; if (settingName == "crossValues" && list.length > 1) { // can't do multiple values on non-ordinal cross-axis return false; } - return list.some(s => types.includes(s.type)); + return list.some((s) => types.includes(s.type)); }; if (settings[settingName].length === 0) { return AXIS_TYPES.none; - } else if (excludeType != AXIS_TYPES.time && checkTypes(["datetime", "date"])) { + } else if ( + excludeType != AXIS_TYPES.time && + checkTypes(["datetime", "date"]) + ) { return AXIS_TYPES.time; - } else if (excludeType != AXIS_TYPES.linear && checkTypes(["integer", "float"])) { + } else if ( + excludeType != AXIS_TYPES.linear && + checkTypes(["integer", "float"]) + ) { return AXIS_TYPES.linear; } diff --git a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js index 15342d1829..840d8bd55c 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js @@ -9,8 +9,10 @@ import * as d3 from "d3"; import * as fc from "d3fc"; -export const chartSvgFactory = (xAxis, yAxis) => chartFactory(xAxis, yAxis, fc.chartSvgCartesian, false); -export const chartCanvasFactory = (xAxis, yAxis) => chartFactory(xAxis, yAxis, fc.chartCanvasCartesian, true); +export const chartSvgFactory = (xAxis, yAxis) => + chartFactory(xAxis, yAxis, fc.chartSvgCartesian, false); +export const chartCanvasFactory = (xAxis, yAxis) => + chartFactory(xAxis, yAxis, fc.chartCanvasCartesian, true); const chartFactory = (xAxis, yAxis, cartesian, canvas) => { let axisSplitter = null; @@ -20,7 +22,7 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { xScale: xAxis.scale, yScale: yAxis.scale, xAxis: xAxis.component, - yAxis: yAxis.component + yAxis: yAxis.component, }) .xDomain(xAxis.domain) .xLabel(xAxis.label) @@ -64,7 +66,10 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { const plotArea = container.select("d3fc-svg.plot-area"); const node = plotArea.select("svg").node(); - node.setAttribute("viewBox", `0 0 ${plotArea.node().clientWidth} ${plotArea.node().clientHeight}`); + node.setAttribute( + "viewBox", + `0 0 ${plotArea.node().clientWidth} ${plotArea.node().clientHeight}` + ); node.setAttribute("preserveAspectRatio", "none"); for (const axis of ["x-axis", "y-axis"]) { @@ -81,8 +86,10 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { // Render a second axis on the right of the chart const altData = axisSplitter.altData(); - const y2AxisDataJoin = fc.dataJoin("d3fc-svg", "y2-axis").key(d => d); - const ySeriesDataJoin = fc.dataJoin("g", "y-series").key(d => d); + const y2AxisDataJoin = fc + .dataJoin("d3fc-svg", "y2-axis") + .key((d) => d); + const ySeriesDataJoin = fc.dataJoin("g", "y-series").key((d) => d); // Column 5 of the grid container @@ -108,7 +115,7 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { // Render the axis y2AxisDataJoin(container, ["right"]) - .attr("class", d => `y-axis ${d}-axis`) + .attr("class", (d) => `y-axis ${d}-axis`) .on("measure", (d, i, nodes) => { const {width, height} = d3.event.detail; if (d === "left") { @@ -120,17 +127,17 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { y2Scale.range([height, 0]); }) .on("draw", (d, i, nodes) => { - d3.select(nodes[i]) - .select("svg") - .call(yAxisComponent); + d3.select(nodes[i]).select("svg").call(yAxisComponent); }); // Render all the series using either the primary or alternate // y-scales if (canvas) { - const drawMultiCanvasSeries = selection => { + const drawMultiCanvasSeries = (selection) => { const canvasPlotArea = chart.plotArea(); - canvasPlotArea.context(selection.node().getContext("2d")).xScale(xAxis.scale); + canvasPlotArea + .context(selection.node().getContext("2d")) + .xScale(xAxis.scale); const yScales = [yAxis.scale, y2Scale]; [data, altData].forEach((d, i) => { @@ -139,26 +146,32 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { }); }; - container.select("d3fc-canvas.plot-area").on("draw", (d, i, nodes) => { - drawMultiCanvasSeries(d3.select(nodes[i]).select("canvas")); - }); + container + .select("d3fc-canvas.plot-area") + .on("draw", (d, i, nodes) => { + drawMultiCanvasSeries( + d3.select(nodes[i]).select("canvas") + ); + }); } else { - const drawMultiSvgSeries = selection => { + const drawMultiSvgSeries = (selection) => { const svgPlotArea = chart.plotArea(); svgPlotArea.xScale(xAxis.scale); const yScales = [yAxis.scale, y2Scale]; - ySeriesDataJoin(selection, [data, altData]).each((d, i, nodes) => { - svgPlotArea.yScale(yScales[i]); - d3.select(nodes[i]) - .datum(d) - .call(svgPlotArea); - }); + ySeriesDataJoin(selection, [data, altData]).each( + (d, i, nodes) => { + svgPlotArea.yScale(yScales[i]); + d3.select(nodes[i]).datum(d).call(svgPlotArea); + } + ); }; - container.select("d3fc-svg.plot-area").on("draw", (d, i, nodes) => { - drawMultiSvgSeries(d3.select(nodes[i]).select("svg")); - }); + container + .select("d3fc-svg.plot-area") + .on("draw", (d, i, nodes) => { + drawMultiSvgSeries(d3.select(nodes[i]).select("svg")); + }); } } diff --git a/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js b/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js index dd0b0387b3..d552eea1a4 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js @@ -11,6 +11,10 @@ export default (domain1, domain2) => { } }; -const isMatchable = domain => domain.length === 2 && !isNaN(domain[0]) && !isNaN(domain[1]) && domain[0] !== domain[1]; -const originRatio = domain => (0 - domain[0]) / (domain[1] - domain[0]); +const isMatchable = (domain) => + domain.length === 2 && + !isNaN(domain[0]) && + !isNaN(domain[1]) && + domain[0] !== domain[1]; +const originRatio = (domain) => (0 - domain[0]) / (domain[1] - domain[0]); const adjustLowerBound = (domain, ratio) => (ratio * domain[1]) / (ratio - 1); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/flatten.js b/packages/perspective-viewer-d3fc/src/js/axis/flatten.js index e8dba048cd..6e0e95e7f2 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/flatten.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/flatten.js @@ -7,16 +7,22 @@ * */ -export const flattenExtent = array => { - const withUndefined = fn => (a, b) => { +export const flattenExtent = (array) => { + const withUndefined = (fn) => (a, b) => { if (a === undefined) return b; if (b === undefined) return a; return fn(a, b); }; - return array.reduce((r, v) => [withUndefined(Math.min)(r[0], v[0]), withUndefined(Math.max)(r[1], v[1])], [undefined, undefined]); + return array.reduce( + (r, v) => [ + withUndefined(Math.min)(r[0], v[0]), + withUndefined(Math.max)(r[1], v[1]), + ], + [undefined, undefined] + ); }; -export const flattenArray = array => { +export const flattenArray = (array) => { if (Array.isArray(array)) { return [].concat(...array.map(flattenArray)); } else { diff --git a/packages/perspective-viewer-d3fc/src/js/axis/linearAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/linearAxis.js index 9cf6acbae6..0b7ec02f60 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/linearAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/linearAxis.js @@ -15,33 +15,35 @@ import valueformatter from "./valueFormatter"; export const scale = () => d3.scaleLinear(); export const domain = () => { - const base = customExtent() - .pad([0, 0.1]) - .padUnit("percent"); + const base = customExtent().pad([0, 0.1]).padUnit("percent"); let valueNames = ["crossValue"]; - const _domain = data => { - base.accessors(valueNames.map(v => d => parseFloat(d[v]))); + const _domain = (data) => { + base.accessors(valueNames.map((v) => (d) => parseFloat(d[v]))); return getDataExtent(flattenArray(data)); }; fc.rebindAll(_domain, base); - const getMinimumGap = data => { - const gaps = valueNames.map(valueName => + const getMinimumGap = (data) => { + const gaps = valueNames.map((valueName) => data - .map(d => d[valueName]) + .map((d) => d[valueName]) .sort((a, b) => a - b) .filter((d, i, a) => i === 0 || d !== a[i - 1]) - .reduce((acc, d, i, src) => (i === 0 || acc <= d - src[i - 1] ? acc : Math.abs(d - src[i - 1]))) + .reduce((acc, d, i, src) => + i === 0 || acc <= d - src[i - 1] + ? acc + : Math.abs(d - src[i - 1]) + ) ); return Math.min(...gaps); }; - const getDataExtent = data => { + const getDataExtent = (data) => { if (base.padUnit() == "domain") { const dataWidth = getMinimumGap(data); return base.pad([dataWidth / 2, dataWidth / 2])(data); @@ -68,6 +70,6 @@ export const domain = () => { return _domain; }; -export const labelFunction = valueName => d => d[valueName][0]; +export const labelFunction = (valueName) => (d) => d[valueName][0]; export const tickFormatFunction = valueformatter; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/minBandwidth.js b/packages/perspective-viewer-d3fc/src/js/axis/minBandwidth.js index 4d9251e70d..67fb81f61e 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/minBandwidth.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/minBandwidth.js @@ -10,8 +10,8 @@ import {rebindAll} from "d3fc"; const MIN_BANDWIDTH = 1; -export default adaptee => { - const minBandwidth = arg => { +export default (adaptee) => { + const minBandwidth = (arg) => { return adaptee(arg); }; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/noAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/noAxis.js index f03b5efcc2..8c9fea1f33 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/noAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/noAxis.js @@ -17,12 +17,14 @@ export const domain = () => { let valueNames = ["crossValue"]; let orient = "horizontal"; - const _domain = data => { + const _domain = (data) => { const flattenedData = flattenArray(data); - return transformDomain([...new Set(flattenedData.map(d => d[valueNames[0]]))]); + return transformDomain([ + ...new Set(flattenedData.map((d) => d[valueNames[0]])), + ]); }; - const transformDomain = d => (orient == "vertical" ? d.reverse() : d); + const transformDomain = (d) => (orient == "vertical" ? d.reverse() : d); _domain.valueName = (...args) => { if (!args.length) { @@ -50,4 +52,4 @@ export const domain = () => { return _domain; }; -export const labelFunction = valueName => d => d[valueName].join("|"); +export const labelFunction = (valueName) => (d) => d[valueName].join("|"); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js index fa742d11a7..c8c2e35977 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js @@ -10,7 +10,12 @@ import * as d3 from "d3"; import * as fc from "d3fc"; import minBandwidth from "./minBandwidth"; import {flattenArray} from "./flatten"; -import {multiAxisBottom, multiAxisLeft, multiAxisTop, multiAxisRight} from "../d3fc/axis/multi-axis"; +import { + multiAxisBottom, + multiAxisLeft, + multiAxisTop, + multiAxisRight, +} from "../d3fc/axis/multi-axis"; import {getChartContainer} from "../plugin/root"; export const scale = () => minBandwidth(d3.scaleBand()).padding(0.5); @@ -19,12 +24,14 @@ export const domain = () => { let valueNames = ["crossValue"]; let orient = "horizontal"; - const _domain = data => { + const _domain = (data) => { const flattenedData = flattenArray(data); - return transformDomain([...new Set(flattenedData.map(d => d[valueNames[0]]))]); + return transformDomain([ + ...new Set(flattenedData.map((d) => d[valueNames[0]])), + ]); }; - const transformDomain = d => (orient == "vertical" ? d.reverse() : d); + const transformDomain = (d) => (orient == "vertical" ? d.reverse() : d); _domain.valueName = (...args) => { if (!args.length) { @@ -52,9 +59,9 @@ export const domain = () => { return _domain; }; -export const labelFunction = valueName => d => d[valueName].join("|"); +export const labelFunction = (valueName) => (d) => d[valueName].join("|"); -export const component = settings => { +export const component = (settings) => { let orient = "horizontal"; let settingName = "crossValues"; let domain = null; @@ -66,10 +73,12 @@ export const component = settings => { const levelGroups = axisGroups(domain); const groupTickLayout = levelGroups.map(getGroupTickLayout); - const tickSizeInner = multiLevel ? groupTickLayout.map(l => l.size) : groupTickLayout[0].size; + const tickSizeInner = multiLevel + ? groupTickLayout.map((l) => l.size) + : groupTickLayout[0].size; const tickSizeOuter = groupTickLayout.reduce((s, v) => s + v.size, 0); - const createAxis = base => scale => { + const createAxis = (base) => (scale) => { const axis = base(scale); if (multiLevel) { @@ -94,7 +103,7 @@ export const component = settings => { right: createAxis(axisSet.right), top: createAxis(axisSet.top), size: `${tickSizeOuter + 10}px`, - decorate + decorate, }; }; @@ -107,27 +116,27 @@ export const component = settings => { // fc.axisOrdinalBottom : fc.axisOrdinalLeft; // }; - const getAxisSet = multiLevel => { + const getAxisSet = (multiLevel) => { if (multiLevel) { return { bottom: multiAxisBottom, left: multiAxisLeft, top: multiAxisTop, - right: multiAxisRight + right: multiAxisRight, }; } else { return { bottom: fc.axisOrdinalBottom, left: fc.axisOrdinalLeft, top: fc.axisOrdinalTop, - right: fc.axisOrdinalRight + right: fc.axisOrdinalRight, }; } }; - const axisGroups = domain => { + const axisGroups = (domain) => { const groups = []; - domain.forEach(tick => { + domain.forEach((tick) => { const split = tick && tick.split ? tick.split("|") : [tick]; split.forEach((s, i) => { while (groups.length <= i) groups.push([]); @@ -143,54 +152,69 @@ export const component = settings => { return groups.reverse(); }; - const getGroupTickLayout = group => { + const getGroupTickLayout = (group) => { const width = settings.size.width; - const maxLength = Math.max(...group.map(g => (g.text ? g.text.length : 0))); + const maxLength = Math.max( + ...group.map((g) => (g.text ? g.text.length : 0)) + ); if (orient === "horizontal") { // x-axis may rotate labels and expand the available height if (group && group.length * 16 > width - 100) { return { size: maxLength * 5 + 10, - rotation: 90 + rotation: 90, }; - } else if (group && group.length * (maxLength * 6 + 10) > width - 100) { + } else if ( + group && + group.length * (maxLength * 6 + 10) > width - 100 + ) { return { size: maxLength * 3 + 20, - rotation: 45 + rotation: 45, }; } return { size: 25, - rotation: 0 + rotation: 0, }; } else { // y-axis size always based on label size return { size: maxLength * 5 + 10, - rotation: 0 + rotation: 0, }; } }; const hideOverlappingLabels = (s, rotated) => { - const getTransformCoords = transform => { + const getTransformCoords = (transform) => { const splitOn = transform.indexOf(",") !== -1 ? "," : " "; const coords = transform .substring(transform.indexOf("(") + 1, transform.indexOf(")")) .split(splitOn) - .map(c => parseInt(c)); + .map((c) => parseInt(c)); while (coords.length < 2) coords.push(0); return coords; }; - const rectanglesOverlap = (r1, r2) => r1.x <= r2.x + r2.width && r2.x <= r1.x + r1.width && r1.y <= r2.y + r2.height && r2.y <= r1.y + r1.height; - const rotatedLabelsOverlap = (r1, r2) => r1.x + r1.width + 14 > r2.x + r2.width; + const rectanglesOverlap = (r1, r2) => + r1.x <= r2.x + r2.width && + r2.x <= r1.x + r1.width && + r1.y <= r2.y + r2.height && + r2.y <= r1.y + r1.height; + const rotatedLabelsOverlap = (r1, r2) => + r1.x + r1.width + 14 > r2.x + r2.width; const isOverlap = rotated ? rotatedLabelsOverlap : rectanglesOverlap; - const rectangleContained = (r1, r2) => r1.x >= r2.x && r1.x + r1.width <= r2.x + r2.width && r1.y >= r2.y && r1.y + r1.height <= r2.y + r2.height; + const rectangleContained = (r1, r2) => + r1.x >= r2.x && + r1.x + r1.width <= r2.x + r2.width && + r1.y >= r2.y && + r1.y + r1.height <= r2.y + r2.height; // The bounds rect is the available screen space a label can fit into - const boundsRect = orient == "horizontal" ? getXAxisBoundsRect(s) : null; + const boundsRect = + orient == "horizontal" ? getXAxisBoundsRect(s) : null; const previousRectangles = []; s.each((d, i, nodes) => { @@ -201,12 +225,19 @@ export const component = settings => { // Work out the actual rectanble the label occupies const tickRect = tick.node().getBBox(); - const rect = {x: tickRect.x + transformCoords[0], y: tickRect.y + transformCoords[1], width: tickRect.width, height: tickRect.height}; + const rect = { + x: tickRect.x + transformCoords[0], + y: tickRect.y + transformCoords[1], + width: tickRect.width, + height: tickRect.height, + }; - const overlap = previousRectangles.some(r => isOverlap(r, rect)); + const overlap = previousRectangles.some((r) => isOverlap(r, rect)); // Test that it also fits into the screen space - const hidden = overlap || (boundsRect && !rectangleContained(rect, boundsRect)); + const hidden = + overlap || + (boundsRect && !rectangleContained(rect, boundsRect)); tick.attr("visibility", hidden ? "hidden" : ""); if (!hidden) { @@ -215,7 +246,7 @@ export const component = settings => { }); }; - const getXAxisBoundsRect = s => { + const getXAxisBoundsRect = (s) => { const container = getChartContainer(s.node()); if (container === null) { return; @@ -229,11 +260,11 @@ export const component = settings => { x: chartRect.left - axisRect.left, width: chartRect.width, y: chartRect.top - axisRect.top, - height: chartRect.height + height: chartRect.height, }; }; - const getLabelTransform = rotation => { + const getLabelTransform = (rotation) => { if (!rotation) { return "translate(0, 8)"; } diff --git a/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js b/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js index 7d9c70707c..53618a5d96 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js @@ -12,36 +12,44 @@ import {withoutOpacity} from "../series/seriesColors.js"; // Render a set of labels with the little left/right arrows for moving // between axes -export const splitterLabels = settings => { +export const splitterLabels = (settings) => { let labels = []; let alt = false; let color; - const _render = selection => { + const _render = (selection) => { selection.text(""); - const labelDataJoin = fc.dataJoin("span", "splitter-label").key(d => d); + const labelDataJoin = fc + .dataJoin("span", "splitter-label") + .key((d) => d); const disabled = !alt && labels.length === 1; const coloured = color && settings.splitValues.length === 0; labelDataJoin(selection, labels) .classed("disabled", disabled) - .text(d => d.name) - .style("color", d => (coloured ? withoutOpacity(color(d.name)) : undefined)) - .on("click", d => { + .text((d) => d.name) + .style("color", (d) => + coloured ? withoutOpacity(color(d.name)) : undefined + ) + .on("click", (d) => { if (disabled) return; if (alt) { - settings.splitMainValues = settings.splitMainValues.filter(v => v != d.name); + settings.splitMainValues = settings.splitMainValues.filter( + (v) => v != d.name + ); } else { - settings.splitMainValues = [d.name].concat(settings.splitMainValues || []); + settings.splitMainValues = [d.name].concat( + settings.splitMainValues || [] + ); } redrawChart(selection); }); }; - const redrawChart = selection => { + const redrawChart = (selection) => { const chartElement = getChartElement(selection.node()); chartElement._container.innerHTML = ""; chartElement._draw(); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/timeAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/timeAxis.js index 0aa7e5b35a..c24d06c08b 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/timeAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/timeAxis.js @@ -17,27 +17,31 @@ export const domain = () => { let valueNames = ["crossValue"]; - const _domain = data => { - base.accessors(valueNames.map(v => d => new Date(d[v]))); + const _domain = (data) => { + base.accessors(valueNames.map((v) => (d) => new Date(d[v]))); return getDataExtent(flattenArray(data)); }; fc.rebindAll(_domain, base, fc.exclude("include", "paddingStrategy")); - const getMinimumGap = data => { - const gaps = valueNames.map(valueName => + const getMinimumGap = (data) => { + const gaps = valueNames.map((valueName) => data - .map(d => new Date(d[valueName]).getTime()) + .map((d) => new Date(d[valueName]).getTime()) .sort((a, b) => a - b) .filter((d, i, a) => i === 0 || d !== a[i - 1]) - .reduce((acc, d, i, src) => (i === 0 || acc <= d - src[i - 1] ? acc : Math.abs(d - src[i - 1]))) + .reduce((acc, d, i, src) => + i === 0 || acc <= d - src[i - 1] + ? acc + : Math.abs(d - src[i - 1]) + ) ); return Math.min(...gaps); }; - const getDataExtent = data => { + const getDataExtent = (data) => { const dataWidth = getMinimumGap(data); return base.padUnit("domain").pad([dataWidth / 2, dataWidth / 2])(data); }; @@ -60,4 +64,4 @@ export const domain = () => { return _domain; }; -export const labelFunction = valueName => d => new Date(d[valueName][0]); +export const labelFunction = (valueName) => (d) => new Date(d[valueName][0]); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/valueFormatter.js b/packages/perspective-viewer-d3fc/src/js/axis/valueFormatter.js index f94c939100..b4dd5df005 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/valueFormatter.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/valueFormatter.js @@ -10,4 +10,9 @@ import * as d3 from "d3"; const SI_MIN = 10000000; -export default d => (Math.abs(d) >= SI_MIN ? d3.format(".3s")(d) : Number.isInteger(d) ? d3.format(",.0f")(d) : d3.format(",.2f")(d)); +export default (d) => + Math.abs(d) >= SI_MIN + ? d3.format(".3s")(d) + : Number.isInteger(d) + ? d3.format(",.0f")(d) + : d3.format(",.2f")(d); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/withoutTicks.js b/packages/perspective-viewer-d3fc/src/js/axis/withoutTicks.js index c58acd7a97..dd977a63a8 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/withoutTicks.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/withoutTicks.js @@ -8,14 +8,14 @@ */ import {rebindAll} from "d3fc"; -export default adaptee => { - const withoutTicks = arg => { +export default (adaptee) => { + const withoutTicks = (arg) => { return adaptee(arg); }; rebindAll(withoutTicks, adaptee); - withoutTicks.ticks = function() { + withoutTicks.ticks = function () { return []; }; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/area.js b/packages/perspective-viewer-d3fc/src/js/charts/area.js index 677b2bd3fa..19ba3a6c97 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/area.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/area.js @@ -26,11 +26,11 @@ function areaChart(container, settings) { const data = splitAndBaseData(settings, filterData(settings)); const color = seriesColors(settings); - const legend = colorLegend() - .settings(settings) - .scale(color); + const legend = colorLegend().settings(settings).scale(color); - const series = fc.seriesSvgRepeat().series(areaSeries(settings, color).orient("vertical")); + const series = fc + .seriesSvgRepeat() + .series(areaSeries(settings, color).orient("vertical")); const xAxis = axisFactory(settings) .excludeType(AXIS_TYPES.linear) @@ -50,7 +50,9 @@ function areaChart(container, settings) { const yAxis1 = yAxisFactory(splitter.data()); // No grid lines if splitting y-axis - const plotSeries = splitter.haveSplit() ? series : withGridLines(series, settings).orient("vertical"); + const plotSeries = splitter.haveSplit() + ? series + : withGridLines(series, settings).orient("vertical"); const chart = chartSvgFactory(xAxis, yAxis1) .axisSplitter(splitter) @@ -76,7 +78,9 @@ function areaChart(container, settings) { chart.altAxis(yAxis2); // Give the tooltip the information (i.e. 2 datasets with different // scales) - toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); + toolTip + .data(splitter.data()) + .altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } // render @@ -88,7 +92,7 @@ areaChart.plugin = { name: "Y Area", max_cells: 4000, max_columns: 50, - render_warning: true + render_warning: true, }; export default areaChart; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/bar.js b/packages/perspective-viewer-d3fc/src/js/charts/bar.js index e4037514f6..05def95846 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/bar.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/bar.js @@ -24,9 +24,7 @@ function barChart(container, settings) { const data = groupAndStackData(settings, filterData(settings)); const color = seriesColors(settings); - const legend = colorLegend() - .settings(settings) - .scale(color); + const legend = colorLegend().settings(settings).scale(color); const bars = barSeries(settings, color).orient("horizontal"); const series = fc @@ -46,7 +44,9 @@ function barChart(container, settings) { .valueName("crossValue") .orient("vertical")(data); - const chart = chartSvgFactory(xAxis, yAxis).plotArea(withGridLines(series, settings).orient("horizontal")); + const chart = chartSvgFactory(xAxis, yAxis).plotArea( + withGridLines(series, settings).orient("horizontal") + ); if (chart.yPaddingInner) { chart.yPaddingInner(0.5); @@ -68,7 +68,7 @@ barChart.plugin = { name: "X Bar", max_cells: 1000, max_columns: 50, - render_warning: true + render_warning: true, }; export default barChart; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/candlestick.js b/packages/perspective-viewer-d3fc/src/js/charts/candlestick.js index 7333cf24a9..7349356770 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/candlestick.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/candlestick.js @@ -18,9 +18,9 @@ candlestick.plugin = { initial: { type: "number", count: 4, - names: ["Open", "Close", "High", "Low"] + names: ["Open", "Close", "High", "Low"], }, - selectMode: "toggle" + selectMode: "toggle", }; export default candlestick; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/charts.js b/packages/perspective-viewer-d3fc/src/js/charts/charts.js index 8e5a94e8d9..34468e117c 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/charts.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/charts.js @@ -20,6 +20,19 @@ import candlestick from "./candlestick"; import sunburst from "./sunburst"; import treemap from "./treemap"; -const chartClasses = [barChart, columnChart, lineChart, xyLine, areaChart, yScatter, xyScatter, ohlc, candlestick, treemap, sunburst, heatmap]; +const chartClasses = [ + barChart, + columnChart, + lineChart, + xyLine, + areaChart, + yScatter, + xyScatter, + ohlc, + candlestick, + treemap, + sunburst, + heatmap, +]; export default chartClasses; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/column.js b/packages/perspective-viewer-d3fc/src/js/charts/column.js index 5199a5fc98..2586b58329 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/column.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/column.js @@ -10,7 +10,11 @@ import * as fc from "d3fc"; import {axisFactory} from "../axis/axisFactory"; import {chartSvgFactory} from "../axis/chartFactory"; import domainMatchOrigins from "../axis/domainMatchOrigins"; -import {axisSplitter, dataBlankFunction, groupedBlankFunction} from "../axis/axisSplitter"; +import { + axisSplitter, + dataBlankFunction, + groupedBlankFunction, +} from "../axis/axisSplitter"; import {AXIS_TYPES} from "../axis/axisType"; import {barSeries} from "../series/barSeries"; import {seriesColors} from "../series/seriesColors"; @@ -25,9 +29,7 @@ function columnChart(container, settings) { const data = groupAndStackData(settings, filterData(settings)); const color = seriesColors(settings); - const legend = colorLegend() - .settings(settings) - .scale(color); + const legend = colorLegend().settings(settings).scale(color); const bars = barSeries(settings, color).orient("vertical"); const series = fc @@ -48,13 +50,18 @@ function columnChart(container, settings) { .paddingStrategy(hardLimitZeroPadding()); // Check whether we've split some values into a second y-axis - const blankFunction = settings.mainValues.length > 1 ? groupedBlankFunction : dataBlankFunction; + const blankFunction = + settings.mainValues.length > 1 + ? groupedBlankFunction + : dataBlankFunction; const splitter = axisSplitter(settings, data, blankFunction).color(color); const yAxis1 = yAxisFactory(splitter.data()); // No grid lines if splitting y-axis - const plotSeries = splitter.haveSplit() ? series : withGridLines(series, settings).orient("vertical"); + const plotSeries = splitter.haveSplit() + ? series + : withGridLines(series, settings).orient("vertical"); const chart = chartSvgFactory(xAxis, yAxis1) .axisSplitter(splitter) @@ -88,7 +95,7 @@ columnChart.plugin = { name: "Y Bar", max_cells: 1000, max_columns: 50, - render_warning: true + render_warning: true, }; export default columnChart; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/heatmap.js b/packages/perspective-viewer-d3fc/src/js/charts/heatmap.js index 17769b417c..ae3f553c91 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/heatmap.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/heatmap.js @@ -35,7 +35,9 @@ function heatmapChart(container, settings) { .valueName("mainValue") .orient("vertical")(data); - const chart = chartSvgFactory(xAxis, yAxis).plotArea(withGridLines(series, settings)); + const chart = chartSvgFactory(xAxis, yAxis).plotArea( + withGridLines(series, settings) + ); if (chart.xPaddingInner) { chart.xPaddingInner(0); @@ -62,7 +64,7 @@ heatmapChart.plugin = { name: "Heatmap", max_cells: 5000, max_columns: 100, - render_warning: true + render_warning: true, }; export default heatmapChart; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/line.js b/packages/perspective-viewer-d3fc/src/js/charts/line.js index 97eba9adc7..cd2ee899cf 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/line.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/line.js @@ -27,9 +27,7 @@ function lineChart(container, settings) { const data = splitData(settings, filterData(settings)); const color = seriesColors(settings); - const legend = colorLegend() - .settings(settings) - .scale(color); + const legend = colorLegend().settings(settings).scale(color); const series = fc .seriesSvgRepeat() @@ -57,7 +55,9 @@ function lineChart(container, settings) { const yAxis1 = yAxisFactory(splitter.data()); // No grid lines if splitting y-axis - const plotSeries = splitter.haveSplit() ? series : withGridLines(series, settings).orient("vertical"); + const plotSeries = splitter.haveSplit() + ? series + : withGridLines(series, settings).orient("vertical"); const chart = chartSvgFactory(xAxis, yAxis1) .axisSplitter(splitter) .plotArea(plotSeries); @@ -82,7 +82,9 @@ function lineChart(container, settings) { chart.altAxis(yAxis2); // Give the tooltip the information (i.e. 2 datasets with different // scales) - toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); + toolTip + .data(splitter.data()) + .altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } const transposed_data = splitter.data(); @@ -97,7 +99,7 @@ lineChart.plugin = { name: "Y Line", max_cells: 4000, max_columns: 50, - render_warning: true + render_warning: true, }; export default lineChart; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/ohlc.js b/packages/perspective-viewer-d3fc/src/js/charts/ohlc.js index 9daecd405a..2b99321d02 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/ohlc.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/ohlc.js @@ -18,9 +18,9 @@ ohlc.plugin = { initial: { type: "number", count: 4, - names: ["Open", "Close", "High", "Low"] + names: ["Open", "Close", "High", "Low"], }, - selectMode: "toggle" + selectMode: "toggle", }; export default ohlc; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/ohlcCandle.js b/packages/perspective-viewer-d3fc/src/js/charts/ohlcCandle.js index b778ab4b2f..d8dac0913a 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/ohlcCandle.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/ohlcCandle.js @@ -21,17 +21,21 @@ import {colorScale, setOpacity} from "../series/seriesColors"; import {colorLegend} from "../legend/legend"; function ohlcCandle(seriesCanvas) { - return function(container, settings) { + return function (container, settings) { const srcData = ohlcData(settings, filterDataByGroup(settings)); - const bollinger = fc.indicatorBollingerBands().value(d => d.openValue); - const data = srcData.map(seriesData => { + const bollinger = fc + .indicatorBollingerBands() + .value((d) => d.openValue); + const data = srcData.map((seriesData) => { const bollingerData = bollinger(seriesData); - return seriesData.map((d, i) => Object.assign({bollinger: bollingerData[i]}, d)); + return seriesData.map((d, i) => + Object.assign({bollinger: bollingerData[i]}, d) + ); }); const keys = srcData - .map(k => k.key) + .map((k) => k.key) .concat(settings.hideKeys ? settings.hideKeys : []) .sort(); @@ -65,9 +69,7 @@ function ohlcCandle(seriesCanvas) { .paddingStrategy(paddingStrategy)(data); const chart = chartCanvasFactory(xAxis, yAxis).plotArea( - withGridLines(multi, settings) - .orient("vertical") - .canvas(true) + withGridLines(multi, settings).orient("vertical").canvas(true) ); chart.yNice && chart.yNice(); @@ -76,8 +78,14 @@ function ohlcCandle(seriesCanvas) { .chart(chart) .settings(settings) .xScale(xAxis.scale) - .onChange(zoom => { - const zoomedData = data.map(series => series.filter(d => d.crossValue >= zoom.xDomain[0] && d.crossValue <= zoom.xDomain[1])); + .onChange((zoom) => { + const zoomedData = data.map((series) => + series.filter( + (d) => + d.crossValue >= zoom.xDomain[0] && + d.crossValue <= zoom.xDomain[1] + ) + ); chart.yDomain(yAxis.domainFunction(zoomedData)); }) .canvas(true); diff --git a/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js b/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js index 08a20be255..35d5884709 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js @@ -30,10 +30,11 @@ function sunburst(container, settings) { if (color) { const color_column = settings.realValues[1]; - if (settings.mainValues.find(x => x.name === color_column)?.type === "string") { - const legend = colorLegend() - .settings(settings) - .scale(color); + if ( + settings.mainValues.find((x) => x.name === color_column)?.type === + "string" + ) { + const legend = colorLegend().settings(settings).scale(color); container.call(legend); } else { const legend = colorRangeLegend().scale(color); @@ -59,13 +60,18 @@ function sunburst(container, settings) { .merge(sunburstDiv) .select("svg") .select("g.sunburst") - .attr("transform", `translate(${containerSize.width / 2}, ${containerSize.height / 2})`) - .each(function({split, data}) { + .attr( + "transform", + `translate(${containerSize.width / 2}, ${containerSize.height / 2})` + ) + .each(function ({split, data}) { const sunburstElement = select(this); const svgNode = this.parentNode; const {width, height} = svgNode.getBoundingClientRect(); - const radius = (Math.min(width, height) - 24) / (settings.crossValues.length * 2); + const radius = + (Math.min(width, height) - 24) / + (settings.crossValues.length * 2); sunburstSeries() .settings(settings) .split(split) @@ -73,7 +79,9 @@ function sunburst(container, settings) { .color(color) .radius(radius)(sunburstElement); - tooltip().settings(settings)(sunburstElement.selectAll("g.segment")); + tooltip().settings(settings)( + sunburstElement.selectAll("g.segment") + ); }); } @@ -85,8 +93,8 @@ sunburst.plugin = { initial: { type: "number", count: 1, - names: ["Size", "Color", "Tooltip"] - } + names: ["Size", "Color", "Tooltip"], + }, }; export default sunburst; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/treemap.js b/packages/perspective-viewer-d3fc/src/js/charts/treemap.js index 00012301a7..6535c63409 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/treemap.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/treemap.js @@ -26,7 +26,7 @@ function treemap(container, settings) { const data = treeData(settings); const color = treeColor( settings, - data.map(d => d.data) + data.map((d) => d.data) ); if (color) { @@ -37,10 +37,11 @@ function treemap(container, settings) { container.datum(data).call(treemapGrid); if (color) { const color_column = settings.realValues[1]; - if (settings.mainValues.find(x => x.name === color_column)?.type === "string") { - const legend = colorLegend() - .settings(settings) - .scale(color); + if ( + settings.mainValues.find((x) => x.name === color_column)?.type === + "string" + ) { + const legend = colorLegend().settings(settings).scale(color); container.call(legend); } else { const legend = colorRangeLegend().scale(color); @@ -57,7 +58,7 @@ function treemap(container, settings) { .merge(treemapDiv) .select("svg") .select("g.treemap") - .each(function({split, data}) { + .each(function ({split, data}) { const treemapSvg = d3.select(this); if (!settings.treemaps[split]) settings.treemaps[split] = {}; @@ -65,12 +66,14 @@ function treemap(container, settings) { treemapSeries() .settings(settings.treemaps[split]) .data(data) - .container(d3.select(d3.select(this.parentNode).node().parentNode)) + .container( + d3.select(d3.select(this.parentNode).node().parentNode) + ) .color(color)(treemapSvg); - tooltip() - .settings(settings) - .centered(true)(treemapSvg.selectAll("g")); + tooltip().settings(settings).centered(true)( + treemapSvg.selectAll("g") + ); }); } @@ -83,7 +86,7 @@ treemap.plugin = { initial: { type: "number", count: 1, - names: ["Size", "Color", "Tooltip"] - } + names: ["Size", "Color", "Tooltip"], + }, }; export default treemap; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js b/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js index 3a732d0fb5..af3f3f2e04 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js @@ -23,7 +23,9 @@ import zoomableChart from "../zoom/zoomableChart"; import nearbyTip from "../tooltip/nearbyTip"; function xyLine(container, settings) { - const data = transposeData(xySplitData(settings, filterDataByGroup(settings))); + const data = transposeData( + xySplitData(settings, filterDataByGroup(settings)) + ); const color = seriesColorsFromGroups(settings); const symbols = symbolTypeFromGroups(settings); @@ -99,9 +101,9 @@ xyLine.plugin = { initial: { type: "number", count: 2, - names: ["X Axis", "Y Axis"] + names: ["X Axis", "Y Axis"], }, - selectMode: "toggle" + selectMode: "toggle", }; export default xyLine; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js b/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js index 189c2f7395..41b0273326 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js @@ -9,7 +9,10 @@ import * as fc from "d3fc"; import {axisFactory} from "../axis/axisFactory"; import {chartCanvasFactory} from "../axis/chartFactory"; -import {pointSeriesCanvas, symbolTypeFromGroups} from "../series/pointSeriesCanvas"; +import { + pointSeriesCanvas, + symbolTypeFromGroups, +} from "../series/pointSeriesCanvas"; import {pointData} from "../data/pointData"; import {seriesColorsFromGroups} from "../series/seriesColors"; import {seriesLinearRange, seriesColorRange} from "../series/seriesRange"; @@ -32,7 +35,7 @@ import nearbyTip from "../tooltip/nearbyTip"; function interpolate_scale([x1, y1], [x2, y2]) { const m = (y2 - y1) / (x2 - x1); const b = y2 - m * x2; - return function(container) { + return function (container) { const node = container.node(); const shortest_axis = Math.min(node.clientWidth, node.clientHeight); return Math.min(y2, Math.max(y1, m * shortest_axis + b)); @@ -42,7 +45,8 @@ function interpolate_scale([x1, y1], [x2, y2]) { function xyScatter(container, settings) { const data = pointData(settings, filterDataByGroup(settings)); const symbols = symbolTypeFromGroups(settings); - const useGroupColors = settings.realValues.length <= 2 || settings.realValues[2] === null; + const useGroupColors = + settings.realValues.length <= 2 || settings.realValues[2] === null; let color = null; let legend = null; @@ -58,13 +62,26 @@ function xyScatter(container, settings) { legend = colorRangeLegend().scale(color); } - const size = settings.realValues[3] ? seriesLinearRange(settings, data, "size").range([10, 10000]) : null; + const size = settings.realValues[3] + ? seriesLinearRange(settings, data, "size").range([10, 10000]) + : null; const scale_factor = interpolate_scale([600, 0.1], [1600, 1])(container); const series = fc .seriesCanvasMulti() .mapping((data, index) => data[index]) - .series(data.map(series => pointSeriesCanvas(settings, series.key, size, color, symbols, scale_factor))); + .series( + data.map((series) => + pointSeriesCanvas( + settings, + series.key, + size, + color, + symbols, + scale_factor + ) + ) + ); const axisDefault = () => axisFactory(settings) @@ -121,9 +138,9 @@ xyScatter.plugin = { initial: { type: "number", count: 2, - names: ["X Axis", "Y Axis", "Color", "Size", "Tooltip"] + names: ["X Axis", "Y Axis", "Color", "Size", "Tooltip"], }, - selectMode: "toggle" + selectMode: "toggle", }; export default xyScatter; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js index 003747e54c..ee2ca18a10 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js @@ -34,7 +34,11 @@ function yScatter(container, settings) { const series = fc .seriesSvgMulti() .mapping((data, index) => data[index]) - .series(data.map(series => categoryPointSeries(settings, series.key, color, symbols))); + .series( + data.map((series) => + categoryPointSeries(settings, series.key, color, symbols) + ) + ); const paddingStrategy = hardLimitZeroPadding() .pad([0.05, 0.05]) @@ -56,7 +60,9 @@ function yScatter(container, settings) { const yAxis1 = yAxisFactory(splitter.data()); // No grid lines if splitting y-axis - const plotSeries = splitter.haveSplit() ? series : withGridLines(series, settings).orient("vertical"); + const plotSeries = splitter.haveSplit() + ? series + : withGridLines(series, settings).orient("vertical"); const chart = chartSvgFactory(xAxis, yAxis1) .axisSplitter(splitter) @@ -82,7 +88,9 @@ function yScatter(container, settings) { chart.altAxis(yAxis2); // Give the tooltip the information (i.e. 2 datasets with different // scales) - toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); + toolTip + .data(splitter.data()) + .altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } // render @@ -97,7 +105,7 @@ yScatter.plugin = { name: "Y Scatter", max_cells: 4000, max_columns: 50, - render_warning: true + render_warning: true, }; export default yScatter; diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js index becb74986d..6635f092af 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js @@ -1,11 +1,25 @@ import {select, line} from "d3"; -import {axisOrdinalTop, axisOrdinalBottom, axisOrdinalLeft, axisOrdinalRight, dataJoin, rebindAll, exclude} from "d3fc"; +import { + axisOrdinalTop, + axisOrdinalBottom, + axisOrdinalLeft, + axisOrdinalRight, + dataJoin, + rebindAll, + exclude, +} from "d3fc"; import store from "./store"; const multiAxis = (orient, baseAxis, scale) => { let tickSizeOuter = 6; let tickSizeInner = 6; - let axisStore = store("tickFormat", "ticks", "tickArguments", "tickValues", "tickPadding"); + let axisStore = store( + "tickFormat", + "ticks", + "tickArguments", + "tickValues", + "tickPadding" + ); let decorate = () => {}; let groups = null; @@ -13,13 +27,15 @@ const multiAxis = (orient, baseAxis, scale) => { const groupDataJoin = dataJoin("g", "group"); const domainPathDataJoin = dataJoin("path", "domain"); - const translate = (x, y) => (isVertical() ? `translate(${y}, ${x})` : `translate(${x}, ${y})`); + const translate = (x, y) => + isVertical() ? `translate(${y}, ${x})` : `translate(${x}, ${y})`; - const pathTranspose = arr => (isVertical() ? arr.map(d => [d[1], d[0]]) : arr); + const pathTranspose = (arr) => + isVertical() ? arr.map((d) => [d[1], d[0]]) : arr; const isVertical = () => orient === "left" || orient === "right"; - const multiAxis = selection => { + const multiAxis = (selection) => { if (!groups) { axisStore(baseAxis(scale).decorate(decorate))(selection); return; @@ -43,7 +59,7 @@ const multiAxis = (orient, baseAxis, scale) => { [range[0], sign * tickSizeOuter], [range[0], 0], [range[1], 0], - [range[1], sign * tickSizeOuter] + [range[1], sign * tickSizeOuter], ]); const domainLine = domainPathDataJoin(container, [data]); @@ -54,8 +70,9 @@ const multiAxis = (orient, baseAxis, scale) => { const g = groupDataJoin(container, groups); - const getAxisSize = i => (Array.isArray(tickSizeInner) ? tickSizeInner[i] : tickSizeInner); - const getAxisOffset = i => { + const getAxisSize = (i) => + Array.isArray(tickSizeInner) ? tickSizeInner[i] : tickSizeInner; + const getAxisOffset = (i) => { let sum = 0; for (let n = 0; n < i; n++) { sum += getAxisSize(n); @@ -63,20 +80,24 @@ const multiAxis = (orient, baseAxis, scale) => { return sum; }; - g.attr("transform", (d, i) => translate(0, sign * getAxisOffset(i))).each((group, i, nodes) => { + g.attr("transform", (d, i) => + translate(0, sign * getAxisOffset(i)) + ).each((group, i, nodes) => { const groupElement = select(nodes[i]); const groupScale = scaleFromGroup(scale, group); const useAxis = axisStore(baseAxis(groupScale)) .decorate((s, data) => decorate(s, data, i)) .tickSizeInner(getAxisSize(i)) - .tickOffset(d => groupScale.step(d) / 2); + .tickOffset((d) => groupScale.step(d) / 2); useAxis(groupElement); groupElement.select("path.domain").attr("visibility", "hidden"); }); // exit - g.exit().attr("transform", (d, i) => translate(0, sign * getAxisOffset(i))); + g.exit().attr("transform", (d, i) => + translate(0, sign * getAxisOffset(i)) + ); }); }; @@ -89,12 +110,12 @@ const multiAxis = (orient, baseAxis, scale) => { customScale.ticks = () => { return group; }; - customScale.tickFormat = () => d => { + customScale.tickFormat = () => (d) => { return d.text; }; customScale.copy = () => scaleFromGroup(scale, group); - customScale.step = value => value.domain.length * scale.step(); + customScale.step = (value) => value.domain.length * scale.step(); rebindAll(customScale, scale, exclude("ticks", "step", "copy")); return customScale; @@ -145,10 +166,13 @@ const multiAxis = (orient, baseAxis, scale) => { return multiAxis; }; -export const multiAxisTop = scale => multiAxis("top", axisOrdinalTop, scale); +export const multiAxisTop = (scale) => multiAxis("top", axisOrdinalTop, scale); -export const multiAxisBottom = scale => multiAxis("bottom", axisOrdinalBottom, scale); +export const multiAxisBottom = (scale) => + multiAxis("bottom", axisOrdinalBottom, scale); -export const multiAxisLeft = scale => multiAxis("left", axisOrdinalLeft, scale); +export const multiAxisLeft = (scale) => + multiAxis("left", axisOrdinalLeft, scale); -export const multiAxisRight = scale => multiAxis("right", axisOrdinalRight, scale); +export const multiAxisRight = (scale) => + multiAxis("right", axisOrdinalRight, scale); diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/store.js b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/store.js index 9e321b286c..d6310cdace 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/store.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/store.js @@ -1,7 +1,7 @@ export default (...names) => { const data = {}; - const store = target => { + const store = (target) => { for (const key of Object.keys(data)) { target[key](data[key]); } diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js b/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js index a12494533a..19a8160b68 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js @@ -10,11 +10,11 @@ import * as d3Array from "d3-array"; import {defaultPadding} from "../padding/default"; -export const extentLinear = function() { +export const extentLinear = function () { let accessors = [ - function(d) { + function (d) { return d; - } + }, ]; let symmetricalAbout = null; let include = []; @@ -28,7 +28,11 @@ export const extentLinear = function() { let _iterator = accessors[Symbol.iterator](); try { - for (let _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for ( + let _step; + !(_iteratorNormalCompletion = (_step = _iterator.next()).done); + _iteratorNormalCompletion = true + ) { let accessor = _step.value; for (let i = 0; i < data.length; i++) { @@ -57,11 +61,24 @@ export const extentLinear = function() { let extent$$1 = [d3Array.min(values), d3Array.max(values)]; - extent$$1[0] = extent$$1[0] == null ? d3Array.min(include) : d3Array.min([extent$$1[0]].concat(toConsumableArray(include))); - extent$$1[1] = extent$$1[1] == null ? d3Array.max(include) : d3Array.max([extent$$1[1]].concat(toConsumableArray(include))); + extent$$1[0] = + extent$$1[0] == null + ? d3Array.min(include) + : d3Array.min( + [extent$$1[0]].concat(toConsumableArray(include)) + ); + extent$$1[1] = + extent$$1[1] == null + ? d3Array.max(include) + : d3Array.max( + [extent$$1[1]].concat(toConsumableArray(include)) + ); if (symmetricalAbout != null) { - let halfRange = Math.max(Math.abs(extent$$1[1] - symmetricalAbout), Math.abs(extent$$1[0] - symmetricalAbout)); + let halfRange = Math.max( + Math.abs(extent$$1[1] - symmetricalAbout), + Math.abs(extent$$1[0] - symmetricalAbout) + ); extent$$1[0] = symmetricalAbout - halfRange; extent$$1[1] = symmetricalAbout + halfRange; } @@ -69,7 +86,7 @@ export const extentLinear = function() { return paddingStrategy(extent$$1); }; - instance.accessors = function() { + instance.accessors = function () { if (!arguments.length) { return accessors; } @@ -79,7 +96,7 @@ export const extentLinear = function() { //This function points directly at the paddingStrategy child object's //properties for backwards-compatibility. DEPRECATED. - instance.pad = function() { + instance.pad = function () { if (!arguments.length) { return paddingStrategy.pad; } @@ -89,15 +106,17 @@ export const extentLinear = function() { //This function points directly at the paddingStrategy child object's //properties for backwards-compatibility. DEPRECATED. - instance.padUnit = function() { + instance.padUnit = function () { if (!arguments.length) { return paddingStrategy.padUnit; } - paddingStrategy.padUnit(arguments.length <= 0 ? undefined : arguments[0]); + paddingStrategy.padUnit( + arguments.length <= 0 ? undefined : arguments[0] + ); return instance; }; - instance.include = function() { + instance.include = function () { if (!arguments.length) { return include; } @@ -105,7 +124,7 @@ export const extentLinear = function() { return instance; }; - instance.symmetricalAbout = function() { + instance.symmetricalAbout = function () { if (!arguments.length) { return symmetricalAbout; } @@ -113,7 +132,7 @@ export const extentLinear = function() { return instance; }; - instance.paddingStrategy = function() { + instance.paddingStrategy = function () { if (!arguments.length) { return paddingStrategy; } @@ -124,7 +143,7 @@ export const extentLinear = function() { return instance; }; -let toConsumableArray = function(arr) { +let toConsumableArray = function (arr) { if (Array.isArray(arr)) { let arr2 = Array(arr.length); for (let i = 0; i < arr.length; i++) { diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/default.js b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/default.js index ed33eb6c1c..a035c99abc 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/default.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/default.js @@ -11,7 +11,7 @@ export const defaultPadding = () => { let pad = [0, 0]; let padUnit = "percent"; - const padding = extent => { + const padding = (extent) => { switch (padUnit) { case "domain": { extent[0] -= pad[0]; @@ -30,7 +30,7 @@ export const defaultPadding = () => { return extent; }; - padding.pad = function() { + padding.pad = function () { if (!arguments.length) { return pad; } @@ -38,7 +38,7 @@ export const defaultPadding = () => { return padding; }; - padding.padUnit = function() { + padding.padUnit = function () { if (!arguments.length) { return padUnit; } diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js index 45cc31c20e..12d0e8a89a 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js @@ -13,7 +13,7 @@ import * as fc from "d3fc"; export const hardLimitZeroPadding = () => { const _defaultPadding = defaultPadding(); - const padding = extent => { + const padding = (extent) => { let pad = _defaultPadding.pad(); let padUnit = _defaultPadding.padUnit(); @@ -35,8 +35,10 @@ export const hardLimitZeroPadding = () => { // If datapoints are exclusively negative or exclusively positive hard // limit extent to 0. - extent[0] = extent[0] >= 0 && paddedLowerExtent < 0 ? 0 : paddedLowerExtent; - extent[1] = extent[1] <= 0 && paddedUpperExtent > 0 ? 0 : paddedUpperExtent; + extent[0] = + extent[0] >= 0 && paddedLowerExtent < 0 ? 0 : paddedLowerExtent; + extent[1] = + extent[1] <= 0 && paddedUpperExtent > 0 ? 0 : paddedUpperExtent; return extent; }; diff --git a/packages/perspective-viewer-d3fc/src/js/data/findBest.js b/packages/perspective-viewer-d3fc/src/js/data/findBest.js index fc86965913..689bc2aa4e 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/findBest.js +++ b/packages/perspective-viewer-d3fc/src/js/data/findBest.js @@ -8,13 +8,17 @@ */ export const findBestFromData = (array, valueFn, compareFn = Math.min) => { - const findBestFromArray = array => { + const findBestFromArray = (array) => { return array.reduce((best, v) => { const thisValue = findBestFromItem(v, valueFn); - return thisValue && (!best || compareFn(best.value, thisValue.value) === thisValue.value) ? thisValue : best; + return thisValue && + (!best || + compareFn(best.value, thisValue.value) === thisValue.value) + ? thisValue + : best; }, null); }; - const findBestFromItem = item => { + const findBestFromItem = (item) => { if (Array.isArray(item)) { return findBestFromArray(item, valueFn); } @@ -22,7 +26,7 @@ export const findBestFromData = (array, valueFn, compareFn = Math.min) => { return value !== null ? { item, - value + value, } : null; }; diff --git a/packages/perspective-viewer-d3fc/src/js/data/groupData.js b/packages/perspective-viewer-d3fc/src/js/data/groupData.js index 1774e847c6..1caee93905 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/groupData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/groupData.js @@ -11,7 +11,9 @@ import {splitIntoMultiSeries} from "./splitIntoMultiSeries"; export function groupData(settings, data) { const stack = {stack: false}; - const groupedSeries = splitIntoMultiSeries(settings, data, stack).map(data => groupPointDataByMainValue(settings, data, stack)); + const groupedSeries = splitIntoMultiSeries(settings, data, stack).map( + (data) => groupPointDataByMainValue(settings, data, stack) + ); if (settings.mainValues.length > 1) { const flattenedSeries = groupedSeries.reduce((a, b) => a.concat(b)); @@ -23,20 +25,25 @@ export function groupData(settings, data) { export function groupAndStackData(settings, data) { const stack = {stack: true}; - return splitIntoMultiSeries(settings, data, stack).map(data => groupPointDataByMainValue(settings, data, stack)); + return splitIntoMultiSeries(settings, data, stack).map((data) => + groupPointDataByMainValue(settings, data, stack) + ); } function seriesDataFn(settings, data, {stack = false}) { const labelfn = labelFunction(settings); - return mainValue => { - const baseValue = col => (stack ? col[`__BASE_VALUE__${mainValue.name}`] || 0 : 0); + return (mainValue) => { + const baseValue = (col) => + stack ? col[`__BASE_VALUE__${mainValue.name}`] || 0 : 0; const series = data.map((col, i) => ({ crossValue: labelfn(col, i), mainValue: !!col[mainValue.name] ? col[mainValue.name] : null, baseValue: baseValue(col), - key: col.__KEY__ ? `${col.__KEY__}|${mainValue.name}` : mainValue.name, - row: col.row || col + key: col.__KEY__ + ? `${col.__KEY__}|${mainValue.name}` + : mainValue.name, + row: col.row || col, })); series.key = series[0].key; return series; diff --git a/packages/perspective-viewer-d3fc/src/js/data/heatmapData.js b/packages/perspective-viewer-d3fc/src/js/data/heatmapData.js index f8f8ef8fc7..c186edf160 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/heatmapData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/heatmapData.js @@ -21,14 +21,17 @@ export function heatmapData(settings, data) { data.forEach((col, i) => { const crossValue = labelfn(col, i); Object.keys(col) - .filter(key => key !== "__ROW_PATH__") - .forEach(key => { + .filter((key) => key !== "__ROW_PATH__") + .forEach((key) => { const mainValue = getMainValues(key); heatmapData.push({ crossValue: crossValue, - mainValue: mainType === AXIS_TYPES.time ? new Date(mainValue) : mainValue, + mainValue: + mainType === AXIS_TYPES.time + ? new Date(mainValue) + : mainValue, colorValue: col[key], - row: col + row: col, }); }); }); diff --git a/packages/perspective-viewer-d3fc/src/js/data/ohlcData.js b/packages/perspective-viewer-d3fc/src/js/data/ohlcData.js index 9d264f14d0..6e23e7b5da 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/ohlcData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/ohlcData.js @@ -10,25 +10,40 @@ import {labelFunction} from "../axis/axisLabel"; import {splitIntoMultiSeries} from "./splitIntoMultiSeries"; export function ohlcData(settings, data) { - return splitIntoMultiSeries(settings, data, {excludeEmpty: true}).map(data => seriesToOHLC(settings, data)); + return splitIntoMultiSeries(settings, data, {excludeEmpty: true}).map( + (data) => seriesToOHLC(settings, data) + ); } function seriesToOHLC(settings, data) { const labelfn = labelFunction(settings); - const getNextOpen = i => data[i < data.length - 1 ? i + 1 : i][settings.mainValues[0].name]; + const getNextOpen = (i) => + data[i < data.length - 1 ? i + 1 : i][settings.mainValues[0].name]; const mappedSeries = data.map((col, i) => { - const openValue = settings.mainValues.length >= 1 ? col[settings.mainValues[0].name] : undefined; - const closeValue = settings.mainValues.length >= 2 ? col[settings.mainValues[1].name] : getNextOpen(i); + const openValue = + settings.mainValues.length >= 1 + ? col[settings.mainValues[0].name] + : undefined; + const closeValue = + settings.mainValues.length >= 2 + ? col[settings.mainValues[1].name] + : getNextOpen(i); return { crossValue: labelfn(col, i), - mainValues: settings.mainValues.map(v => col[v.name]), + mainValues: settings.mainValues.map((v) => col[v.name]), openValue: openValue, closeValue: closeValue, - highValue: settings.mainValues.length >= 3 ? col[settings.mainValues[2].name] : Math.max(openValue, closeValue), - lowValue: settings.mainValues.length >= 4 ? col[settings.mainValues[3].name] : Math.min(openValue, closeValue), + highValue: + settings.mainValues.length >= 3 + ? col[settings.mainValues[2].name] + : Math.max(openValue, closeValue), + lowValue: + settings.mainValues.length >= 4 + ? col[settings.mainValues[3].name] + : Math.min(openValue, closeValue), key: data.key, - row: col + row: col, }; }); diff --git a/packages/perspective-viewer-d3fc/src/js/data/pointData.js b/packages/perspective-viewer-d3fc/src/js/data/pointData.js index 7743a3c9b6..09e4711321 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/pointData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/pointData.js @@ -10,7 +10,9 @@ import {labelFunction} from "../axis/axisLabel"; import {splitIntoMultiSeries} from "./splitIntoMultiSeries"; export function pointData(settings, data) { - return splitIntoMultiSeries(settings, data, {excludeEmpty: true}).map(data => seriesToPoints(settings, data)); + return splitIntoMultiSeries(settings, data, {excludeEmpty: true}).map( + (data) => seriesToPoints(settings, data) + ); } function seriesToPoints(settings, data) { @@ -18,13 +20,15 @@ function seriesToPoints(settings, data) { const mappedSeries = data.map((col, i) => ({ crossValue: labelfn(col, i), - mainValues: settings.mainValues.map(v => col[v.name]), + mainValues: settings.mainValues.map((v) => col[v.name]), x: col[settings.mainValues[0].name], y: col[settings.mainValues[1].name], - colorValue: settings.realValues[2] ? col[settings.realValues[2]] : undefined, + colorValue: settings.realValues[2] + ? col[settings.realValues[2]] + : undefined, size: settings.realValues[3] ? col[settings.realValues[3]] : undefined, key: data.key, - row: col + row: col, })); mappedSeries.key = data.key; diff --git a/packages/perspective-viewer-d3fc/src/js/data/splitAndBaseData.js b/packages/perspective-viewer-d3fc/src/js/data/splitAndBaseData.js index e60aa46faa..178a0faed9 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/splitAndBaseData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/splitAndBaseData.js @@ -15,8 +15,8 @@ export function splitAndBaseData(settings, data) { const baseValues = {}; return Object.keys(col) - .filter(key => key !== "__ROW_PATH__") - .map(key => { + .filter((key) => key !== "__ROW_PATH__") + .map((key) => { // Keys are of the form "split1|split2|aggregate" const labels = key.split("|"); // label="aggregate" @@ -30,7 +30,7 @@ export function splitAndBaseData(settings, data) { crossValue: labelfn(col, i), mainValue: value, baseValue: baseValue, - row: col + row: col, }; }); }); diff --git a/packages/perspective-viewer-d3fc/src/js/data/splitData.js b/packages/perspective-viewer-d3fc/src/js/data/splitData.js index cbe073cfbf..ce13307d6c 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/splitData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/splitData.js @@ -13,12 +13,12 @@ export function splitData(settings, data) { return data.map((col, i) => { return Object.keys(col) - .filter(key => key !== "__ROW_PATH__") - .map(key => ({ + .filter((key) => key !== "__ROW_PATH__") + .map((key) => ({ key, crossValue: labelfn(col, i), mainValue: col[key], - row: col + row: col, })); }); } diff --git a/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js b/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js index ac46a4fd53..690bd54b60 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js @@ -7,31 +7,45 @@ * */ -export function splitIntoMultiSeries(settings, data, {stack = false, excludeEmpty = false} = {}) { +export function splitIntoMultiSeries( + settings, + data, + {stack = false, excludeEmpty = false} = {} +) { const useData = data || settings.data; if (settings.splitValues.length > 0) { - return splitByValuesIntoMultiSeries(settings, useData, {stack, excludeEmpty}); + return splitByValuesIntoMultiSeries(settings, useData, { + stack, + excludeEmpty, + }); } return [useData]; } -function splitByValuesIntoMultiSeries(settings, data, {stack = false, excludeEmpty = false}) { +function splitByValuesIntoMultiSeries( + settings, + data, + {stack = false, excludeEmpty = false} +) { // Create a series for each "split" value, each one containing all the // "aggregate" values, and "base" values to offset it from the previous // series const multiSeries = {}; - data.forEach(col => { + data.forEach((col) => { // Split this column by "split", including multiple aggregates for each const baseValues = {}; const split = {}; // Keys are of the form "split1|split2|aggregate" Object.keys(col) - .filter(key => key !== "__ROW_PATH__") - .filter(key => !excludeEmpty || (col[key] != null && col[key] != undefined)) - .forEach(key => { + .filter((key) => key !== "__ROW_PATH__") + .filter( + (key) => + !excludeEmpty || (col[key] != null && col[key] != undefined) + ) + .forEach((key) => { const labels = key.split("|"); // label="aggregate" const label = labels[labels.length - 1]; @@ -42,7 +56,9 @@ function splitByValuesIntoMultiSeries(settings, data, {stack = false, excludeEmp // Combine aggregate values for the same split in a single // object - const splitValues = (split[splitName] = split[splitName] || {__ROW_PATH__: col.__ROW_PATH__}); + const splitValues = (split[splitName] = split[splitName] || { + __ROW_PATH__: col.__ROW_PATH__, + }); const baseValue = baseValues[baseKey] || 0; splitValues.__KEY__ = splitName; @@ -59,13 +75,14 @@ function splitByValuesIntoMultiSeries(settings, data, {stack = false, excludeEmp }); // Push each object onto the correct series - Object.keys(split).forEach(splitName => { - const series = (multiSeries[splitName] = multiSeries[splitName] || []); + Object.keys(split).forEach((splitName) => { + const series = (multiSeries[splitName] = + multiSeries[splitName] || []); series.push(split[splitName]); }); }); - return Object.keys(multiSeries).map(k => { + return Object.keys(multiSeries).map((k) => { const series = multiSeries[k]; series.key = k; return series; diff --git a/packages/perspective-viewer-d3fc/src/js/data/transposeData.js b/packages/perspective-viewer-d3fc/src/js/data/transposeData.js index 068ce2cbf9..7018b2046a 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/transposeData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/transposeData.js @@ -7,7 +7,7 @@ * */ -export const transposeData = function(x) { +export const transposeData = function (x) { const columns = []; for (const row of x) { for (let col = 0; col < row.length; col++) { diff --git a/packages/perspective-viewer-d3fc/src/js/data/treeData.js b/packages/perspective-viewer-d3fc/src/js/data/treeData.js index 484c3a7f62..82c1657b58 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/treeData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/treeData.js @@ -12,39 +12,59 @@ import {toValue} from "../tooltip/selectionData"; export function treeData(settings) { const sets = {}; - const real_aggs = settings.realValues.map(x => (x === null ? null : settings.mainValues.find(y => y.name === x))); + const real_aggs = settings.realValues.map((x) => + x === null ? null : settings.mainValues.find((y) => y.name === x) + ); settings.data.forEach((d, j) => { const groups = d.__ROW_PATH__; const splits = getSplitNames(d); - splits.forEach(split => { + splits.forEach((split) => { let currentLevel; if (!sets[split]) { sets[split] = []; } currentLevel = sets[split]; groups.forEach((group, i) => { - let element = currentLevel.find(e => e.name === group); + let element = currentLevel.find((e) => e.name === group); if (!element) { element = {name: group, children: []}; currentLevel.push(element); } - if (settings.realValues.length > 1 && settings.realValues[1] !== null) { + if ( + settings.realValues.length > 1 && + settings.realValues[1] !== null + ) { const is_leaf = i === groups.length - 1; - const colorValue = is_leaf ? getDataValue(d, settings.mainValues[1], split) : getDataValue(settings.agg_paths[j][i + 1] || d, settings.mainValues[1], split); + const colorValue = is_leaf + ? getDataValue(d, settings.mainValues[1], split) + : getDataValue( + settings.agg_paths[j][i + 1] || d, + settings.mainValues[1], + split + ); if (colorValue !== undefined) { element.color = colorValue; } } - if (settings.realValues.length > 2 && settings.realValues[2] !== null) { + if ( + settings.realValues.length > 2 && + settings.realValues[2] !== null + ) { element.tooltip = []; for (let i = 2; i < settings.realValues.length; ++i) { - element.tooltip.push(getDataValue(d, real_aggs[i], split)); + element.tooltip.push( + getDataValue(d, real_aggs[i], split) + ); } } if (i === groups.length - 1) { element.name = groups.slice(-1)[0]; if (groups.length === settings.crossValues.length) { - const size = getDataValue(d, settings.mainValues[0], split); + const size = getDataValue( + d, + settings.mainValues[0], + split + ); element.size = size > 0 ? size : 0; } } @@ -53,54 +73,65 @@ export function treeData(settings) { }); }); - const data = Object.entries(sets).map(set => { + const data = Object.entries(sets).map((set) => { const tree = {name: "root", children: set[1]}; - const root = d3.hierarchy(tree).sum(d => d.size); - const chartData = d3.partition().size([2 * Math.PI, root.height + 1])(root); - chartData.each(d => { + const root = d3.hierarchy(tree).sum((d) => d.size); + const chartData = d3.partition().size([2 * Math.PI, root.height + 1])( + root + ); + chartData.each((d) => { d.current = d; d.mainValues = - settings.realValues.length === 1 || (settings.realValues[1] === null && settings.realValues[2] === null) + settings.realValues.length === 1 || + (settings.realValues[1] === null && + settings.realValues[2] === null) ? d.value - : [d.value, d.data.color].concat(d.data.tooltip || []).filter(x => x !== undefined); + : [d.value, d.data.color] + .concat(d.data.tooltip || []) + .filter((x) => x !== undefined); d.crossValue = d .ancestors() .slice(0, -1) .reverse() - .map(cross => cross.data.name) + .map((cross) => cross.data.name) .join("|"); d.key = set[0]; - d.label = toValue(settings.crossValues[d.depth - 1 < 0 ? 0 : d.depth - 1].type, d.data.name); + d.label = toValue( + settings.crossValues[d.depth - 1 < 0 ? 0 : d.depth - 1].type, + d.data.name + ); }); return { split: set[0], data: chartData, - extents: getExtents(settings, set) + extents: getExtents(settings, set), }; }); return data; } -export const getDataValue = (d, aggregate, split) => (split.length ? d[`${split}|${aggregate.name}`] : d[aggregate.name]); +export const getDataValue = (d, aggregate, split) => + split.length ? d[`${split}|${aggregate.name}`] : d[aggregate.name]; function getExtents(settings, [split, data]) { if (settings.realValues.length > 1 && settings.realValues[1] !== null) { - const min = Math.min(...settings.data.map(d => getDataValue(d, settings.mainValues[1], split))); - const max = Math.max(...data.map(d => d.color)); + const min = Math.min( + ...settings.data.map((d) => + getDataValue(d, settings.mainValues[1], split) + ) + ); + const max = Math.max(...data.map((d) => d.color)); return [min, max]; } } function getSplitNames(d) { const splits = []; - Object.keys(d).forEach(key => { + Object.keys(d).forEach((key) => { if (key !== "__ROW_PATH__") { - const splitValue = key - .split("|") - .slice(0, -1) - .join("|"); + const splitValue = key.split("|").slice(0, -1).join("|"); if (!splits.includes(splitValue)) { splits.push(splitValue); } diff --git a/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js b/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js index 7e2e6998c4..be435d8be2 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js +++ b/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js @@ -12,15 +12,15 @@ import {groupFromKey} from "../series/seriesKey"; export function xySplitData(settings, data) { const n_cols = settings.mainValues.length; - return data.map(col => { - const cols = Object.keys(col).filter(key => key !== "__ROW_PATH__"); + return data.map((col) => { + const cols = Object.keys(col).filter((key) => key !== "__ROW_PATH__"); const row = new Array(cols.length / n_cols); for (let i = 0; i < cols.length / n_cols; i++) { row[i] = { key: groupFromKey(cols[i * n_cols]), crossValue: col[cols[i * n_cols]], mainValue: col[cols[i * n_cols + 1]], - row: col + row: col, }; } return row; diff --git a/packages/perspective-viewer-d3fc/src/js/gridlines/gridlines.js b/packages/perspective-viewer-d3fc/src/js/gridlines/gridlines.js index 20ff81eb98..52789954e7 100644 --- a/packages/perspective-viewer-d3fc/src/js/gridlines/gridlines.js +++ b/packages/perspective-viewer-d3fc/src/js/gridlines/gridlines.js @@ -8,15 +8,21 @@ */ import * as fc from "d3fc"; -const mainGridSvg = settings => x => x.style("stroke-width", "1.0").style("stroke", settings ? settings.colorStyles.grid.gridLineColor : "#bbb"); - -const mainGridCanvas = settings => c => { +const mainGridSvg = (settings) => (x) => + x + .style("stroke-width", "1.0") + .style( + "stroke", + settings ? settings.colorStyles.grid.gridLineColor : "#bbb" + ); + +const mainGridCanvas = (settings) => (c) => { c.strokeStyle = settings ? settings.colorStyles.grid.gridLineColor : "#bbb"; c.lineWidth = 1; }; -const crossGridSvg = x => x.style("display", "none"); -const crossGridCanvas = settings => c => { +const crossGridSvg = (x) => x.style("display", "none"); +const crossGridCanvas = (settings) => (c) => { c.lineWidth = 1; c.strokeStyle = settings ? settings.colorStyles.grid.gridLineColor : "#bbb"; }; @@ -33,7 +39,7 @@ export default (series, settings) => { let mainGrid = mainGridSvg(settings); let crossGrid = crossGridSvg; - const _withGridLines = function(...args) { + const _withGridLines = function (...args) { if (canvas) { seriesMulti = fc.seriesCanvasMulti().context(context); annotationGridline = fc.annotationCanvasGridline(); @@ -46,7 +52,9 @@ export default (series, settings) => { const xStyle = orient === "vertical" ? crossGrid : mainGrid; const yStyle = orient === "horizontal" ? crossGrid : mainGrid; - const gridlines = annotationGridline.xDecorate(xStyle).yDecorate(yStyle); + const gridlines = annotationGridline + .xDecorate(xStyle) + .yDecorate(yStyle); return multi.series([gridlines, series])(...args); }; diff --git a/packages/perspective-viewer-d3fc/src/js/layout/gridLayoutMultiChart.js b/packages/perspective-viewer-d3fc/src/js/layout/gridLayoutMultiChart.js index 4f5019bdba..dcc855b16a 100644 --- a/packages/perspective-viewer-d3fc/src/js/layout/gridLayoutMultiChart.js +++ b/packages/perspective-viewer-d3fc/src/js/layout/gridLayoutMultiChart.js @@ -19,8 +19,12 @@ export function gridLayoutMultiChart() { let color = null; let containerSize = null; - const _gridLayoutMultiChart = container => { - const innerContainer = getOrCreateElement(container, "div.inner-container", () => container.append("div").attr("class", "inner-container")); + const _gridLayoutMultiChart = (container) => { + const innerContainer = getOrCreateElement( + container, + "div.inner-container", + () => container.append("div").attr("class", "inner-container") + ); const innerRect = innerContainer.node().getBoundingClientRect(); const containerHeight = innerRect.height; @@ -28,20 +32,37 @@ export function gridLayoutMultiChart() { const minSize = 300; const data = container.datum(); - const cols = Math.min(data.length, Math.floor(containerWidth / minSize)); + const cols = Math.min( + data.length, + Math.floor(containerWidth / minSize) + ); const rows = Math.ceil(data.length / cols); containerSize = { width: containerWidth / Math.max(cols, 1), - height: Math.min(containerHeight, Math.max(containerHeight / rows, containerWidth / Math.max(cols, 1))) + height: Math.min( + containerHeight, + Math.max( + containerHeight / rows, + containerWidth / Math.max(cols, 1) + ) + ), }; if (containerHeight / rows > containerSize.height * 0.75) { containerSize.height = containerHeight / rows; } - innerContainer.style("grid-template-columns", `repeat(${cols}, ${containerSize.width}px)`); - innerContainer.style("grid-template-rows", `repeat(${rows}, ${containerSize.height}px)`); - - chartDiv = innerContainer.selectAll(`div.${elementsPrefix}-container`).data(data, d => d.split); + innerContainer.style( + "grid-template-columns", + `repeat(${cols}, ${containerSize.width}px)` + ); + innerContainer.style( + "grid-template-rows", + `repeat(${rows}, ${containerSize.height}px)` + ); + + chartDiv = innerContainer + .selectAll(`div.${elementsPrefix}-container`) + .data(data, (d) => d.split); chartDiv.exit().remove(); chartEnter = chartDiv diff --git a/packages/perspective-viewer-d3fc/src/js/legend/colorRangeLegend.js b/packages/perspective-viewer-d3fc/src/js/legend/colorRangeLegend.js index 5ab61533ae..5e7dd0b9b0 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/colorRangeLegend.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/colorRangeLegend.js @@ -15,23 +15,20 @@ export function colorRangeLegend() { let scale = null; function legend(container) { - const legendSelection = getOrCreateElement(container, "div.legend-container", () => - container - .append("div") - .attr("class", "legend-container legend-color") - .style("z-index", "2") + const legendSelection = getOrCreateElement( + container, + "div.legend-container", + () => + container + .append("div") + .attr("class", "legend-container legend-color") + .style("z-index", "2") ); const {width, height} = legendSelection.node().getBoundingClientRect(); - const xScale = d3 - .scaleBand() - .domain([0, 1]) - .range([0, width]); + const xScale = d3.scaleBand().domain([0, 1]).range([0, width]); - const domain = scale - .copy() - .nice() - .domain(); + const domain = scale.copy().nice().domain(); const paddedDomain = fc .extentLinear() .pad([0.1, 0.1]) @@ -39,10 +36,7 @@ export function colorRangeLegend() { const [min, max] = paddedDomain; const expandedDomain = d3.range(min, max, (max - min) / height); - const yScale = d3 - .scaleLinear() - .domain(paddedDomain) - .range([height, 0]); + const yScale = d3.scaleLinear().domain(paddedDomain).range([height, 0]); const svgBar = fc .autoBandwidth(fc.seriesSvgBar()) @@ -50,29 +44,38 @@ export function colorRangeLegend() { .yScale(yScale) .crossValue(0) .baseValue((_, i) => expandedDomain[Math.max(0, i - 1)]) - .mainValue(d => d) - .decorate(selection => { - selection.selectAll("path").style("fill", d => scale(d)); + .mainValue((d) => d) + .decorate((selection) => { + selection.selectAll("path").style("fill", (d) => scale(d)); }); - const middle = domain[0] < 0 && domain[1] > 0 ? 0 : Math.round((domain[1] + domain[0]) / 2); + const middle = + domain[0] < 0 && domain[1] > 0 + ? 0 + : Math.round((domain[1] + domain[0]) / 2); const tickValues = [...domain, middle]; const axisLabel = fc .axisRight(yScale) .tickValues(tickValues) .tickSizeOuter(0) - .tickFormat(d => valueformatter(d)); + .tickFormat((d) => valueformatter(d)); - const legendSvg = getOrCreateElement(legendSelection, "svg", () => legendSelection.append("svg")) + const legendSvg = getOrCreateElement(legendSelection, "svg", () => + legendSelection.append("svg") + ) .style("width", width) .style("height", height); - const legendBar = getOrCreateElement(legendSvg, "g", () => legendSvg.append("g")) + const legendBar = getOrCreateElement(legendSvg, "g", () => + legendSvg.append("g") + ) .datum(expandedDomain) .call(svgBar); const barWidth = Math.abs(legendBar.node().getBBox().x); - getOrCreateElement(legendSvg, "#legend-axis", () => legendSvg.append("g").attr("id", "legend-axis")) + getOrCreateElement(legendSvg, "#legend-axis", () => + legendSvg.append("g").attr("id", "legend-axis") + ) .attr("transform", `translate(${barWidth})`) .datum(expandedDomain) .call(axisLabel) diff --git a/packages/perspective-viewer-d3fc/src/js/legend/filter.js b/packages/perspective-viewer-d3fc/src/js/legend/filter.js index 508597729a..13848a1cb7 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/filter.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/filter.js @@ -11,9 +11,9 @@ import {groupFromKey} from "../series/seriesKey"; export function filterData(settings, data) { const useData = data || settings.data; if (settings.hideKeys && settings.hideKeys.length > 0) { - return useData.map(col => { + return useData.map((col) => { const clone = {...col}; - settings.hideKeys.forEach(k => { + settings.hideKeys.forEach((k) => { delete clone[k]; }); return clone; @@ -25,9 +25,9 @@ export function filterData(settings, data) { export function filterDataByGroup(settings, data) { const useData = data || settings.data; if (settings.hideKeys && settings.hideKeys.length > 0) { - return useData.map(col => { + return useData.map((col) => { const clone = {}; - Object.keys(col).map(key => { + Object.keys(col).map((key) => { if (!settings.hideKeys.includes(groupFromKey(key))) { clone[key] = col[key]; } diff --git a/packages/perspective-viewer-d3fc/src/js/legend/legend.js b/packages/perspective-viewer-d3fc/src/js/legend/legend.js index 589e440fa0..7a9e4ff764 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/legend.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/legend.js @@ -14,38 +14,31 @@ import {withoutOpacity} from "../series/seriesColors"; import {getChartElement} from "../plugin/root"; import {getOrCreateElement} from "../utils/utils"; -const scrollColorLegend = settings => +const scrollColorLegend = (settings) => scrollableLegend( - d3Legend - .legendColor() - .shape("circle") - .shapeRadius(6), + d3Legend.legendColor().shape("circle").shapeRadius(6), settings ); -const scrollSymbolLegend = settings => +const scrollSymbolLegend = (settings) => scrollableLegend( - d3Legend - .legendSymbol() - .shapePadding(1) - .labelOffset(3), + d3Legend.legendSymbol().shapePadding(1).labelOffset(3), settings ); export const colorLegend = () => legendComponent(scrollColorLegend); -export const symbolLegend = () => legendComponent(scrollSymbolLegend, symbolScale); -export const colorGroupLegend = () => legendComponent(scrollColorLegend, symbolScale); +export const symbolLegend = () => + legendComponent(scrollSymbolLegend, symbolScale); +export const colorGroupLegend = () => + legendComponent(scrollColorLegend, symbolScale); function symbolScale(fromScale) { if (!fromScale) return null; const domain = fromScale.domain(); - const range = fromScale.range().map(r => d3.symbol().type(r)()); + const range = fromScale.range().map((r) => d3.symbol().type(r)()); - return d3 - .scaleOrdinal() - .domain(domain) - .range(range); + return d3.scaleOrdinal().domain(domain).range(range); } function legendComponent(scrollLegendComponent, scaleModifier) { @@ -59,10 +52,12 @@ function legendComponent(scrollLegendComponent, scaleModifier) { scrollLegend .scale(scale) .orient("vertical") - .on("cellclick", function(d) { + .on("cellclick", function (d) { settings.hideKeys = settings.hideKeys || []; if (settings.hideKeys.includes(d)) { - settings.hideKeys = settings.hideKeys.filter(k => k !== d); + settings.hideKeys = settings.hideKeys.filter( + (k) => k !== d + ); } else { settings.hideKeys.push(d); } @@ -70,15 +65,22 @@ function legendComponent(scrollLegendComponent, scaleModifier) { getChartElement(this)._draw(); }); - scrollLegend.labels(options => { + scrollLegend.labels((options) => { const parts = options.domain[options.i].split("|"); - return settings.mainValues.length <= 1 && parts.length > 1 ? parts.slice(0, parts.length - 1).join("|") : options.domain[options.i]; + return settings.mainValues.length <= 1 && parts.length > 1 + ? parts.slice(0, parts.length - 1).join("|") + : options.domain[options.i]; }); - const legendSelection = getOrCreateElement(container, "div.legend-container", () => container.append("div")); + const legendSelection = getOrCreateElement( + container, + "div.legend-container", + () => container.append("div") + ); - scrollLegend.decorate(selection => { - const isHidden = data => settings.hideKeys && settings.hideKeys.includes(data); + scrollLegend.decorate((selection) => { + const isHidden = (data) => + settings.hideKeys && settings.hideKeys.includes(data); const cells = selection .select("g.legendCells") @@ -86,13 +88,15 @@ function legendComponent(scrollLegendComponent, scaleModifier) { .selectAll("g.cell"); cells.classed("hidden", isHidden); - cells.append("title").html(d => d); + cells.append("title").html((d) => d); if (color) { cells .select("circle, path") - .style("fill", d => (isHidden(d) ? null : color(d))) - .style("stroke", d => (isHidden(d) ? null : withoutOpacity(color(d)))); + .style("fill", (d) => (isHidden(d) ? null : color(d))) + .style("stroke", (d) => + isHidden(d) ? null : withoutOpacity(color(d)) + ); } }); diff --git a/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js b/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js index a51eb2375a..e6d5b19e3b 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js @@ -23,12 +23,15 @@ export default (fromLegend, settings) => { let domain = []; let pageCount = 1; let pageSize; - let pageIndex = settings.legend && settings.legend.pageIndex ? settings.legend.pageIndex : 0; + let pageIndex = + settings.legend && settings.legend.pageIndex + ? settings.legend.pageIndex + : 0; let decorate = () => {}; let draggable = draggableComponent().settings(settings); let resizable; - const scrollableLegend = selection => { + const scrollableLegend = (selection) => { domain = legend.scale().domain(); resizable = resizableComponent() @@ -41,14 +44,14 @@ export default (fromLegend, settings) => { render(selection); }; - const render = selection => { + const render = (selection) => { calculatePageSize(selection); renderControls(selection); renderLegend(selection); cropCellContents(selection); }; - const renderControls = selection => { + const renderControls = (selection) => { const controls = getLegendControls(selection); controls.style("display", pageCount <= 1 ? "none" : "block"); @@ -75,7 +78,7 @@ export default (fromLegend, settings) => { }); }; - const renderLegend = selection => { + const renderLegend = (selection) => { if (pageCount > 1) legend.cellFilter(cellFilter()); selection.select("g.legendCells").remove(); @@ -86,32 +89,41 @@ export default (fromLegend, settings) => { .select("g.legendCells") .node() .getBBox(); - legendElement.attr("height", cellContainerSize.height + controlsHeightPx); + legendElement.attr( + "height", + cellContainerSize.height + controlsHeightPx + ); decorate(selection); }; - const setPage = index => { + const setPage = (index) => { pageIndex = index; settings.legend = {...settings.legend, pageIndex}; }; - const cellFilter = () => (_, i) => i >= pageSize * pageIndex && i < pageSize * pageIndex + pageSize; + const cellFilter = () => (_, i) => + i >= pageSize * pageIndex && i < pageSize * pageIndex + pageSize; - const calculatePageSize = selection => { + const calculatePageSize = (selection) => { const legendContainerRect = selection.node().getBoundingClientRect(); - let proposedPageSize = Math.floor(legendContainerRect.height / averageCellHeightPx) - 1; + let proposedPageSize = + Math.floor(legendContainerRect.height / averageCellHeightPx) - 1; // if page size is less than all legend items, leave space for the // legend controls - pageSize = proposedPageSize < domain.length ? proposedPageSize - 1 : proposedPageSize; + pageSize = + proposedPageSize < domain.length + ? proposedPageSize - 1 + : proposedPageSize; pageCount = calculatePageCount(proposedPageSize); pageIndex = Math.min(pageIndex, pageCount - 1); }; - const calculatePageCount = pageSize => Math.ceil(domain.length / pageSize); + const calculatePageCount = (pageSize) => + Math.ceil(domain.length / pageSize); - const getLegendControls = container => + const getLegendControls = (container) => getOrCreateElement(container, ".legend-controls", () => container .append("g") @@ -119,7 +131,10 @@ export default (fromLegend, settings) => { .html(legendControlsTemplate) ); - const getLegendElement = container => getOrCreateElement(container, ".legend", () => container.append("svg").attr("class", "legend")); + const getLegendElement = (container) => + getOrCreateElement(container, ".legend", () => + container.append("svg").attr("class", "legend") + ); scrollableLegend.decorate = (...args) => { if (!args.length) { diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/cropCellContents.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/cropCellContents.js index d3ccdeb7b7..1772296c97 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/cropCellContents.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/cropCellContents.js @@ -13,7 +13,12 @@ export function cropCellContents(legendDiv) { const legendCells = legendDiv.select("g.legendCells"); const legendDivRect = legendDiv.node().getBoundingClientRect(); - if (!isElementOverflowing(legendDivRect, legendCells.node().getBoundingClientRect())) { + if ( + !isElementOverflowing( + legendDivRect, + legendCells.node().getBoundingClientRect() + ) + ) { return; } @@ -22,7 +27,11 @@ export function cropCellContents(legendDiv) { legendCells.selectAll(".label").text((d, i, nodes) => { const cell = nodes[i]; if (isElementOverflowing(legendDivRect, cell.getBoundingClientRect())) { - const cutoffCharIndex = getCutoffCharacterIndex(cell, svg, legendDivRect); + const cutoffCharIndex = getCutoffCharacterIndex( + cell, + svg, + legendDivRect + ); return `${d.substring(0, cutoffCharIndex - 3)}...`; } else { return d; diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js index effff86975..83c61b58dc 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js @@ -17,7 +17,7 @@ export function draggableComponent() { let pinned = true; let settings = null; - const draggable = element => { + const draggable = (element) => { const node = element.node(); node.style.cursor = "move"; if (settings.legend) { @@ -25,13 +25,17 @@ export function draggableComponent() { node.style.top = settings.legend.top; } - const drag = d3.drag().on("drag", function() { - const offsets = enforceContainerBoundaries(this, d3.event.dx, d3.event.dy); + const drag = d3.drag().on("drag", function () { + const offsets = enforceContainerBoundaries( + this, + d3.event.dx, + d3.event.dy + ); this.style.left = `${this.offsetLeft + offsets.x}px`; this.style.top = `${this.offsetTop + offsets.y}px`; const position = { left: this.style.left, - top: this.style.top + top: this.style.top, }; settings.legend = {...settings.legend, ...position}; @@ -62,7 +66,7 @@ function unpinNodeFromTopRight(node, pinned) { // Default behaviour for the legend is to remain pinned to the top right // hand corner with a specific margin. Once the legend has moved we // cannot continue to use that css based approach. - d3.select(window).on(resizeForDraggingEvent, function() { + d3.select(window).on(resizeForDraggingEvent, function () { const offsets = enforceContainerBoundaries(node, 0, 0); node.style.left = `${node.offsetLeft + offsets.x}px`; node.style.top = `${node.offsetTop + offsets.y}px`; @@ -86,5 +90,8 @@ function isNodeInTopRight(node) { const fuzz = 5; - return nodeRect.right + margin + fuzz >= containerRect.right && nodeRect.top - margin - fuzz <= containerRect.top; + return ( + nodeRect.right + margin + fuzz >= containerRect.right && + nodeRect.top - margin - fuzz <= containerRect.top + ); } diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js index 3ea289eb58..e5062f4c43 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js @@ -25,7 +25,7 @@ export function enforceContainerBoundaries(innerNode, offsetX, offsetY) { top: innerNodeRect.top + offsetY - margin, right: innerNodeRect.right + offsetX + margin, bottom: innerNodeRect.bottom + offsetY + margin, - left: innerNodeRect.left + offsetX - margin + left: innerNodeRect.left + offsetX - margin, }; const adjustedOffsets = {x: offsetX, y: offsetY}; @@ -33,13 +33,21 @@ export function enforceContainerBoundaries(innerNode, offsetX, offsetY) { {edge: "right", dimension: "x"}, {edge: "left", dimension: "x"}, {edge: "top", dimension: "y"}, - {edge: "bottom", dimension: "y"} + {edge: "bottom", dimension: "y"}, ]; - boundaries.forEach(bound => { - if (isElementOverflowing(chartNodeRect, draggedInnerNodeRect, bound.edge)) { - const adjustment = draggedInnerNodeRect[bound.edge] - chartNodeRect[bound.edge]; - adjustedOffsets[bound.dimension] = adjustedOffsets[bound.dimension] - adjustment; + boundaries.forEach((bound) => { + if ( + isElementOverflowing( + chartNodeRect, + draggedInnerNodeRect, + bound.edge + ) + ) { + const adjustment = + draggedInnerNodeRect[bound.edge] - chartNodeRect[bound.edge]; + adjustedOffsets[bound.dimension] = + adjustedOffsets[bound.dimension] - adjustment; } }); diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js index 2c2d2cacfb..4af7e8ed57 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js @@ -27,22 +27,57 @@ export function resizableComponent() { const maxDimensionsPx = {height: null, width: null}; const callbacks = []; - const executeCallbacks = (event, direction) => callbacks.filter(callback => callback.event === event).forEach(callback => callback.execute(direction)); + const executeCallbacks = (event, direction) => + callbacks + .filter((callback) => callback.event === event) + .forEach((callback) => callback.execute(direction)); - const resizable = container => { + const resizable = (container) => { if (handlesContainerExists(container)) { return; } const dragHelper = { - left: () => executeCallbacks(resizeEvent, {horizontal: dragLeft(d3.event), vertical: false}), - top: () => executeCallbacks(resizeEvent, {horizontal: false, vertical: dragTop(d3.event)}), - right: () => executeCallbacks(resizeEvent, {horizontal: dragRight(d3.event), vertical: false}), - bottom: () => executeCallbacks(resizeEvent, {horizontal: false, vertical: dragBottom(d3.event)}), - topleft: () => executeCallbacks(resizeEvent, {horizontal: dragLeft(d3.event), vertical: dragTop(d3.event)}), - topright: () => executeCallbacks(resizeEvent, {horizontal: dragRight(d3.event), vertical: dragTop(d3.event)}), - bottomright: () => executeCallbacks(resizeEvent, {horizontal: dragRight(d3.event), vertical: dragBottom(d3.event)}), - bottomleft: () => executeCallbacks(resizeEvent, {horizontal: dragLeft(d3.event), vertical: dragBottom(d3.event)}) + left: () => + executeCallbacks(resizeEvent, { + horizontal: dragLeft(d3.event), + vertical: false, + }), + top: () => + executeCallbacks(resizeEvent, { + horizontal: false, + vertical: dragTop(d3.event), + }), + right: () => + executeCallbacks(resizeEvent, { + horizontal: dragRight(d3.event), + vertical: false, + }), + bottom: () => + executeCallbacks(resizeEvent, { + horizontal: false, + vertical: dragBottom(d3.event), + }), + topleft: () => + executeCallbacks(resizeEvent, { + horizontal: dragLeft(d3.event), + vertical: dragTop(d3.event), + }), + topright: () => + executeCallbacks(resizeEvent, { + horizontal: dragRight(d3.event), + vertical: dragTop(d3.event), + }), + bottomright: () => + executeCallbacks(resizeEvent, { + horizontal: dragRight(d3.event), + vertical: dragBottom(d3.event), + }), + bottomleft: () => + executeCallbacks(resizeEvent, { + horizontal: dragLeft(d3.event), + vertical: dragBottom(d3.event), + }), }; const containerNode = container.node(); @@ -59,35 +94,74 @@ export function resizableComponent() { .attr("height", containerRect.height); const handlesGroup = handles.append("g"); - const isVertical = d => d === "left" || d === "right"; - const xCoordHelper = {left: 0, top: handleWidthPx, right: containerRect.width - handleWidthPx, bottom: handleWidthPx}; - const yCoordHelper = {left: handleWidthPx, top: 0, right: handleWidthPx, bottom: containerRect.height - handleWidthPx}; + const isVertical = (d) => d === "left" || d === "right"; + const xCoordHelper = { + left: 0, + top: handleWidthPx, + right: containerRect.width - handleWidthPx, + bottom: handleWidthPx, + }; + const yCoordHelper = { + left: handleWidthPx, + top: 0, + right: handleWidthPx, + bottom: containerRect.height - handleWidthPx, + }; const edgeHandles = ["left", "top", "right", "bottom"]; - const [leftHandle, topHandle, rightHandle, bottomHandle] = edgeHandles.map(edge => - handlesGroup - .append("rect") - .attr("id", `drag${edge}`) - .attr("class", isVertical(edge) ? verticalHandleClass : horizontalHandleClass) - .attr("y", yCoordHelper[edge]) - .attr("x", xCoordHelper[edge]) - .attr("height", isVertical(edge) ? containerRect.height - handleWidthPx * 2 : handleWidthPx) - .attr("width", isVertical(edge) ? handleWidthPx : containerRect.width - handleWidthPx * 2) - .attr("fill", isVertical(edge) ? "lightgreen" : "lightblue") - .attr("fill-opacity", fillOpacity) - .style("z-index", zIndex) - .attr("cursor", isVertical(edge) ? "ew-resize" : "ns-resize") - .call(d3.drag().on("drag", dragHelper[edge])) - ); - - const concatCornerEdges = corner => `${corner[0]}${corner[1]}`; - const cornerCursorHelper = {topleft: "nwse", topright: "nesw", bottomright: "nwse", bottomleft: "nesw"}; + const [leftHandle, topHandle, rightHandle, bottomHandle] = + edgeHandles.map((edge) => + handlesGroup + .append("rect") + .attr("id", `drag${edge}`) + .attr( + "class", + isVertical(edge) + ? verticalHandleClass + : horizontalHandleClass + ) + .attr("y", yCoordHelper[edge]) + .attr("x", xCoordHelper[edge]) + .attr( + "height", + isVertical(edge) + ? containerRect.height - handleWidthPx * 2 + : handleWidthPx + ) + .attr( + "width", + isVertical(edge) + ? handleWidthPx + : containerRect.width - handleWidthPx * 2 + ) + .attr("fill", isVertical(edge) ? "lightgreen" : "lightblue") + .attr("fill-opacity", fillOpacity) + .style("z-index", zIndex) + .attr( + "cursor", + isVertical(edge) ? "ew-resize" : "ns-resize" + ) + .call(d3.drag().on("drag", dragHelper[edge])) + ); + + const concatCornerEdges = (corner) => `${corner[0]}${corner[1]}`; + const cornerCursorHelper = { + topleft: "nwse", + topright: "nesw", + bottomright: "nwse", + bottomleft: "nesw", + }; const cornerHandles = [ ["top", "left"], ["top", "right"], ["bottom", "right"], - ["bottom", "left"] + ["bottom", "left"], ]; - const [topLeftHandle, topRightHandle, bottomRightHandle, bottomLeftHandle] = cornerHandles.map(corner => + const [ + topLeftHandle, + topRightHandle, + bottomRightHandle, + bottomLeftHandle, + ] = cornerHandles.map((corner) => handlesGroup .append("rect") .attr("id", `drag${concatCornerEdges(corner)}`) @@ -97,8 +171,13 @@ export function resizableComponent() { .attr("fill", "red") .attr("fill-opacity", fillOpacity) .style("z-index", zIndex) - .attr("cursor", `${cornerCursorHelper[concatCornerEdges(corner)]}-resize`) - .call(d3.drag().on("drag", dragHelper[concatCornerEdges(corner)])) + .attr( + "cursor", + `${cornerCursorHelper[concatCornerEdges(corner)]}-resize` + ) + .call( + d3.drag().on("drag", dragHelper[concatCornerEdges(corner)]) + ) ); enforceMaxDimensions("height", "y", bottomHandle); @@ -106,35 +185,89 @@ export function resizableComponent() { pinCorners(handles); function dragLeft(event) { - const offset = enforceDistToParallelBarConstraints(enforceContainerBoundaries(leftHandle.node(), event.x, 0).x, handles, "width", (x, y) => x - y); + const offset = enforceDistToParallelBarConstraints( + enforceContainerBoundaries(leftHandle.node(), event.x, 0).x, + handles, + "width", + (x, y) => x - y + ); containerNode.style.left = `${containerNode.offsetLeft + offset}px`; - containerNode.style.width = `${containerNode.offsetWidth - offset}px`; + containerNode.style.width = `${ + containerNode.offsetWidth - offset + }px`; updateSettings(); return resizeAndRelocateHandles(rightHandle, offset, "width", "x"); } function dragRight(event) { - const offset = -enforceDistToParallelBarConstraints(enforceContainerBoundaries(rightHandle.node(), event.dx, 0).x, handles, "width", (x, y) => x + y); - if (pointerFallenBehindAbsoluteCoordinates(offset, "x", rightHandle, event)) return false; - containerNode.style.width = `${containerNode.offsetWidth - offset}px`; + const offset = -enforceDistToParallelBarConstraints( + enforceContainerBoundaries(rightHandle.node(), event.dx, 0).x, + handles, + "width", + (x, y) => x + y + ); + if ( + pointerFallenBehindAbsoluteCoordinates( + offset, + "x", + rightHandle, + event + ) + ) + return false; + containerNode.style.width = `${ + containerNode.offsetWidth - offset + }px`; updateSettings(); return resizeAndRelocateHandles(rightHandle, offset, "width", "x"); } function dragTop(event) { - const offset = enforceDistToParallelBarConstraints(enforceContainerBoundaries(topHandle.node(), 0, event.y).y, handles, "height", (x, y) => x - y); + const offset = enforceDistToParallelBarConstraints( + enforceContainerBoundaries(topHandle.node(), 0, event.y).y, + handles, + "height", + (x, y) => x - y + ); containerNode.style.top = `${containerNode.offsetTop + offset}px`; - containerNode.style.height = `${containerNode.offsetHeight - offset}px`; + containerNode.style.height = `${ + containerNode.offsetHeight - offset + }px`; updateSettings(); - return resizeAndRelocateHandles(bottomHandle, offset, "height", "y"); + return resizeAndRelocateHandles( + bottomHandle, + offset, + "height", + "y" + ); } function dragBottom(event) { - const offset = -enforceDistToParallelBarConstraints(enforceContainerBoundaries(bottomHandle.node(), 0, event.dy).y, handles, "height", (x, y) => x + y); - if (pointerFallenBehindAbsoluteCoordinates(offset, "y", bottomHandle, event)) return false; - containerNode.style.height = `${containerNode.offsetHeight - offset}px`; + const offset = -enforceDistToParallelBarConstraints( + enforceContainerBoundaries(bottomHandle.node(), 0, event.dy).y, + handles, + "height", + (x, y) => x + y + ); + if ( + pointerFallenBehindAbsoluteCoordinates( + offset, + "y", + bottomHandle, + event + ) + ) + return false; + containerNode.style.height = `${ + containerNode.offsetHeight - offset + }px`; updateSettings(); - return resizeAndRelocateHandles(bottomHandle, offset, "height", "y"); + return resizeAndRelocateHandles( + bottomHandle, + offset, + "height", + "y" + ); } function updateSettings() { @@ -142,7 +275,7 @@ export function resizableComponent() { top: containerNode.style.top, left: containerNode.style.left, height: containerNode.style.height, - width: containerNode.style.width + width: containerNode.style.width, }; settings.legend = {...settings.legend, ...dimensions}; } @@ -150,22 +283,45 @@ export function resizableComponent() { function resizeAndRelocateHandles(handle, offset, dimension, axis) { extendHandlesBox(handles, dimension, offset); pinHandleToHandleBoxEdge(handle, axis, offset); - extendPerpendicularHandles(handles, offset, dimension, dimension === "height" ? verticalHandleClass : horizontalHandleClass); + extendPerpendicularHandles( + handles, + offset, + dimension, + dimension === "height" + ? verticalHandleClass + : horizontalHandleClass + ); pinCorners(handles); return offset != 0; } function pinCorners(handles) { topLeftHandle.attr("y", 0, "x", 0); - topRightHandle.attr("y", 0).attr("x", handles.attr("width") - handleWidthPx); - bottomRightHandle.attr("y", handles.attr("height") - handleWidthPx).attr("x", handles.attr("width") - handleWidthPx); - bottomLeftHandle.attr("y", handles.attr("height") - handleWidthPx).attr("x", 0); + topRightHandle + .attr("y", 0) + .attr("x", handles.attr("width") - handleWidthPx); + bottomRightHandle + .attr("y", handles.attr("height") - handleWidthPx) + .attr("x", handles.attr("width") - handleWidthPx); + bottomLeftHandle + .attr("y", handles.attr("height") - handleWidthPx) + .attr("x", 0); } function enforceMaxDimensions(dimension, axis, relativeHandle) { - if (!!maxDimensionsPx[dimension] && maxDimensionsPx[dimension] < containerRect[dimension]) { - containerNode.style[dimension] = `${maxDimensionsPx[dimension]}px`; - resizeAndRelocateHandles(relativeHandle, containerRect[dimension] - maxDimensionsPx[dimension], dimension, axis); + if ( + !!maxDimensionsPx[dimension] && + maxDimensionsPx[dimension] < containerRect[dimension] + ) { + containerNode.style[ + dimension + ] = `${maxDimensionsPx[dimension]}px`; + resizeAndRelocateHandles( + relativeHandle, + containerRect[dimension] - maxDimensionsPx[dimension], + dimension, + axis + ); } } }; @@ -175,7 +331,7 @@ export function resizableComponent() { return resizable; }; - resizable.zIndex = input => { + resizable.zIndex = (input) => { zIndex = input; return resizable; }; @@ -188,49 +344,86 @@ export function resizableComponent() { return resizable; }; - resizable.minWidth = input => { + resizable.minWidth = (input) => { minDimensionsPx.width = input; - if (!!maxDimensionsPx.width) maxDimensionsPx.width = Math.max(minDimensionsPx.width, maxDimensionsPx.width); + if (!!maxDimensionsPx.width) + maxDimensionsPx.width = Math.max( + minDimensionsPx.width, + maxDimensionsPx.width + ); return resizable; }; - resizable.minHeight = input => { + resizable.minHeight = (input) => { minDimensionsPx.height = input; - if (!!maxDimensionsPx.height) maxDimensionsPx.height = Math.max(minDimensionsPx.height, maxDimensionsPx.height); + if (!!maxDimensionsPx.height) + maxDimensionsPx.height = Math.max( + minDimensionsPx.height, + maxDimensionsPx.height + ); return resizable; }; - resizable.handleWidth = input => { + resizable.handleWidth = (input) => { handleWidthPx = input; return resizable; }; - resizable.maxWidth = input => { + resizable.maxWidth = (input) => { maxDimensionsPx.width = input; - minDimensionsPx.width = Math.min(minDimensionsPx.width, maxDimensionsPx.width); + minDimensionsPx.width = Math.min( + minDimensionsPx.width, + maxDimensionsPx.width + ); return resizable; }; - resizable.maxHeight = input => { + resizable.maxHeight = (input) => { maxDimensionsPx.height = input; - minDimensionsPx.height = Math.min(minDimensionsPx.height, maxDimensionsPx.height); + minDimensionsPx.height = Math.min( + minDimensionsPx.height, + maxDimensionsPx.height + ); return resizable; }; - function pointerFallenBehindAbsoluteCoordinates(offset, axis, handle, event) { - const becauseCrossedMinSize = (offset, axis, handle, event) => offset < 0 && event[axis] < Number(handle.attr(axis)); - const becauseExitedCoordinateSpace = (offset, axis, handle, event) => offset > 0 && event[axis] > Number(handle.attr(axis)); - return becauseCrossedMinSize(offset, axis, handle, event) || becauseExitedCoordinateSpace(offset, axis, handle, event); + function pointerFallenBehindAbsoluteCoordinates( + offset, + axis, + handle, + event + ) { + const becauseCrossedMinSize = (offset, axis, handle, event) => + offset < 0 && event[axis] < Number(handle.attr(axis)); + const becauseExitedCoordinateSpace = (offset, axis, handle, event) => + offset > 0 && event[axis] > Number(handle.attr(axis)); + return ( + becauseCrossedMinSize(offset, axis, handle, event) || + becauseExitedCoordinateSpace(offset, axis, handle, event) + ); } - function enforceDistToParallelBarConstraints(offset, dragHandleContainer, dimension, operatorFunction) { - const anticipatedDimension = operatorFunction(Number(dragHandleContainer.attr(dimension)), offset); + function enforceDistToParallelBarConstraints( + offset, + dragHandleContainer, + dimension, + operatorFunction + ) { + const anticipatedDimension = operatorFunction( + Number(dragHandleContainer.attr(dimension)), + offset + ); if (anticipatedDimension < minDimensionsPx[dimension]) { - const difference = minDimensionsPx[dimension] - anticipatedDimension; + const difference = + minDimensionsPx[dimension] - anticipatedDimension; return operatorFunction(offset, difference); } - if (!!maxDimensionsPx[dimension] && anticipatedDimension > maxDimensionsPx[dimension]) { - const difference = maxDimensionsPx[dimension] - anticipatedDimension; + if ( + !!maxDimensionsPx[dimension] && + anticipatedDimension > maxDimensionsPx[dimension] + ) { + const difference = + maxDimensionsPx[dimension] - anticipatedDimension; return operatorFunction(offset, difference); } return offset; @@ -240,17 +433,31 @@ export function resizableComponent() { } // "dimension" referring to width or height -const extendPerpendicularHandles = (handles, offset, dimension, orientationClass) => { +const extendPerpendicularHandles = ( + handles, + offset, + dimension, + orientationClass +) => { const perpendicularHandles = handles.selectAll(`.${orientationClass}`); perpendicularHandles.each((_, i, nodes) => { const handleNode = nodes[i]; const handleElement = d3.select(handleNode); - handleElement.attr(dimension, handleNode.getBoundingClientRect()[dimension] - offset); + handleElement.attr( + dimension, + handleNode.getBoundingClientRect()[dimension] - offset + ); }); }; -const handlesContainerExists = container => container.select(`#${handlesContainerId}`).size() > 0; +const handlesContainerExists = (container) => + container.select(`#${handlesContainerId}`).size() > 0; -const pinHandleToHandleBoxEdge = (handle, axis, offset) => handle.attr(axis, Number(handle.attr(axis)) - offset); +const pinHandleToHandleBoxEdge = (handle, axis, offset) => + handle.attr(axis, Number(handle.attr(axis)) - offset); -const extendHandlesBox = (handles, dimension, offset) => handles.attr(dimension, handles.node().getBoundingClientRect()[dimension] - offset); +const extendHandlesBox = (handles, dimension, offset) => + handles.attr( + dimension, + handles.node().getBoundingClientRect()[dimension] - offset + ); diff --git a/packages/perspective-viewer-d3fc/src/js/plugin/plugin.js b/packages/perspective-viewer-d3fc/src/js/plugin/plugin.js index f72ec82722..fe0feee759 100644 --- a/packages/perspective-viewer-d3fc/src/js/plugin/plugin.js +++ b/packages/perspective-viewer-d3fc/src/js/plugin/plugin.js @@ -17,18 +17,30 @@ import * as d3 from "d3"; const DEFAULT_PLUGIN_SETTINGS = { initial: { type: "number", - count: 1 + count: 1, }, - selectMode: "select" + selectMode: "select", }; const styleWithD3FC = `${style}${getD3FCStyles()}`; -const EXCLUDED_SETTINGS = ["crossValues", "mainValues", "realValues", "splitValues", "filter", "data", "size", "colorStyles", "agg_paths"]; +const EXCLUDED_SETTINGS = [ + "crossValues", + "mainValues", + "realValues", + "splitValues", + "filter", + "data", + "size", + "colorStyles", + "agg_paths", +]; function getD3FCStyles() { - const headerStyles = document.querySelector("head").querySelectorAll("style"); + const headerStyles = document + .querySelector("head") + .querySelectorAll("style"); const d3fcStyles = []; - headerStyles.forEach(s => { + headerStyles.forEach((s) => { if (s.innerText.indexOf("d3fc-") !== -1) { d3fcStyles.push(s.innerText); } @@ -37,10 +49,14 @@ function getD3FCStyles() { } export function register(...plugins) { - plugins = new Set(plugins.length > 0 ? plugins : charts.map(chart => chart.plugin.name)); - charts.forEach(chart => { + plugins = new Set( + plugins.length > 0 ? plugins : charts.map((chart) => chart.plugin.name) + ); + charts.forEach((chart) => { if (plugins.has(chart.plugin.name)) { - const name = chart.plugin.name.toLowerCase().replace(/[ \t\r\n\/]*/g, ""); + const name = chart.plugin.name + .toLowerCase() + .replace(/[ \t\r\n\/]*/g, ""); const plugin_name = `perspective-viewer-d3fc-${name}`; customElements.define( plugin_name, @@ -57,7 +73,8 @@ export function register(...plugins) { this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = ``; this.shadowRoot.innerHTML += `
      `; - this._container = this.shadowRoot.querySelector(".chart"); + this._container = + this.shadowRoot.querySelector(".chart"); this._initialized = true; } } @@ -71,11 +88,17 @@ export function register(...plugins) { } get min_config_columns() { - return chart.plugin.initial?.count || DEFAULT_PLUGIN_SETTINGS.initial.count; + return ( + chart.plugin.initial?.count || + DEFAULT_PLUGIN_SETTINGS.initial.count + ); } get config_column_names() { - return chart.plugin.initial?.names || DEFAULT_PLUGIN_SETTINGS.initial.names; + return ( + chart.plugin.initial?.names || + DEFAULT_PLUGIN_SETTINGS.initial.names + ); } // get initial() { @@ -116,7 +139,11 @@ export function register(...plugins) { const realValues = this.config.columns; const leaves_only = chart.plugin.name !== "Sunburst"; if (end_col && end_row) { - jsonp = view.to_json({end_row, end_col, leaves_only}); + jsonp = view.to_json({ + end_row, + end_col, + leaves_only, + }); } else if (end_col) { jsonp = view.to_json({end_col, leaves_only}); } else if (end_row) { @@ -125,8 +152,22 @@ export function register(...plugins) { jsonp = view.to_json({leaves_only}); } - metadata = await Promise.all([viewer.getTable().then(table => table.schema(false)), view.expression_schema(false), view.schema(false), jsonp, view.get_config()]); - let [table_schema, expression_schema, view_schema, json, config] = metadata; + metadata = await Promise.all([ + viewer + .getTable() + .then((table) => table.schema(false)), + view.expression_schema(false), + view.schema(false), + jsonp, + view.get_config(), + ]); + let [ + table_schema, + expression_schema, + view_schema, + json, + config, + ] = metadata; /** * Retrieve a tree axis column from the table and @@ -134,7 +175,7 @@ export function register(...plugins) { * `undefined`. * @param {String} column a column name */ - const get_pivot_column_type = function(column) { + const get_pivot_column_type = function (column) { let type = table_schema[column]; if (!type) { type = expression_schema[column]; @@ -142,35 +183,60 @@ export function register(...plugins) { return type; }; - const {columns, row_pivots, column_pivots, filter} = config; + const {columns, row_pivots, column_pivots, filter} = + config; const filtered = row_pivots.length > 0 ? json.reduce( (acc, col) => { - if (col.__ROW_PATH__ && col.__ROW_PATH__.length == row_pivots.length) { - acc.agg_paths.push(acc.aggs.slice()); + if ( + col.__ROW_PATH__ && + col.__ROW_PATH__.length == + row_pivots.length + ) { + acc.agg_paths.push( + acc.aggs.slice() + ); acc.rows.push(col); } else { - const len = col.__ROW_PATH__.filter(x => x !== undefined).length; + const len = + col.__ROW_PATH__.filter( + (x) => x !== undefined + ).length; acc.aggs[len] = col; - acc.aggs = acc.aggs.slice(0, len + 1); + acc.aggs = acc.aggs.slice( + 0, + len + 1 + ); } return acc; }, {rows: [], aggs: [], agg_paths: []} ) : {rows: json}; - const dataMap = (col, i) => (!row_pivots.length ? {...col, __ROW_PATH__: [i]} : col); + const dataMap = (col, i) => + !row_pivots.length + ? {...col, __ROW_PATH__: [i]} + : col; const mapped = filtered.rows.map(dataMap); let settings = { realValues, - crossValues: row_pivots.map(r => ({name: r, type: get_pivot_column_type(r)})), - mainValues: columns.map(a => ({name: a, type: view_schema[a]})), - splitValues: column_pivots.map(r => ({name: r, type: get_pivot_column_type(r)})), + crossValues: row_pivots.map((r) => ({ + name: r, + type: get_pivot_column_type(r), + })), + mainValues: columns.map((a) => ({ + name: a, + type: view_schema[a], + })), + splitValues: column_pivots.map((r) => ({ + name: r, + type: get_pivot_column_type(r), + })), filter, data: mapped, - agg_paths: filtered.agg_paths + agg_paths: filtered.agg_paths, }; this._chart = chart; @@ -178,14 +244,23 @@ export function register(...plugins) { const handler = { set: (obj, prop, value) => { if (!EXCLUDED_SETTINGS.includes(prop)) { - this._container && this._container.dispatchEvent(new Event("perspective-plugin-update", {bubbles: true, composed: true})); + this._container && + this._container.dispatchEvent( + new Event( + "perspective-plugin-update", + {bubbles: true, composed: true} + ) + ); } obj[prop] = value; return true; - } + }, }; - this._settings = new Proxy({...this._settings, ...settings}, handler); + this._settings = new Proxy( + {...this._settings, ...settings}, + handler + ); initialiseStyles(this._container, this._settings); if (clear) { @@ -194,7 +269,9 @@ export function register(...plugins) { this._draw(); - await new Promise(resolve => requestAnimationFrame(resolve)); + await new Promise((resolve) => + requestAnimationFrame(resolve) + ); } async clear() { @@ -207,12 +284,19 @@ export function register(...plugins) { if (this._settings.data && this.isConnected) { const containerDiv = d3.select(this._container); const chartClass = `chart ${name}`; - this._settings.size = this._container.getBoundingClientRect(); + this._settings.size = + this._container.getBoundingClientRect(); if (this._settings.data.length > 0) { - this._chart(containerDiv.attr("class", chartClass), this._settings); + this._chart( + containerDiv.attr("class", chartClass), + this._settings + ); } else { - containerDiv.attr("class", `${chartClass} disabled`); + containerDiv.attr( + "class", + `${chartClass} disabled` + ); } } } @@ -250,7 +334,7 @@ export function register(...plugins) { save() { const settings = {...this._settings}; - EXCLUDED_SETTINGS.forEach(s => { + EXCLUDED_SETTINGS.forEach((s) => { delete settings[s]; }); return settings; @@ -267,7 +351,9 @@ export function register(...plugins) { } ); - customElements.get("perspective-viewer").registerPlugin(plugin_name); + customElements + .get("perspective-viewer") + .registerPlugin(plugin_name); } }); } diff --git a/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/closest.js b/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/closest.js index 154a21c7d7..2cc1bc1013 100644 --- a/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/closest.js +++ b/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/closest.js @@ -7,11 +7,13 @@ * */ if (!Element.prototype.matches) { - Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; + Element.prototype.matches = + Element.prototype.msMatchesSelector || + Element.prototype.webkitMatchesSelector; } if (!Element.prototype.closest) { - Element.prototype.closest = function(s) { + Element.prototype.closest = function (s) { var el = this; do { diff --git a/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/matches.js b/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/matches.js index a3865853c4..d1f4055cdd 100644 --- a/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/matches.js +++ b/packages/perspective-viewer-d3fc/src/js/plugin/polyfills/matches.js @@ -7,5 +7,7 @@ * */ if (!Element.prototype.matches) { - Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; + Element.prototype.matches = + Element.prototype.msMatchesSelector || + Element.prototype.webkitMatchesSelector; } diff --git a/packages/perspective-viewer-d3fc/src/js/series/areaSeries.js b/packages/perspective-viewer-d3fc/src/js/series/areaSeries.js index 4eb24d47c2..5b3d1c82a7 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/areaSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/areaSeries.js @@ -11,12 +11,12 @@ import * as fc from "d3fc"; export function areaSeries(settings, color) { let series = fc.seriesSvgArea(); - series = series.decorate(selection => { - selection.style("fill", d => color(d[0].key)); + series = series.decorate((selection) => { + selection.style("fill", (d) => color(d[0].key)); }); return series - .crossValue(d => d.crossValue) - .mainValue(d => d.mainValue) - .baseValue(d => d.baseValue); + .crossValue((d) => d.crossValue) + .mainValue((d) => d.mainValue) + .baseValue((d) => d.baseValue); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/barSeries.js b/packages/perspective-viewer-d3fc/src/js/series/barSeries.js index b48f411052..640c19a9c3 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/barSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/barSeries.js @@ -10,22 +10,25 @@ import * as fc from "d3fc"; import {tooltip} from "../tooltip/tooltip"; export function barSeries(settings, color) { - let series = settings.mainValues.length > 1 ? fc.seriesSvgGrouped(fc.seriesSvgBar()) : fc.seriesSvgBar(); + let series = + settings.mainValues.length > 1 + ? fc.seriesSvgGrouped(fc.seriesSvgBar()) + : fc.seriesSvgBar(); - series = series.decorate(selection => { + series = series.decorate((selection) => { tooltip().settings(settings)(selection); - selection.style("fill", d => color(d.key)); + selection.style("fill", (d) => color(d.key)); }); return fc .autoBandwidth(minBandwidth(series)) - .crossValue(d => d.crossValue) - .mainValue(d => d.mainValue) - .baseValue(d => d.baseValue); + .crossValue((d) => d.crossValue) + .mainValue((d) => d.mainValue) + .baseValue((d) => d.baseValue); } -const minBandwidth = adaptee => { - const min = arg => { +const minBandwidth = (adaptee) => { + const min = (arg) => { return adaptee(arg); }; diff --git a/packages/perspective-viewer-d3fc/src/js/series/categoryPointSeries.js b/packages/perspective-viewer-d3fc/src/js/series/categoryPointSeries.js index 78ff12eacd..8b5399c6be 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/categoryPointSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/categoryPointSeries.js @@ -18,15 +18,22 @@ export function categoryPointSeries(settings, seriesKey, color, symbols) { series.type(symbols(seriesKey)); } - series.decorate(selection => { - selection.style("stroke", d => withoutOpacity(color(d.colorValue || seriesKey))).style("fill", d => withOpacity(color(d.colorValue || seriesKey), opacity)); + series.decorate((selection) => { + selection + .style("stroke", (d) => + withoutOpacity(color(d.colorValue || seriesKey)) + ) + .style("fill", (d) => + withOpacity(color(d.colorValue || seriesKey), opacity) + ); }); - return series.crossValue(d => d.crossValue).mainValue(d => d.mainValue); + return series.crossValue((d) => d.crossValue).mainValue((d) => d.mainValue); } export function symbolType(settings) { - const col = settings.data && settings.data.length > 0 ? settings.data[0] : {}; - const domain = Object.keys(col).filter(k => k !== "__ROW_PATH__"); + const col = + settings.data && settings.data.length > 0 ? settings.data[0] : {}; + const domain = Object.keys(col).filter((k) => k !== "__ROW_PATH__"); return fromDomain(domain); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/colorStyles.js b/packages/perspective-viewer-d3fc/src/js/series/colorStyles.js index 938f318bd3..b2edbf5549 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/colorStyles.js +++ b/packages/perspective-viewer-d3fc/src/js/series/colorStyles.js @@ -21,7 +21,7 @@ export const initialiseStyles = (container, settings) => { scheme: [], gradient: {}, interpolator: {}, - grid: {} + grid: {}, }; const computed = computedStyle(container); @@ -37,7 +37,7 @@ export const initialiseStyles = (container, settings) => { styles.grid.gridLineColor = computed`--d3fc-gridline--color`; const gradients = ["full", "positive", "negative"]; - gradients.forEach(g => { + gradients.forEach((g) => { const gradient = computed(`--d3fc-${g}--gradient`); styles.gradient[g] = parseGradient(gradient, styles.opacity); }); @@ -46,7 +46,7 @@ export const initialiseStyles = (container, settings) => { } }; -const getOpacityFromColor = color => { +const getOpacityFromColor = (color) => { return d3.color(color).opacity; }; @@ -56,16 +56,21 @@ const stepAsColor = (value, opacity) => { return color + ""; }; -const computedStyle = container => { +const computedStyle = (container) => { if (window.ShadyCSS) { - return d => window.ShadyCSS.getComputedStyleValue(container, d); + return (d) => window.ShadyCSS.getComputedStyleValue(container, d); } else { const containerStyles = getComputedStyle(container); - return d => containerStyles.getPropertyValue(d); + return (d) => containerStyles.getPropertyValue(d); } }; const parseGradient = (gradient, opacity) => { const parsed = gparser.parse(gradient)[0].colorStops; - return parsed.map((g, i) => [g.length ? g.length.value / 100 : i / (parsed.length - 1), stepAsColor(g.value, opacity)]).sort((a, b) => a[0] - b[0]); + return parsed + .map((g, i) => [ + g.length ? g.length.value / 100 : i / (parsed.length - 1), + stepAsColor(g.value, opacity), + ]) + .sort((a, b) => a[0] - b[0]); }; diff --git a/packages/perspective-viewer-d3fc/src/js/series/heatmapSeries.js b/packages/perspective-viewer-d3fc/src/js/series/heatmapSeries.js index c0a706bdb8..5636e1b5f2 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/heatmapSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/heatmapSeries.js @@ -12,16 +12,16 @@ import {tooltip} from "../tooltip/tooltip"; export function heatmapSeries(settings, color) { let series = fc.seriesSvgHeatmap(); - series.decorate(selection => { + series.decorate((selection) => { tooltip().settings(settings)(selection); - selection.select("path").attr("fill", d => color(d.colorValue)); + selection.select("path").attr("fill", (d) => color(d.colorValue)); }); return fc .autoBandwidth(series) - .xValue(d => d.crossValue) - .yValue(d => d.mainValue) - .colorValue(d => d.colorValue) + .xValue((d) => d.crossValue) + .yValue((d) => d.mainValue) + .colorValue((d) => d.colorValue) .colorInterpolate(color.interpolator()) .widthFraction(1.0); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/lineSeries.js b/packages/perspective-viewer-d3fc/src/js/series/lineSeries.js index ccbd553236..ef6cec4276 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/lineSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/lineSeries.js @@ -12,9 +12,11 @@ import {withoutOpacity} from "./seriesColors.js"; export function lineSeries(settings, color) { let series = fc.seriesSvgLine(); - series = series.decorate(selection => { - selection.style("stroke", d => withoutOpacity(color(d[0] && d[0].key))); + series = series.decorate((selection) => { + selection.style("stroke", (d) => + withoutOpacity(color(d[0] && d[0].key)) + ); }); - return series.crossValue(d => d.crossValue).mainValue(d => d.mainValue); + return series.crossValue((d) => d.crossValue).mainValue((d) => d.mainValue); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/ohlcCandleSeries.js b/packages/perspective-viewer-d3fc/src/js/series/ohlcCandleSeries.js index eea2bd6308..5ebedde1e9 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/ohlcCandleSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/ohlcCandleSeries.js @@ -9,7 +9,7 @@ import * as fc from "d3fc"; import {colorScale, setOpacity} from "../series/seriesColors"; -const isUp = d => d.closeValue >= d.openValue; +const isUp = (d) => d.closeValue >= d.openValue; export function ohlcCandleSeries(settings, seriesCanvas, upColor) { const domain = upColor.domain(); @@ -18,16 +18,14 @@ export function ohlcCandleSeries(settings, seriesCanvas, upColor) { .settings(settings) .defaultColors([settings.colorStyles["series-2"]]) .mapFunction(setOpacity(0.5))(); - const avgColor = colorScale() - .settings(settings) - .domain(domain)(); + const avgColor = colorScale().settings(settings).domain(domain)(); const series = seriesCanvas() - .crossValue(d => d.crossValue) - .openValue(d => d.openValue) - .highValue(d => d.highValue) - .lowValue(d => d.lowValue) - .closeValue(d => d.closeValue) + .crossValue((d) => d.crossValue) + .openValue((d) => d.openValue) + .highValue((d) => d.highValue) + .lowValue((d) => d.lowValue) + .closeValue((d) => d.closeValue) .decorate((context, d) => { const color = isUp(d) ? upColor(d.key) : downColor(d.key); context.fillStyle = color; @@ -36,20 +34,22 @@ export function ohlcCandleSeries(settings, seriesCanvas, upColor) { const bollingerAverageSeries = fc .seriesCanvasLine() - .mainValue(d => d.bollinger.average) - .crossValue(d => d.crossValue) + .mainValue((d) => d.bollinger.average) + .crossValue((d) => d.crossValue) .decorate((context, d) => { context.strokeStyle = avgColor(d[0].key); }); const bollingerAreaSeries = fc .seriesCanvasArea() - .mainValue(d => d.bollinger.upper) - .baseValue(d => d.bollinger.lower) - .crossValue(d => d.crossValue) + .mainValue((d) => d.bollinger.upper) + .baseValue((d) => d.bollinger.lower) + .crossValue((d) => d.crossValue) .decorate((context, d) => { context.fillStyle = setOpacity(0.25)(avgColor(d[0].key)); }); - return fc.seriesCanvasMulti().series([bollingerAreaSeries, series, bollingerAverageSeries]); + return fc + .seriesCanvasMulti() + .series([bollingerAreaSeries, series, bollingerAverageSeries]); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/pointSeriesCanvas.js b/packages/perspective-viewer-d3fc/src/js/series/pointSeriesCanvas.js index ab9d13fbf1..2b7e9dbd1b 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/pointSeriesCanvas.js +++ b/packages/perspective-viewer-d3fc/src/js/series/pointSeriesCanvas.js @@ -11,20 +11,29 @@ import {withOpacity, withoutOpacity} from "./seriesColors"; import {groupFromKey} from "./seriesKey"; import {fromDomain} from "./seriesSymbols"; -export function pointSeriesCanvas(settings, seriesKey, size, color, symbols, scale_factor = 1) { +export function pointSeriesCanvas( + settings, + seriesKey, + size, + color, + symbols, + scale_factor = 1 +) { let series = seriesCanvasPoint() - .crossValue(d => d.x) - .mainValue(d => d.y); + .crossValue((d) => d.x) + .mainValue((d) => d.y); if (size) { - series.size(d => Math.round(scale_factor * size(d.size))); + series.size((d) => Math.round(scale_factor * size(d.size))); } if (symbols) { series.type(symbols(seriesKey)); } series.decorate((context, d) => { - const colorValue = color(d.colorValue !== undefined ? d.colorValue : seriesKey); + const colorValue = color( + d.colorValue !== undefined ? d.colorValue : seriesKey + ); const opacity = settings.colorStyles && settings.colorStyles.opacity; context.strokeStyle = withoutOpacity(colorValue); @@ -35,9 +44,10 @@ export function pointSeriesCanvas(settings, seriesKey, size, color, symbols, sca } export function symbolTypeFromGroups(settings) { - const col = settings.data && settings.data.length > 0 ? settings.data[0] : {}; + const col = + settings.data && settings.data.length > 0 ? settings.data[0] : {}; const domain = []; - Object.keys(col).forEach(key => { + Object.keys(col).forEach((key) => { if (key !== "__ROW_PATH__") { const group = groupFromKey(key); if (!domain.includes(group)) { diff --git a/packages/perspective-viewer-d3fc/src/js/series/seriesColors.js b/packages/perspective-viewer-d3fc/src/js/series/seriesColors.js index b228df6f83..748ccf8c8b 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/seriesColors.js +++ b/packages/perspective-viewer-d3fc/src/js/series/seriesColors.js @@ -10,24 +10,22 @@ import * as d3 from "d3"; import {groupFromKey} from "./seriesKey"; export function seriesColors(settings) { - const col = settings.data && settings.data.length > 0 ? settings.data[0] : {}; - const domain = Object.keys(col).filter(k => k !== "__ROW_PATH__"); - return colorScale() - .settings(settings) - .domain(domain)(); + const col = + settings.data && settings.data.length > 0 ? settings.data[0] : {}; + const domain = Object.keys(col).filter((k) => k !== "__ROW_PATH__"); + return colorScale().settings(settings).domain(domain)(); } export function seriesColorsFromDistinct(settings, data) { let domain = Array.from(new Set(data)); - return colorScale() - .settings(settings) - .domain(domain)(); + return colorScale().settings(settings).domain(domain)(); } export function seriesColorsFromGroups(settings) { - const col = settings.data && settings.data.length > 0 ? settings.data[0] : {}; + const col = + settings.data && settings.data.length > 0 ? settings.data[0] : {}; const domain = []; - Object.keys(col).forEach(key => { + Object.keys(col).forEach((key) => { if (key !== "__ROW_PATH__") { const group = groupFromKey(key); if (!domain.includes(group)) { @@ -35,16 +33,15 @@ export function seriesColorsFromGroups(settings) { } } }); - return colorScale() - .settings(settings) - .domain(domain)(); + return colorScale().settings(settings).domain(domain)(); } export function colorScale() { let domain = null; let defaultColors = null; let settings = {}; - let mapFunction = d => withOpacity(d, settings.colorStyles && settings.colorStyles.opacity); + let mapFunction = (d) => + withOpacity(d, settings.colorStyles && settings.colorStyles.opacity); const colors = () => { const styles = settings.colorStyles; @@ -100,7 +97,7 @@ export function withOpacity(color, opacity = 0.5) { } export function setOpacity(opacity) { - return color => { + return (color) => { const decoded = d3.color(color); decoded.opacity = opacity; return decoded + ""; diff --git a/packages/perspective-viewer-d3fc/src/js/series/seriesKey.js b/packages/perspective-viewer-d3fc/src/js/series/seriesKey.js index b2cf5d8edf..b8275bda3f 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/seriesKey.js +++ b/packages/perspective-viewer-d3fc/src/js/series/seriesKey.js @@ -8,8 +8,5 @@ */ export function groupFromKey(key) { - return key - .split("|") - .slice(0, -1) - .join("|"); + return key.split("|").slice(0, -1).join("|"); } diff --git a/packages/perspective-viewer-d3fc/src/js/series/seriesRange.js b/packages/perspective-viewer-d3fc/src/js/series/seriesRange.js index a849121826..df15a8f38a 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/seriesRange.js +++ b/packages/perspective-viewer-d3fc/src/js/series/seriesRange.js @@ -31,20 +31,22 @@ export function seriesColorRange(settings, data, valueName, customExtent) { } const getExtent = (data, valueName, customExtent) => { - return ( - customExtent || - domain() - .valueName(valueName) - .pad([0, 0])(data) - ); + return customExtent || domain().valueName(valueName).pad([0, 0])(data); }; -const multiInterpolator = gradientPairs => { +const multiInterpolator = (gradientPairs) => { // A new interpolator that calls through to a set of // interpolators between each value/color pair - const interpolators = gradientPairs.slice(1).map((p, i) => d3.interpolate(gradientPairs[i][1], p[1])); - return value => { - const index = gradientPairs.findIndex((p, i) => i < gradientPairs.length - 1 && value <= gradientPairs[i + 1][0] && value > p[0]); + const interpolators = gradientPairs + .slice(1) + .map((p, i) => d3.interpolate(gradientPairs[i][1], p[1])); + return (value) => { + const index = gradientPairs.findIndex( + (p, i) => + i < gradientPairs.length - 1 && + value <= gradientPairs[i + 1][0] && + value > p[0] + ); if (index === -1) { if (value <= gradientPairs[0][0]) { return gradientPairs[0][1]; diff --git a/packages/perspective-viewer-d3fc/src/js/series/seriesSymbols.js b/packages/perspective-viewer-d3fc/src/js/series/seriesSymbols.js index 551e4093a3..f5f09e93d6 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/seriesSymbols.js +++ b/packages/perspective-viewer-d3fc/src/js/series/seriesSymbols.js @@ -8,13 +8,18 @@ */ import * as d3 from "d3"; -const symbols = [d3.symbolCircle, d3.symbolCross, d3.symbolDiamond, d3.symbolSquare, d3.symbolStar, d3.symbolTriangle, d3.symbolWye]; +const symbols = [ + d3.symbolCircle, + d3.symbolCross, + d3.symbolDiamond, + d3.symbolSquare, + d3.symbolStar, + d3.symbolTriangle, + d3.symbolWye, +]; export function fromDomain(domain) { return domain.length > 1 - ? d3 - .scaleOrdinal() - .domain(domain) - .range(symbols) + ? d3.scaleOrdinal().domain(domain).range(symbols) : null; } diff --git a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstArc.js b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstArc.js index f7e04a2833..7e3bb746e9 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstArc.js +++ b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstArc.js @@ -9,13 +9,15 @@ import {arc} from "d3"; -export const drawArc = radius => +export const drawArc = (radius) => arc() - .startAngle(d => d.x0) - .endAngle(d => d.x1) - .padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005)) + .startAngle((d) => d.x0) + .endAngle((d) => d.x1) + .padAngle((d) => Math.min((d.x1 - d.x0) / 2, 0.005)) .padRadius(radius) - .innerRadius(d => Math.max(1, (d.y0 - 1) * radius)) - .outerRadius(d => Math.max((d.y0 - 1) * radius, (d.y1 - 1) * radius - 1)); + .innerRadius((d) => Math.max(1, (d.y0 - 1) * radius)) + .outerRadius((d) => + Math.max((d.y0 - 1) * radius, (d.y1 - 1) * radius - 1) + ); -export const arcVisible = d => d.y0 >= 1 && d.x1 > d.x0; +export const arcVisible = (d) => d.y0 >= 1 && d.x1 > d.x0; diff --git a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstClick.js b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstClick.js index af555019c9..f0a9fcc61f 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstClick.js +++ b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstClick.js @@ -11,46 +11,71 @@ import {interpolate} from "d3"; import {drawArc, arcVisible} from "./sunburstArc"; import {labelVisible, labelTransform} from "./sunburstLabel"; -export const clickHandler = (data, g, parent, parentTitle, path, label, radius, split, settings) => (p, skipTransition) => { - settings.sunburstLevel[split] = p.data.name; - if (p.parent) { - parent.datum(p.parent); - parent.style("cursor", "pointer"); - parentTitle.html(`⇪ ${p.label}`); - } else { - parent.datum(data); - parent.style("cursor", "default"); - parentTitle.html(""); - } - data.each( - d => - (d.target = { - x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI, - x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI, - y0: Math.max(0, d.y0 - p.depth), - y1: Math.max(0, d.y1 - p.depth) - }) - ); +export const clickHandler = + (data, g, parent, parentTitle, path, label, radius, split, settings) => + (p, skipTransition) => { + settings.sunburstLevel[split] = p.data.name; + if (p.parent) { + parent.datum(p.parent); + parent.style("cursor", "pointer"); + parentTitle.html(`⇪ ${p.label}`); + } else { + parent.datum(data); + parent.style("cursor", "default"); + parentTitle.html(""); + } + data.each( + (d) => + (d.target = { + x0: + Math.max( + 0, + Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0)) + ) * + 2 * + Math.PI, + x1: + Math.max( + 0, + Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0)) + ) * + 2 * + Math.PI, + y0: Math.max(0, d.y0 - p.depth), + y1: Math.max(0, d.y1 - p.depth), + }) + ); - const t = g.transition().duration(skipTransition ? 0 : 750); - path.transition(t) - .tween("data", d => { - const i = interpolate(d.current, d.target); - return t => (d.current = i(t)); - }) - .filter(function(d) { - return +this.getAttribute("fill-opacity") || arcVisible(d.target); - }) - .attr("fill-opacity", d => (arcVisible(d.target) ? 1 : 0)) - .attr("user-select", d => (arcVisible(d.target) ? "initial" : "none")) - .attr("pointer-events", d => (arcVisible(d.target) ? "initial" : "none")) - .attrTween("d", d => () => drawArc(radius)(d.current)); + const t = g.transition().duration(skipTransition ? 0 : 750); + path.transition(t) + .tween("data", (d) => { + const i = interpolate(d.current, d.target); + return (t) => (d.current = i(t)); + }) + .filter(function (d) { + return ( + +this.getAttribute("fill-opacity") || arcVisible(d.target) + ); + }) + .attr("fill-opacity", (d) => (arcVisible(d.target) ? 1 : 0)) + .attr("user-select", (d) => + arcVisible(d.target) ? "initial" : "none" + ) + .attr("pointer-events", (d) => + arcVisible(d.target) ? "initial" : "none" + ) + .attrTween("d", (d) => () => drawArc(radius)(d.current)); - label - .filter(function(d) { - return +this.getAttribute("fill-opacity") || labelVisible(d.target); - }) - .transition(t) - .attr("fill-opacity", d => +labelVisible(d.target)) - .attrTween("transform", d => () => labelTransform(d.current, radius)); -}; + label + .filter(function (d) { + return ( + +this.getAttribute("fill-opacity") || labelVisible(d.target) + ); + }) + .transition(t) + .attr("fill-opacity", (d) => +labelVisible(d.target)) + .attrTween( + "transform", + (d) => () => labelTransform(d.current, radius) + ); + }; diff --git a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstColor.js b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstColor.js index 94094b7873..c2e4baa9ed 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstColor.js +++ b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstColor.js @@ -14,21 +14,33 @@ import {seriesColorsFromDistinct} from "../seriesColors"; export function treeColor(settings, data) { if (settings.realValues.length > 1 && settings.realValues[1] !== null) { const color_column = settings.realValues[1]; - if (settings.mainValues.find(x => x.name === color_column)?.type === "string") { + if ( + settings.mainValues.find((x) => x.name === color_column)?.type === + "string" + ) { const colors = data - .map(d => d.data) - .filter(x => x.height > 0) - .map(x => getColors(x)) + .map((d) => d.data) + .filter((x) => x.height > 0) + .map((x) => getColors(x)) .reduce((a, b) => a.concat(b)); return seriesColorsFromDistinct(settings, colors); } else { - return seriesColorRange(settings, null, null, flattenExtent(data.map(d => d.extents))); + return seriesColorRange( + settings, + null, + null, + flattenExtent(data.map((d) => d.extents)) + ); } } } // only get the colors from the bottom level (e.g. nodes with no children) function getColors(nodes, colors = []) { - nodes.children && nodes.children.length > 0 ? nodes.children.forEach(child => colors.concat(getColors(child, colors))) : nodes.data.color && colors.push(nodes.data.color); + nodes.children && nodes.children.length > 0 + ? nodes.children.forEach((child) => + colors.concat(getColors(child, colors)) + ) + : nodes.data.color && colors.push(nodes.data.color); return colors; } diff --git a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstLabel.js b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstLabel.js index 6790f7571a..7f0ff73d57 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstLabel.js +++ b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstLabel.js @@ -9,7 +9,8 @@ import {select} from "d3"; -export const labelVisible = d => d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.06; +export const labelVisible = (d) => + d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.06; export function labelTransform(d, radius) { const x = (((d.x0 + d.x1) / 2) * 180) / Math.PI; @@ -27,6 +28,11 @@ export function cropLabel(d, targetWidth) { textSelection.text(() => labelText); actualWidth = this.getBBox().width; } - textSelection.text(() => `${labelText.substring(0, labelText.length - 3).replace(/\s+$/, "")}...`); + textSelection.text( + () => + `${labelText + .substring(0, labelText.length - 3) + .replace(/\s+$/, "")}...` + ); } } diff --git a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstSeries.js b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstSeries.js index bff324d968..ea94a45663 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/sunburst/sunburstSeries.js @@ -18,8 +18,10 @@ export function sunburstSeries() { let color = null; let radius = null; - const _sunburstSeries = sunburstElement => { - const segment = sunburstElement.selectAll("g.segment").data(data.descendants().slice(1)); + const _sunburstSeries = (sunburstElement) => { + const segment = sunburstElement + .selectAll("g.segment") + .data(data.descendants().slice(1)); const segmentEnter = segment .enter() .append("g") @@ -34,18 +36,22 @@ export function sunburstSeries() { const path = segmentMerge .select("path") - .attr("fill-opacity", d => (arcVisible(d.current) ? 1 : 0)) - .attr("user-select", d => (arcVisible(d.current) ? "initial" : "none")) - .attr("pointer-events", d => (arcVisible(d.current) ? "initial" : "none")) - .attr("d", d => drawArc(radius)(d.current)); - color && path.style("fill", d => color(d.data.color)); + .attr("fill-opacity", (d) => (arcVisible(d.current) ? 1 : 0)) + .attr("user-select", (d) => + arcVisible(d.current) ? "initial" : "none" + ) + .attr("pointer-events", (d) => + arcVisible(d.current) ? "initial" : "none" + ) + .attr("d", (d) => drawArc(radius)(d.current)); + color && path.style("fill", (d) => color(d.data.color)); const label = segmentMerge .select("text") - .attr("fill-opacity", d => +labelVisible(d.current)) - .attr("transform", d => labelTransform(d.current, radius)) - .text(d => d.label) - .each(function(d) { + .attr("fill-opacity", (d) => +labelVisible(d.current)) + .attr("transform", (d) => labelTransform(d.current, radius)) + .text((d) => d.label) + .each(function (d) { cropLabel.call(this, d, radius); }); @@ -55,17 +61,29 @@ export function sunburstSeries() { .attr("r", radius) .datum(data); - const onClick = clickHandler(data, sunburstElement, parent, parentTitle, path, label, radius, split, settings); + const onClick = clickHandler( + data, + sunburstElement, + parent, + parentTitle, + path, + label, + radius, + split, + settings + ); if (settings.sunburstLevel) { - const currentLevel = data.descendants().find(d => d.data.name === settings.sunburstLevel[split]); + const currentLevel = data + .descendants() + .find((d) => d.data.name === settings.sunburstLevel[split]); currentLevel && onClick(currentLevel, true); } else { settings.sunburstLevel = {}; } - parent.on("click", d => onClick(d, false)); - path.filter(d => d.children) + parent.on("click", (d) => onClick(d, false)); + path.filter((d) => d.children) .style("cursor", "pointer") - .on("click", d => onClick(d, false)); + .on("click", (d) => onClick(d, false)); }; _sunburstSeries.settings = (...args) => { diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapColor.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapColor.js index 808fab86e1..7cfaca5180 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapColor.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapColor.js @@ -11,13 +11,21 @@ import {seriesColorRange} from "../seriesRange"; import {seriesColorsFromDistinct} from "../seriesColors"; export function treeColor(settings, data) { - if (settings.realValues.length < 1 || settings.realValues[1] === null || settings.realValues[1] === undefined) return; + if ( + settings.realValues.length < 1 || + settings.realValues[1] === null || + settings.realValues[1] === undefined + ) + return; const color_column = settings.realValues[1]; const colors = data - .filter(x => x.height > 0) - .map(x => getColors(x)) + .filter((x) => x.height > 0) + .map((x) => getColors(x)) .reduce((a, b) => a.concat(b)); - if (settings.mainValues.find(x => x.name === color_column)?.type === "string") { + if ( + settings.mainValues.find((x) => x.name === color_column)?.type === + "string" + ) { return seriesColorsFromDistinct(settings, colors); } else { let min = Math.min(...colors); @@ -28,6 +36,10 @@ export function treeColor(settings, data) { // only get the colors from the bottom level (e.g. nodes with no children) function getColors(nodes, colors = []) { - nodes.children && nodes.children.length > 0 ? nodes.children.forEach(child => colors.concat(getColors(child, colors))) : nodes.data.color && colors.push(nodes.data.color); + nodes.children && nodes.children.length > 0 + ? nodes.children.forEach((child) => + colors.concat(getColors(child, colors)) + ) + : nodes.data.color && colors.push(nodes.data.color); return colors; } diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLabel.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLabel.js index 25e27ba936..a861d5e300 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLabel.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLabel.js @@ -12,20 +12,21 @@ import {isElementOverlapping, isElementOverflowing} from "../../utils/utils"; const minTextSize = 7; -export const labelMapExists = d => (d.target && d.target.textAttributes ? true : false); +export const labelMapExists = (d) => + d.target && d.target.textAttributes ? true : false; export const toggleLabels = (nodes, treemapLevel, crossValues) => { nodes .selectAll("text") .style("font-size", null) - .attr("class", d => textLevelHelper(d, treemapLevel, crossValues)); + .attr("class", (d) => textLevelHelper(d, treemapLevel, crossValues)); const visibleNodes = selectVisibleNodes(nodes); centerLabels(visibleNodes); preventTextCollisions(visibleNodes); }; -export const restoreLabels = nodes => { +export const restoreLabels = (nodes) => { nodes.each((d, i, nodes) => { const label = select(nodes[i]).selectAll("text"); label @@ -36,60 +37,89 @@ export const restoreLabels = nodes => { }); }; -export const preventTextCollisions = nodes => { +export const preventTextCollisions = (nodes) => { const textCollisionFuzzFactorPx = -2; const textAdjustPx = 14; // This should remain the same as the css value for .top => font-size in the chart.less - const rect = element => element.getBoundingClientRect(); + const rect = (element) => element.getBoundingClientRect(); const topNodes = []; nodes .selectAll("text") - .filter((_, i, nodes) => select(nodes[i]).attr("class") === textVisability.high) + .filter( + (_, i, nodes) => + select(nodes[i]).attr("class") === textVisability.high + ) .each((_, i, nodes) => topNodes.push(nodes[i])); nodes .selectAll("text") - .filter((_, i, nodes) => select(nodes[i]).attr("class") === textVisability.low) + .filter( + (_, i, nodes) => + select(nodes[i]).attr("class") === textVisability.low + ) .each((_, i, nodes) => { const lowerNode = nodes[i]; topNodes - .filter(topNode => isElementOverlapping("x", rect(topNode), rect(lowerNode)) && isElementOverlapping("y", rect(topNode), rect(lowerNode), textCollisionFuzzFactorPx)) - .forEach(() => select(lowerNode).attr("dy", Number(select(lowerNode).attr("dy")) + textAdjustPx)); + .filter( + (topNode) => + isElementOverlapping( + "x", + rect(topNode), + rect(lowerNode) + ) && + isElementOverlapping( + "y", + rect(topNode), + rect(lowerNode), + textCollisionFuzzFactorPx + ) + ) + .forEach(() => + select(lowerNode).attr( + "dy", + Number(select(lowerNode).attr("dy")) + textAdjustPx + ) + ); }); }; -export const lockTextOpacity = d => select(d).style("opacity", textOpacity[select(d).attr("class")]); +export const lockTextOpacity = (d) => + select(d).style("opacity", textOpacity[select(d).attr("class")]); -export const unlockTextOpacity = d => select(d).style("opacity", null); +export const unlockTextOpacity = (d) => select(d).style("opacity", null); export const textOpacity = {top: 1, mid: 0.7, lower: 0}; -export const selectVisibleNodes = nodes => +export const selectVisibleNodes = (nodes) => nodes.filter( (_, i, nodes) => - select(nodes[i]) - .selectAll("text") - .attr("class") !== textVisability.zero + select(nodes[i]).selectAll("text").attr("class") !== + textVisability.zero ); -export const adjustLabelsThatOverflow = nodes => nodes.selectAll("text").each((_, i, nodes) => shrinkOrHideText(nodes[i])); +export const adjustLabelsThatOverflow = (nodes) => + nodes.selectAll("text").each((_, i, nodes) => shrinkOrHideText(nodes[i])); -const centerLabels = nodes => nodes.selectAll("text").each((_, i, nodes) => centerText(nodes[i])); +const centerLabels = (nodes) => + nodes.selectAll("text").each((_, i, nodes) => centerText(nodes[i])); -const centerText = d => { +const centerText = (d) => { const nodeSelect = select(d); const rect = d.getBoundingClientRect(); nodeSelect.attr("dx", 0 - rect.width / 2).attr("dy", 0 + rect.height / 4); }; -const shrinkOrHideText = d => { +const shrinkOrHideText = (d) => { const parent = d.parentNode; const rect = parent.childNodes[0]; const textRect = d.getBoundingClientRect(); const rectRect = rect.getBoundingClientRect(); - if (!needsToShrinkOrHide(d, rectRect, textRect, "left") && !needsToShrinkOrHide(d, rectRect, textRect, "bottom")) { + if ( + !needsToShrinkOrHide(d, rectRect, textRect, "left") && + !needsToShrinkOrHide(d, rectRect, textRect, "bottom") + ) { select(d).attr("class", select(d).attr("class")); } }; @@ -111,7 +141,12 @@ const needsToShrinkOrHide = (d, rectRect, textRect, direction) => { }; const textLevelHelper = (d, treemapLevel, crossValues) => { - if (!crossValues.filter(x => x !== "").every(x => d.crossValue.split("|").includes(x))) return textVisability.zero; + if ( + !crossValues + .filter((x) => x !== "") + .every((x) => d.crossValue.split("|").includes(x)) + ) + return textVisability.zero; switch (d.depth) { case treemapLevel + 1: return textVisability.high; diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLayout.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLayout.js index ec00b0f3f3..95bd2c9a2e 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLayout.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLayout.js @@ -13,7 +13,7 @@ export default (width, height) => { const treemapLayout = d3 .treemap() .size([width, height]) - .paddingInner(d => 1 + 2 * (d.height - 1)); + .paddingInner((d) => 1 + 2 * (d.height - 1)); treemapLayout.tile(d3.treemapBinary); diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js index 94e91b72de..f9da0919fe 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js @@ -12,23 +12,42 @@ import {hierarchy, select} from "d3"; import treemapLayout from "./treemapLayout"; import {textOpacity} from "./treemapLabel"; -const includesAllCrossValues = (d, crossValues) => crossValues.every(val => d.crossValue.split("|").includes(val)); +const includesAllCrossValues = (d, crossValues) => + crossValues.every((val) => d.crossValue.split("|").includes(val)); -export function calculateSubTreeMap(d, crossValues, nodesMerge, treemapLevel, rootNode, treemapDiv) { +export function calculateSubTreeMap( + d, + crossValues, + nodesMerge, + treemapLevel, + rootNode, + treemapDiv +) { // We can approximate coordinates for most of the tree which will be shunted // beyond the viewable area. This approach alone results in excessive // margins as one goes deeper into the treemap. - approximateAttributesForAllNodes(d, crossValues, nodesMerge, treemapLevel, rootNode); + approximateAttributesForAllNodes( + d, + crossValues, + nodesMerge, + treemapLevel, + rootNode + ); d.mapLevel[treemapLevel].levelRoot = true; // Use the pre-existing d3 mechanism to calculate the subtree for the // viewable area. - recalculateVisibleSubTreeCoordinates(d, treemapDiv.node().getBoundingClientRect().width, treemapDiv.node().getBoundingClientRect().height, treemapLevel); + recalculateVisibleSubTreeCoordinates( + d, + treemapDiv.node().getBoundingClientRect().width, + treemapDiv.node().getBoundingClientRect().height, + treemapLevel + ); calculateTextOpacities(nodesMerge, treemapLevel); } export function calculateRootLevelMap(nodesMerge, rootNode) { - nodesMerge.each(d => { + nodesMerge.each((d) => { d.mapLevel = []; d.mapLevel[0] = { x0: d.x0, @@ -36,7 +55,7 @@ export function calculateRootLevelMap(nodesMerge, rootNode) { y0: d.y0, y1: calcHeight(d) + d.y0, visible: true, - opacity: 1 + opacity: 1, }; }); rootNode.mapLevel[0].levelRoot = true; @@ -50,22 +69,41 @@ export const saveLabelMap = (nodes, treemapLevel) => { dx: label.attr("dx"), dy: label.attr("dy"), class: label.attr("class"), - "font-size": label.style("font-size") + "font-size": label.style("font-size"), }; }); }; -function approximateAttributesForAllNodes(d, crossValues, nodesMerge, treemapLevel, rootNode) { - const oldDimensions = {x: d.x0, y: d.y0, width: d.x1 - d.x0, height: d.y1 - d.y0}; - const newDimensions = {width: rootNode.x1 - rootNode.x0, height: rootNode.y1 - rootNode.y0}; - const dimensionMultiplier = {width: newDimensions.width / oldDimensions.width, height: newDimensions.height / oldDimensions.height}; +function approximateAttributesForAllNodes( + d, + crossValues, + nodesMerge, + treemapLevel, + rootNode +) { + const oldDimensions = { + x: d.x0, + y: d.y0, + width: d.x1 - d.x0, + height: d.y1 - d.y0, + }; + const newDimensions = { + width: rootNode.x1 - rootNode.x0, + height: rootNode.y1 - rootNode.y0, + }; + const dimensionMultiplier = { + width: newDimensions.width / oldDimensions.width, + height: newDimensions.height / oldDimensions.height, + }; - nodesMerge.each(d => { + nodesMerge.each((d) => { const x0 = (d.x0 - oldDimensions.x) * dimensionMultiplier.width; const y0 = (d.y0 - oldDimensions.y) * dimensionMultiplier.height; const width = calcWidth(d) * dimensionMultiplier.width; const height = calcHeight(d) * dimensionMultiplier.height; - const visible = includesAllCrossValues(d, crossValues) && d.data.name != crossValues[treemapLevel - 1]; + const visible = + includesAllCrossValues(d, crossValues) && + d.data.name != crossValues[treemapLevel - 1]; d.mapLevel[treemapLevel] = { x0, @@ -73,15 +111,22 @@ function approximateAttributesForAllNodes(d, crossValues, nodesMerge, treemapLev y0, y1: height + y0, visible, - opacity: visible ? 1 : 0 + opacity: visible ? 1 : 0, }; }); d.mapLevel[treemapLevel].levelRoot = true; } -function recalculateVisibleSubTreeCoordinates(subTreeRoot, treeRootWidth, treeRootHeight, treemapLevel) { +function recalculateVisibleSubTreeCoordinates( + subTreeRoot, + treeRootWidth, + treeRootHeight, + treemapLevel +) { const treemapBlueprinter = treemapLayout(treeRootWidth, treeRootHeight); - const treemapBlueprint = treemapBlueprinter(hierarchy(subTreeRoot.data).sum(d => d.size)); + const treemapBlueprint = treemapBlueprinter( + hierarchy(subTreeRoot.data).sum((d) => d.size) + ); const dummiedDescendants = treemapBlueprint.descendants(); const descendants = subTreeRoot.descendants(); diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapSeries.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapSeries.js index f2e837d9b1..93d52e9cc6 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapSeries.js @@ -7,7 +7,11 @@ * */ -import {toggleLabels, adjustLabelsThatOverflow, selectVisibleNodes} from "./treemapLabel"; +import { + toggleLabels, + adjustLabelsThatOverflow, + selectVisibleNodes, +} from "./treemapLabel"; import treemapLayout from "./treemapLayout"; import {changeLevel, returnToLevel} from "./treemapTransitions"; import {parentControls} from "./treemapControls"; @@ -16,12 +20,17 @@ import {calculateRootLevelMap, saveLabelMap} from "./treemapLevelCalculation"; export const nodeLevel = { leaf: "leafnode", branch: "branchnode", - root: "rootnode" + root: "rootnode", }; -export const calcWidth = d => d.x1 - d.x0; -export const calcHeight = d => d.y1 - d.y0; +export const calcWidth = (d) => d.x1 - d.x0; +export const calcHeight = (d) => d.y1 - d.y0; const isLeafNode = (maxDepth, d) => d.depth === maxDepth; -const nodeLevelHelper = (maxDepth, d) => (d.depth === 0 ? nodeLevel.root : isLeafNode(maxDepth, d) ? nodeLevel.leaf : nodeLevel.branch); +const nodeLevelHelper = (maxDepth, d) => + d.depth === 0 + ? nodeLevel.root + : isLeafNode(maxDepth, d) + ? nodeLevel.leaf + : nodeLevel.branch; export function treemapSeries() { let settings = null; @@ -30,14 +39,17 @@ export function treemapSeries() { let treemapDiv = null; let parentCtrls = null; - const _treemapSeries = treemapSvg => { + const _treemapSeries = (treemapSvg) => { parentCtrls = parentControls(treemapDiv); parentCtrls(); const maxDepth = data.height; if (!settings.treemapLevel) settings.treemapLevel = 0; if (!settings.treemapRoute) settings.treemapRoute = []; - const treemap = treemapLayout(treemapDiv.node().getBoundingClientRect().width, treemapDiv.node().getBoundingClientRect().height); + const treemap = treemapLayout( + treemapDiv.node().getBoundingClientRect().width, + treemapDiv.node().getBoundingClientRect().height + ); treemap(data); const nodes = treemapSvg.selectAll("g").data(data.descendants()); @@ -47,41 +59,67 @@ export function treemapSeries() { nodesEnter.append("text"); // Draw child nodes first - const nodesMerge = nodesEnter.merge(nodes).sort((a, b) => b.depth - a.depth); + const nodesMerge = nodesEnter + .merge(nodes) + .sort((a, b) => b.depth - a.depth); const rects = nodesMerge .select("rect") - .attr("class", d => `treerect ${nodeLevelHelper(maxDepth, d)}`) - .style("x", d => d.x0) - .style("y", d => d.y0) - .style("width", d => calcWidth(d)) - .style("height", d => calcHeight(d)); + .attr("class", (d) => `treerect ${nodeLevelHelper(maxDepth, d)}`) + .style("x", (d) => d.x0) + .style("y", (d) => d.y0) + .style("width", (d) => calcWidth(d)) + .style("height", (d) => calcHeight(d)); color && - rects.style("fill", d => { + rects.style("fill", (d) => { if (d.data.color) { return color(d.data.color); } }); const labels = nodesMerge - .filter(d => d.value !== 0) + .filter((d) => d.value !== 0) .select("text") - .attr("x", d => d.x0 + calcWidth(d) / 2) - .attr("y", d => d.y0 + calcHeight(d) / 2) - .text(d => d.label); + .attr("x", (d) => d.x0 + calcWidth(d) / 2) + .attr("y", (d) => d.y0 + calcHeight(d) / 2) + .text((d) => d.label); - const rootNode = rects.filter(d => d.crossValue === "").datum(); + const rootNode = rects.filter((d) => d.crossValue === "").datum(); calculateRootLevelMap(nodesMerge, rootNode); toggleLabels(nodesMerge, 0, []); adjustLabelsThatOverflow(selectVisibleNodes(nodesMerge)); saveLabelMap(nodesMerge, 0); - if (settings.treemapRoute.length === 0) settings.treemapRoute.push(rootNode.crossValue); - rects.filter(d => d.children).on("click", d => changeLevel(d, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, parentCtrls)); - - returnToLevel(rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, parentCtrls); + if (settings.treemapRoute.length === 0) + settings.treemapRoute.push(rootNode.crossValue); + rects + .filter((d) => d.children) + .on("click", (d) => + changeLevel( + d, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + parentCtrls + ) + ); + + returnToLevel( + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + parentCtrls + ); }; _treemapSeries.settings = (...args) => { diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapTransitions.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapTransitions.js index 8cdcd25ed3..fa4381cbc0 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapTransitions.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapTransitions.js @@ -9,24 +9,90 @@ import * as d3 from "d3"; import {calcWidth, calcHeight} from "./treemapSeries"; -import {labelMapExists, toggleLabels, preventTextCollisions, lockTextOpacity, unlockTextOpacity, textOpacity, selectVisibleNodes, adjustLabelsThatOverflow, restoreLabels} from "./treemapLabel"; +import { + labelMapExists, + toggleLabels, + preventTextCollisions, + lockTextOpacity, + unlockTextOpacity, + textOpacity, + selectVisibleNodes, + adjustLabelsThatOverflow, + restoreLabels, +} from "./treemapLabel"; import {calculateSubTreeMap, saveLabelMap} from "./treemapLevelCalculation"; -export function returnToLevel(rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, parentCtrls) { +export function returnToLevel( + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + parentCtrls +) { if (settings.treemapLevel > 0) { const crossValues = rootNode.crossValue.split("|"); - executeTransition(rootNode, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, 0, crossValues, parentCtrls, 1, false); + executeTransition( + rootNode, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + 0, + crossValues, + parentCtrls, + 1, + false + ); - settings.treemapRoute.slice(1, settings.treemapRoute.length).forEach(cv => { - const d = nodesMerge.filter(d => d.crossValue === cv).datum(); - const crossValues = d.crossValue.split("|"); - calculateSubTreeMap(d, crossValues, nodesMerge, d.depth, rootNode, treemapDiv); - executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, d.depth, crossValues, parentCtrls, 1, false); - }); + settings.treemapRoute + .slice(1, settings.treemapRoute.length) + .forEach((cv) => { + const d = nodesMerge.filter((d) => d.crossValue === cv).datum(); + const crossValues = d.crossValue.split("|"); + calculateSubTreeMap( + d, + crossValues, + nodesMerge, + d.depth, + rootNode, + treemapDiv + ); + executeTransition( + d, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + d.depth, + crossValues, + parentCtrls, + 1, + false + ); + }); } } -export function changeLevel(d, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, parentCtrls) { +export function changeLevel( + d, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + parentCtrls +) { if (!d.children) return; if (settings.treemapLevel < d.depth) { @@ -38,14 +104,50 @@ export function changeLevel(d, rects, nodesMerge, labels, settings, treemapDiv, settings.treemapLevel = d.depth; const crossValues = d.crossValue.split("|"); - if (!d.mapLevel[settings.treemapLevel] || !d.mapLevel[settings.treemapLevel].levelRoot) { - calculateSubTreeMap(d, crossValues, nodesMerge, settings.treemapLevel, rootNode, treemapDiv); + if ( + !d.mapLevel[settings.treemapLevel] || + !d.mapLevel[settings.treemapLevel].levelRoot + ) { + calculateSubTreeMap( + d, + crossValues, + nodesMerge, + settings.treemapLevel, + rootNode, + treemapDiv + ); } - executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, settings.treemapLevel, crossValues, parentCtrls); + executeTransition( + d, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + settings.treemapLevel, + crossValues, + parentCtrls + ); } -function executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, treemapLevel, crossValues, parentCtrls, duration = 500, recordLabelMap = true) { +function executeTransition( + d, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + treemapLevel, + crossValues, + parentCtrls, + duration = 500, + recordLabelMap = true +) { const parent = d.parent; const t = treemapSvg @@ -53,33 +155,36 @@ function executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, t .duration(duration) .ease(d3.easeCubicOut); - nodesMerge.each(d => (d.target = d.mapLevel[treemapLevel])); + nodesMerge.each((d) => (d.target = d.mapLevel[treemapLevel])); if (!labelMapExists(d)) preventUserInteraction(nodesMerge, parentCtrls); // hide hidden svgs nodesMerge .transition(t) - .tween("data", d => { + .tween("data", (d) => { const i = d3.interpolate(d.current, d.target); - return t => (d.current = i(t)); + return (t) => (d.current = i(t)); }) - .styleTween("opacity", d => () => d.current.opacity) - .attrTween("pointer-events", d => () => (d.target.visible ? "all" : "none")); + .styleTween("opacity", (d) => () => d.current.opacity) + .attrTween( + "pointer-events", + (d) => () => d.target.visible ? "all" : "none" + ); rects .transition(t) - .filter(d => d.target.visible) - .styleTween("x", d => () => `${d.current.x0}px`) - .styleTween("y", d => () => `${d.current.y0}px`) - .styleTween("width", d => () => `${d.current.x1 - d.current.x0}px`) - .styleTween("height", d => () => `${d.current.y1 - d.current.y0}px`); + .filter((d) => d.target.visible) + .styleTween("x", (d) => () => `${d.current.x0}px`) + .styleTween("y", (d) => () => `${d.current.y0}px`) + .styleTween("width", (d) => () => `${d.current.x1 - d.current.x0}px`) + .styleTween("height", (d) => () => `${d.current.y1 - d.current.y0}px`); labels .transition(t) - .filter(d => d.target.visible) - .attrTween("x", d => () => d.current.x0 + calcWidth(d.current) / 2) - .attrTween("y", d => () => d.current.y0 + calcHeight(d.current) / 2) + .filter((d) => d.target.visible) + .attrTween("x", (d) => () => d.current.x0 + calcWidth(d.current) / 2) + .attrTween("y", (d) => () => d.current.y0 + calcHeight(d.current) / 2) .end() .catch(() => enableUserInteraction(nodesMerge)) .then(() => { @@ -91,8 +196,11 @@ function executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, t enableUserInteraction(nodesMerge, parentCtrls, parent); } }) - .catch(ex => { - console.error("Exception completing promises after main transition", ex); + .catch((ex) => { + console.error( + "Exception completing promises after main transition", + ex + ); enableUserInteraction(nodesMerge, parentCtrls, parent); }); @@ -109,7 +217,20 @@ function executeTransition(d, rects, nodesMerge, labels, settings, treemapDiv, t parentCtrls .hide(false) .text(d.label) - .onClick(() => changeLevel(parent, rects, nodesMerge, labels, settings, treemapDiv, treemapSvg, rootNode, parentCtrls, duration))(); + .onClick(() => + changeLevel( + parent, + rects, + nodesMerge, + labels, + settings, + treemapDiv, + treemapSvg, + rootNode, + parentCtrls, + duration + ) + )(); } else { parentCtrls.hide(true)(); } @@ -123,20 +244,25 @@ async function fadeTextTransition(labels, treemapSvg, duration = 400) { await labels .transition(t) - .filter(d => d.target.visible) + .filter((d) => d.target.visible) .tween("data", (d, i, labels) => { const label = labels[i]; - const interpolation = d3.interpolate(lockedOpacity(d), targetOpacity(label)); - return t => (d.current.opacity = interpolation(t)); + const interpolation = d3.interpolate( + lockedOpacity(d), + targetOpacity(label) + ); + return (t) => (d.current.opacity = interpolation(t)); }) - .styleTween("opacity", d => () => d.current.opacity) + .styleTween("opacity", (d) => () => d.current.opacity) .end() - .catch(ex => console.error("Exception in text fade transition", ex)) - .then(() => labels.each((_, i, labels) => unlockTextOpacity(labels[i]))); + .catch((ex) => console.error("Exception in text fade transition", ex)) + .then(() => + labels.each((_, i, labels) => unlockTextOpacity(labels[i])) + ); } -const lockedOpacity = d => d.target.textLockedAt.opacity; -const targetOpacity = d => textOpacity[d3.select(d).attr("class")]; +const lockedOpacity = (d) => d.target.textLockedAt.opacity; +const targetOpacity = (d) => textOpacity[d3.select(d).attr("class")]; const preventUserInteraction = (nodes, parentCtrls) => { parentCtrls.deactivate(true); diff --git a/packages/perspective-viewer-d3fc/src/js/tooltip/generateHTML.js b/packages/perspective-viewer-d3fc/src/js/tooltip/generateHTML.js index cfd911f40b..70cdda4516 100644 --- a/packages/perspective-viewer-d3fc/src/js/tooltip/generateHTML.js +++ b/packages/perspective-viewer-d3fc/src/js/tooltip/generateHTML.js @@ -23,7 +23,7 @@ function addDataValues(tooltipDiv, values) { .selectAll("li") .data(values) .join("li") - .each(function(d) { + .each(function (d) { select(this) .text(`${d.name}: `) .append("b") @@ -31,11 +31,11 @@ function addDataValues(tooltipDiv, values) { }); } -const formatNumber = value => +const formatNumber = (value) => value === null ? "-" : value.toLocaleString(undefined, { style: "decimal", minimumFractionDigits: get_type_config("float").precision, - maximumFractionDigits: get_type_config("float").precision + maximumFractionDigits: get_type_config("float").precision, }); diff --git a/packages/perspective-viewer-d3fc/src/js/tooltip/nearbyTip.js b/packages/perspective-viewer-d3fc/src/js/tooltip/nearbyTip.js index 1681013d3c..04cb6d0c65 100644 --- a/packages/perspective-viewer-d3fc/src/js/tooltip/nearbyTip.js +++ b/packages/perspective-viewer-d3fc/src/js/tooltip/nearbyTip.js @@ -31,8 +31,10 @@ export default () => { const chartPlotArea = `d3fc-${canvas ? "canvas" : "svg"}.plot-area`; if (xScale || yScale) { let tooltipData = null; - const pointer = fc.pointer().on("point", event => { - const closest = event.length ? getClosestDataPoint(event[0]) : null; + const pointer = fc.pointer().on("point", (event) => { + const closest = event.length + ? getClosestDataPoint(event[0]) + : null; tooltipData = closest ? [closest.data] : []; const useYScale = closest ? closest.scale : yScale; @@ -44,7 +46,11 @@ export default () => { .on("measure.nearbyTip", () => renderTip(selection, [])) .on("click", () => { if (tooltipData.length) { - raiseEvent(selection.node(), tooltipData[0], base.settings()); + raiseEvent( + selection.node(), + tooltipData[0], + base.settings() + ); } }) .call(pointer); @@ -62,20 +68,37 @@ export default () => { .append("circle") .attr("class", "nearbyTip") .merge(tips) - .attr("r", d => (size ? scale_factor * Math.sqrt(size(d.size)) : 10)) - .attr("transform", d => `translate(${xScale(d[xValueName])},${useYScale(d[yValueName])})`) + .attr("r", (d) => + size ? scale_factor * Math.sqrt(size(d.size)) : 10 + ) + .attr( + "transform", + (d) => + `translate(${xScale(d[xValueName])},${useYScale( + d[yValueName] + )})` + ) .style("stroke", "none") - .style("fill", d => color && withOpacity(color(d.key))); + .style("fill", (d) => color && withOpacity(color(d.key))); base(tips); }; - const getClosestDataPoint = pos => { - const distFn = scale => { - return v => { - if (v[yValueName] === undefined || v[yValueName] === null || v[xValueName] === undefined || v[xValueName] === null) return null; - - return Math.sqrt(Math.pow(xScale(v[xValueName]) - pos.x, 2) + Math.pow(scale(v[yValueName]) - pos.y, 2)); + const getClosestDataPoint = (pos) => { + const distFn = (scale) => { + return (v) => { + if ( + v[yValueName] === undefined || + v[yValueName] === null || + v[xValueName] === undefined || + v[xValueName] === null + ) + return null; + + return Math.sqrt( + Math.pow(xScale(v[xValueName]) - pos.x, 2) + + Math.pow(scale(v[yValueName]) - pos.y, 2) + ); }; }; @@ -86,8 +109,14 @@ export default () => { if (altDataWithScale) { // Check the alt data with its scale, to see if any are closer const dist2 = distFn(altDataWithScale.yScale); - const best2 = findBestFromData(altDataWithScale.data, dist2, Math.min); - return dist1(best1) <= dist2(best2) ? {data: best1, scale: yScale} : {data: best2, scale: altDataWithScale.yScale}; + const best2 = findBestFromData( + altDataWithScale.data, + dist2, + Math.min + ); + return dist1(best1) <= dist2(best2) + ? {data: best1, scale: yScale} + : {data: best2, scale: altDataWithScale.yScale}; } return {data: best1, scale: yScale}; }; diff --git a/packages/perspective-viewer-d3fc/src/js/tooltip/selectionData.js b/packages/perspective-viewer-d3fc/src/js/tooltip/selectionData.js index 2f50938c75..a7d26a0f71 100644 --- a/packages/perspective-viewer-d3fc/src/js/tooltip/selectionData.js +++ b/packages/perspective-viewer-d3fc/src/js/tooltip/selectionData.js @@ -12,7 +12,12 @@ export function toValue(type, value) { switch (type) { case "date": case "datetime": - return value instanceof Date ? value : new Date(parseInt(value)).toLocaleString("en-us", get_type_config(type).format); + return value instanceof Date + ? value + : new Date(parseInt(value)).toLocaleString( + "en-us", + get_type_config(type).format + ); case "integer": return parseInt(value, 10); case "float": @@ -23,19 +28,25 @@ export function toValue(type, value) { export function getGroupValues(data, settings) { if (settings.crossValues.length === 0) return []; - const groupValues = (data.crossValue.split ? data.crossValue.split("|") : [data.crossValue]) || [data.key]; + const groupValues = (data.crossValue.split + ? data.crossValue.split("|") + : [data.crossValue]) || [data.key]; return groupValues.map((cross, i) => ({ name: settings.crossValues[i].name, - value: toValue(settings.crossValues[i].type, cross) + value: toValue(settings.crossValues[i].type, cross), })); } export function getSplitValues(data, settings) { if (settings.splitValues.length === 0) return []; - const splitValues = data.key ? data.key.split("|") : data.mainValue.split ? data.mainValue.split("|") : [data.mainValue]; + const splitValues = data.key + ? data.key.split("|") + : data.mainValue.split + ? data.mainValue.split("|") + : [data.mainValue]; return settings.splitValues.map((split, i) => ({ name: split.name, - value: toValue(split.type, splitValues[i]) + value: toValue(split.type, splitValues[i]), })); } @@ -45,19 +56,25 @@ export function getDataValues(data, settings) { return [ { name: data.key, - value: data.mainValue - (data.baseValue || 0) - } + value: data.mainValue - (data.baseValue || 0), + }, ]; } return settings.mainValues.map((main, i) => ({ name: main.name, - value: toValue(main.type, data.mainValues[i]) + value: toValue(main.type, data.mainValues[i]), })); } return [ { name: settings.mainValues[0].name, - value: toValue(settings.mainValues[0].type, data.colorValue || data.mainValue - data.baseValue || data.mainValue || data.mainValues) - } + value: toValue( + settings.mainValues[0].type, + data.colorValue || + data.mainValue - data.baseValue || + data.mainValue || + data.mainValues + ), + }, ]; } diff --git a/packages/perspective-viewer-d3fc/src/js/tooltip/selectionEvent.js b/packages/perspective-viewer-d3fc/src/js/tooltip/selectionEvent.js index 0c2283e3be..d521099397 100644 --- a/packages/perspective-viewer-d3fc/src/js/tooltip/selectionEvent.js +++ b/packages/perspective-viewer-d3fc/src/js/tooltip/selectionEvent.js @@ -8,10 +8,10 @@ */ import {getGroupValues, getSplitValues, getDataValues} from "./selectionData"; -const mapToFilter = d => [d.name, "==", d.value]; +const mapToFilter = (d) => [d.name, "==", d.value]; export const raiseEvent = (node, data, settings) => { - const column_names = getDataValues(data, settings).map(d => d.name); + const column_names = getDataValues(data, settings).map((d) => d.name); const groupFilters = getGroupValues(data, settings).map(mapToFilter); const splitFilters = getSplitValues(data, settings).map(mapToFilter); @@ -24,8 +24,8 @@ export const raiseEvent = (node, data, settings) => { detail: { column_names, config: {filters}, - row: data.row - } + row: data.row, + }, }) ); }; @@ -33,9 +33,9 @@ export const raiseEvent = (node, data, settings) => { export const selectionEvent = () => { let settings = null; - const _event = selection => { + const _event = (selection) => { const node = selection.node(); - selection.on("click", data => raiseEvent(node, data, settings)); + selection.on("click", (data) => raiseEvent(node, data, settings)); }; _event.settings = (...args) => { diff --git a/packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js b/packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js index cbe0df3364..6538b25c2a 100644 --- a/packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js +++ b/packages/perspective-viewer-d3fc/src/js/tooltip/tooltip.js @@ -20,7 +20,7 @@ export const tooltip = () => { let settings = null; let centered = false; - const _tooltip = selection => { + const _tooltip = (selection) => { const node = selection.node(); if (!node || !node.isConnected) { @@ -94,7 +94,12 @@ function showTooltip(containerNode, node, tooltipDiv, centered) { let left = rect.left + rect.width / 2 - containerRect.left; let top = rect.top - containerRect.top + containerNode.scrollTop; - if (centered) top = rect.top + rect.height / 2 - containerRect.top + containerNode.scrollTop; + if (centered) + top = + rect.top + + rect.height / 2 - + containerRect.top + + containerNode.scrollTop; tooltipDiv .style("left", `${left}px`) @@ -121,7 +126,13 @@ function centerTip(tooltipDiv, containerRect) { return [newLeft, newTop]; } -function shiftIfOverflowingChartArea(tooltipDiv, containerRect, left, top, centered = false) { +function shiftIfOverflowingChartArea( + tooltipDiv, + containerRect, + left, + top, + centered = false +) { const tooltipDivRect = tooltipDiv.node().getBoundingClientRect(); if (isElementOverflowing(containerRect, tooltipDivRect)) { diff --git a/packages/perspective-viewer-d3fc/src/js/utils/utils.js b/packages/perspective-viewer-d3fc/src/js/utils/utils.js index 5cd4f3528d..34b286711d 100644 --- a/packages/perspective-viewer-d3fc/src/js/utils/utils.js +++ b/packages/perspective-viewer-d3fc/src/js/utils/utils.js @@ -12,19 +12,32 @@ export function getOrCreateElement(container, selector, createCallback) { return element.size() > 0 ? element : createCallback(); } -export function isElementOverflowing(containerRect, innerElementRect, direction = "right") { +export function isElementOverflowing( + containerRect, + innerElementRect, + direction = "right" +) { if (direction === "right" || direction === "bottom") { - return containerRect[direction] < innerElementRect[direction] ? true : false; + return containerRect[direction] < innerElementRect[direction] + ? true + : false; } if (direction === "left" || direction === "top") { - return containerRect[direction] > innerElementRect[direction] ? true : false; + return containerRect[direction] > innerElementRect[direction] + ? true + : false; } throw `Direction being checked for overflow is invalid: ${direction}`; } -export function isElementOverlapping(axis, immovableRect, elementRect, fuzz = 0) { +export function isElementOverlapping( + axis, + immovableRect, + elementRect, + fuzz = 0 +) { const dimension = axis === "x" ? "width" : "height"; const immovableInnerPoint = immovableRect[axis]; @@ -33,9 +46,15 @@ export function isElementOverlapping(axis, immovableRect, elementRect, fuzz = 0) const elementInnerPoint = elementRect[axis]; const elementOuterPoint = elementRect[axis] + elementRect[dimension]; - const innerPointInside = elementInnerPoint + fuzz > immovableInnerPoint && elementInnerPoint - fuzz < immovableOuterPoint; - const outerPointInside = elementOuterPoint + fuzz > immovableInnerPoint && elementOuterPoint - fuzz < immovableOuterPoint; - const pointsEitherSide = elementInnerPoint + fuzz < immovableInnerPoint && elementOuterPoint - fuzz > immovableOuterPoint; + const innerPointInside = + elementInnerPoint + fuzz > immovableInnerPoint && + elementInnerPoint - fuzz < immovableOuterPoint; + const outerPointInside = + elementOuterPoint + fuzz > immovableInnerPoint && + elementOuterPoint - fuzz < immovableOuterPoint; + const pointsEitherSide = + elementInnerPoint + fuzz < immovableInnerPoint && + elementOuterPoint - fuzz > immovableOuterPoint; return innerPointInside || outerPointInside || pointsEitherSide; } diff --git a/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js b/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js index 593752d241..337e54ce31 100644 --- a/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js +++ b/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js @@ -31,49 +31,79 @@ export default () => { settings.zoom = { k: transform.k, x: transform.x, - y: transform.y + y: transform.y, }; applyTransform(transform); selection.call(chart); - const noZoom = transform.k === 1 && transform.x === 0 && transform.y === 0; - - const zoomControls = getZoomControls(selection).style("display", noZoom ? "none" : ""); - zoomControls.select("#zoom-reset").on("click", () => selection.select(chartPlotArea).call(zoom.transform, d3.zoomIdentity)); + const noZoom = + transform.k === 1 && transform.x === 0 && transform.y === 0; + + const zoomControls = getZoomControls(selection).style( + "display", + noZoom ? "none" : "" + ); + zoomControls + .select("#zoom-reset") + .on("click", () => + selection + .select(chartPlotArea) + .call(zoom.transform, d3.zoomIdentity) + ); - const oneYear = zoomControls.select("#one-year").style("display", dateAxis ? "" : "none"); - const sixMonths = zoomControls.select("#six-months").style("display", dateAxis ? "" : "none"); - const oneMonth = zoomControls.select("#one-month").style("display", dateAxis ? "" : "none"); + const oneYear = zoomControls + .select("#one-year") + .style("display", dateAxis ? "" : "none"); + const sixMonths = zoomControls + .select("#six-months") + .style("display", dateAxis ? "" : "none"); + const oneMonth = zoomControls + .select("#one-month") + .style("display", dateAxis ? "" : "none"); if (dateAxis) { - const dateClick = endCalculation => () => { + const dateClick = (endCalculation) => () => { const start = new Date(xScale.domain()[0]); const end = new Date(start); endCalculation(start, end); const xRange = xCopy.range(); - const k = (xRange[1] - xRange[0]) / (xCopy(end) - xCopy(start)); + const k = + (xRange[1] - xRange[0]) / + (xCopy(end) - xCopy(start)); const x = -xCopy(start) * k; let y = 0; if (yScale) { - const yMiddle = yScale.domain().reduce((a, b) => a + b) / 2; + const yMiddle = + yScale.domain().reduce((a, b) => a + b) / 2; y = -yCopy(yMiddle) * k + yScale(yMiddle); } - selection.select(chartPlotArea).call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(k)); + selection + .select(chartPlotArea) + .call( + zoom.transform, + d3.zoomIdentity.translate(x, y).scale(k) + ); }; oneYear.on( "click", - dateClick((start, end) => end.setYear(start.getFullYear() + 1)) + dateClick((start, end) => + end.setYear(start.getFullYear() + 1) + ) ); sixMonths.on( "click", - dateClick((start, end) => end.setMonth(start.getMonth() + 6)) + dateClick((start, end) => + end.setMonth(start.getMonth() + 6) + ) ); oneMonth.on( "click", - dateClick((start, end) => end.setMonth(start.getMonth() + 1)) + dateClick((start, end) => + end.setMonth(start.getMonth() + 1) + ) ); } }); @@ -92,7 +122,9 @@ export default () => { if (yCopy) yCopy.range([0, d3.event.detail.height]); if (settings.zoom) { - const initialTransform = d3.zoomIdentity.translate(settings.zoom.x, settings.zoom.y).scale(settings.zoom.k); + const initialTransform = d3.zoomIdentity + .translate(settings.zoom.x, settings.zoom.y) + .scale(settings.zoom.k); plotArea.call(zoom.transform, initialTransform); } }) @@ -158,7 +190,7 @@ export default () => { return zoomableChart; }; - const applyTransform = transform => { + const applyTransform = (transform) => { const changeArgs = {...transform}; if (xScale) { xScale.domain(transform.rescaleX(xCopy).domain()); @@ -174,7 +206,7 @@ export default () => { onChange(changeArgs); }; - const getZoomControls = container => + const getZoomControls = (container) => getOrCreateElement(container, ".zoom-controls", () => container .append("div") @@ -183,7 +215,7 @@ export default () => { .html(template) ); - const zoomableScale = scale => { + const zoomableScale = (scale) => { if (scale && scale.nice) { return scale; } diff --git a/packages/perspective-viewer-d3fc/src/less/chart.less b/packages/perspective-viewer-d3fc/src/less/chart.less index 770f45b070..f650456ee8 100644 --- a/packages/perspective-viewer-d3fc/src/less/chart.less +++ b/packages/perspective-viewer-d3fc/src/less/chart.less @@ -6,10 +6,10 @@ * the Apache License 2.0. The full license can be found in the LICENSE file. * */ - - @import "~@finos/perspective-viewer/src/less/variables.less"; - - @sans-serif-fonts: Arial, sans-serif; + +@import "~@finos/perspective-viewer/src/less/variables.less"; + +@sans-serif-fonts: Arial, sans-serif; :host { .chart { @@ -93,8 +93,6 @@ } &.treemap { - - & .treemap-container { position: relative; @@ -202,7 +200,8 @@ line-height: 1em !important; } - & .x-label, & .y-label { + & .x-label, + & .y-label { color: var(--d3fc-label--color, inherit); font-size: 14px; } @@ -355,7 +354,7 @@ &:hover { background-color: var(--d3fc-legend--background, #ffffff); box-shadow: @elevation1; - transition: box-shadow 0.2s, background-color 0.2s; + transition: box-shadow 0.2s, background-color 0.2s; } } diff --git a/packages/perspective-viewer-d3fc/test/html/shares-template.html b/packages/perspective-viewer-d3fc/test/html/shares-template.html index 34217ef09b..7e084c558a 100644 --- a/packages/perspective-viewer-d3fc/test/html/shares-template.html +++ b/packages/perspective-viewer-d3fc/test/html/shares-template.html @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/packages/perspective-viewer-d3fc/test/html/simple-template.html b/packages/perspective-viewer-d3fc/test/html/simple-template.html index c52a5f87dd..86495e1ac0 100644 --- a/packages/perspective-viewer-d3fc/test/html/simple-template.html +++ b/packages/perspective-viewer-d3fc/test/html/simple-template.html @@ -1,64 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/packages/perspective-viewer-d3fc/test/html/themed-template.html b/packages/perspective-viewer-d3fc/test/html/themed-template.html index 79545496ba..3f00ff03bc 100644 --- a/packages/perspective-viewer-d3fc/test/html/themed-template.html +++ b/packages/perspective-viewer-d3fc/test/html/themed-template.html @@ -1,89 +1,83 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/packages/perspective-viewer-d3fc/test/js/integration/area.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/area.spec.js index e32929f20e..96dee90a78 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/area.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/area.spec.js @@ -17,7 +17,9 @@ withTemplate("area", "Y Area"); async function get_contents(page) { return await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer perspective-viewer-d3fc-yarea").shadowRoot.querySelector("svg"); + const viewer = document + .querySelector("perspective-viewer perspective-viewer-d3fc-yarea") + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }); } diff --git a/packages/perspective-viewer-d3fc/test/js/integration/bar.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/bar.spec.js index 5327476fc5..f6cec17ec7 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/bar.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/bar.spec.js @@ -20,9 +20,13 @@ withTemplate("bar-x", "X Bar"); withTemplate("bar-themed", "Y Bar", {template: "themed-template"}); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js index f87c45c59f..85656d08b0 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/candlestick.spec.js @@ -18,20 +18,51 @@ utils.with_server({}, () => { describe.page( "candlestick.html", () => { - test.skip("filter by a single instrument.", async page => { + test.skip("filter by a single instrument.", async (page) => { const viewer = await page.$("perspective-viewer"); - await page.evaluate(element => element.setAttribute("filters", '[["Name", "==", "BARC"]]'), viewer); - await page.evaluate(async element => await element.flush(), viewer); + await page.evaluate( + (element) => + element.setAttribute( + "filters", + '[["Name", "==", "BARC"]]' + ), + viewer + ); + await page.evaluate( + async (element) => await element.flush(), + viewer + ); await page.shadow_blur(); }); - test.skip("filter to date range.", async page => { + test.skip("filter to date range.", async (page) => { const viewer = await page.$("perspective-viewer"); - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate(element => element.setAttribute("column-pivots", '["Name"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(element => element.setAttribute("filters", '[["Date", ">", "2019-01-01"]]'), viewer); - await page.evaluate(async element => await element.flush(), viewer); + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Name"]'), + viewer + ); + await page.waitForSelector( + "perspective-viewer:not([updating])" + ); + await page.evaluate( + (element) => + element.setAttribute( + "filters", + '[["Date", ">", "2019-01-01"]]' + ), + viewer + ); + await page.evaluate( + async (element) => await element.flush(), + viewer + ); await page.shadow_blur(); }); }, diff --git a/packages/perspective-viewer-d3fc/test/js/integration/heatmap.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/heatmap.spec.js index 9590d77836..8b4963733a 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/heatmap.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/heatmap.spec.js @@ -17,9 +17,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("heatmap", "Heatmap"); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js index 5ef8752c29..ac3c2989f9 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/line.spec.js @@ -16,9 +16,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("line", "Y Line"); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js index b343f5487e..bcd50ce495 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/ohlc.spec.js @@ -18,20 +18,49 @@ utils.with_server({}, () => { describe.page( "ohlc.html", () => { - test.skip("filter by a single instrument.", async page => { + test.skip("filter by a single instrument.", async (page) => { const viewer = await page.$("perspective-viewer"); - await page.evaluate(element => element.setAttribute("filters", '[["Name", "==", "BARC"]]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate( + (element) => + element.setAttribute( + "filters", + '[["Name", "==", "BARC"]]' + ), + viewer + ); + await page.waitForSelector( + "perspective-viewer:not([updating])" + ); await page.shadow_blur(); }); - test.skip("filter to date range.", async page => { + test.skip("filter to date range.", async (page) => { const viewer = await page.$("perspective-viewer"); - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate(element => element.setAttribute("column-pivots", '["Name"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(element => element.setAttribute("filters", '[["Date", ">", "2019-01-01"]]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Name"]'), + viewer + ); + await page.waitForSelector( + "perspective-viewer:not([updating])" + ); + await page.evaluate( + (element) => + element.setAttribute( + "filters", + '[["Date", ">", "2019-01-01"]]' + ), + viewer + ); + await page.waitForSelector( + "perspective-viewer:not([updating])" + ); await page.shadow_blur(); }); }, diff --git a/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js index 7c739cd976..3135bc62bd 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/scatter.spec.js @@ -16,9 +16,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("scatter", "X/Y Scatter", {columns: ["Sales", "Quantity"]}); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/simple-template.js b/packages/perspective-viewer-d3fc/test/js/integration/simple-template.js index c3187bbb21..8538ee46c0 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/simple-template.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/simple-template.js @@ -13,10 +13,19 @@ const path = require("path"); const SIMPLE_TEMPLATE = "simple-template"; const DEFAULT_COLUMNS = ["Sales"]; -const withTemplate = (name, view, {template = SIMPLE_TEMPLATE, columns = DEFAULT_COLUMNS} = {}) => { +const withTemplate = ( + name, + view, + {template = SIMPLE_TEMPLATE, columns = DEFAULT_COLUMNS} = {} +) => { const dir_name = path.join(__dirname, "..", "..", "..", "dist", "umd"); - const templateContent = fs.readFileSync(path.join(dir_name, `${template}.html`), "utf8"); - const content = templateContent.replace("__view_name", view).replace("__columns", JSON.stringify(columns)); + const templateContent = fs.readFileSync( + path.join(dir_name, `${template}.html`), + "utf8" + ); + const content = templateContent + .replace("__view_name", view) + .replace("__columns", JSON.stringify(columns)); fs.writeFileSync(path.join(dir_name, `${name}.html`), content); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js index 4cbd416f11..2abeb57e70 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/sunburst.spec.js @@ -16,9 +16,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("sunburst", "Sunburst"); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer?.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js index 616ebe477c..bcdf1e9274 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/treemap.spec.js @@ -16,9 +16,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("treemap", "Treemap", {columns: ["Quantity", "Profit"]}); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer?.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js index 263ecefa75..2aff412cdf 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js @@ -16,9 +16,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("xyline", "X/Y Line", {columns: ["Sales", "Quantity"]}); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/integration/yscatter.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/yscatter.spec.js index d040838f62..b45688c0a8 100644 --- a/packages/perspective-viewer-d3fc/test/js/integration/yscatter.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/integration/yscatter.spec.js @@ -17,9 +17,13 @@ const {withTemplate} = require("./simple-template"); withTemplate("yscatter", "Y Scatter"); function get_contents(temp) { - return async function(page) { - return await page.evaluate(async temp => { - const viewer = document.querySelector(`perspective-viewer perspective-viewer-d3fc-${temp}`).shadowRoot.querySelector("svg"); + return async function (page) { + return await page.evaluate(async (temp) => { + const viewer = document + .querySelector( + `perspective-viewer perspective-viewer-d3fc-${temp}` + ) + .shadowRoot.querySelector("svg"); return viewer.outerHTML || "MISSING"; }, temp); }; diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js index 6a4589acdb..7bd5eed569 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/data/findBest.spec.js @@ -14,13 +14,13 @@ describe("findBestFromData should", () => { test("find the right value using the compareFn", () => { const array = [1, 2, 3, 4, 5]; - const result = findBestFromData(array, d => d, compareFn); + const result = findBestFromData(array, (d) => d, compareFn); expect(result).toEqual(5); }); test("work for stacked arrays", () => { const array = [[1, 2, 3], 4, 5, [6, [7, 8]]]; - const result = findBestFromData(array, d => d, compareFn); + const result = findBestFromData(array, (d) => d, compareFn); expect(result).toEqual(8); }); }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js index 37cee679b1..6132aa9432 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js @@ -22,10 +22,10 @@ describe("groupAndStackData should", () => { data: [ {value1: 10, __ROW_PATH__: ["CROSS1.1"]}, {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, - {value1: 30, __ROW_PATH__: ["CROSS1.1"]} + {value1: 30, __ROW_PATH__: ["CROSS1.1"]}, ], mainValues: [{name: "value1", type: "integer"}], - splitValues: [] + splitValues: [], }; const groupedResult = groupAndStackData(settings); @@ -36,18 +36,18 @@ describe("groupAndStackData should", () => { const suppliedData = [ {value1: 10, __ROW_PATH__: ["CROSS1.1"]}, {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, - {value1: 30, __ROW_PATH__: ["CROSS1.1"]} + {value1: 30, __ROW_PATH__: ["CROSS1.1"]}, ]; const settings = { crossValues: [{name: "cross1", type: "string"}], data: suppliedData, mainValues: [{name: "value1", type: "integer"}], - splitValues: [] + splitValues: [], }; const extraData = suppliedData.concat([ {value1: 40, __ROW_PATH__: ["CROSS1.3"]}, - {value1: 50, __ROW_PATH__: ["CROSS1.3"]} + {value1: 50, __ROW_PATH__: ["CROSS1.3"]}, ]); const groupedResult = groupAndStackData(settings, extraData); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js b/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js index 4bac192c2e..60c2559438 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/data/testTreeData.js @@ -2,28 +2,28 @@ export const data = [ { __ROW_PATH__: ["Central", "Furniture"], Sales: 163797.16380000004, - Quantity: 1827 + Quantity: 1827, }, { __ROW_PATH__: ["Central", "Technology"], Sales: 170416.31200000003, - Quantity: 1544 + Quantity: 1544, }, { __ROW_PATH__: ["East", "Furniture"], Sales: 208291.20400000017, - Quantity: 2214 + Quantity: 2214, }, { __ROW_PATH__: ["East", "Technology"], Sales: 264973.9810000004, - Quantity: 1942 - } + Quantity: 1942, + }, ]; const item = { __ROW_PATH__: ["East"], - Quantity: 4156 + Quantity: 4156, }; export const agg_paths = [ @@ -32,7 +32,7 @@ export const agg_paths = [ [item, item, item], [item, item, item], [item, item, item], - [item, item, item] + [item, item, item], ]; export const splitData = [ @@ -45,7 +45,7 @@ export const splitData = [ "Second Class|Sales": 31187.1234, "Second Class|Quantity": 343, "Standard Class|Sales": 102729.92900000002, - "Standard Class|Quantity": 1118 + "Standard Class|Quantity": 1118, }, { __ROW_PATH__: ["Central", "Office Supplies"], @@ -56,7 +56,7 @@ export const splitData = [ "Second Class|Sales": 34307.253, "Second Class|Quantity": 1063, "Standard Class|Sales": 107510.43999999997, - "Standard Class|Quantity": 3446 + "Standard Class|Quantity": 3446, }, { __ROW_PATH__: ["Central", "Technology"], @@ -67,7 +67,7 @@ export const splitData = [ "Second Class|Sales": 38055.629, "Second Class|Quantity": 389, "Standard Class|Sales": 108287.191, - "Standard Class|Quantity": 873 + "Standard Class|Quantity": 873, }, { __ROW_PATH__: ["East", "Furniture"], @@ -78,7 +78,7 @@ export const splitData = [ "Second Class|Sales": 44035.93700000001, "Second Class|Quantity": 433, "Standard Class|Sales": 121992.05199999994, - "Standard Class|Quantity": 1268 + "Standard Class|Quantity": 1268, }, { __ROW_PATH__: ["East", "Office Supplies"], @@ -89,7 +89,7 @@ export const splitData = [ "Second Class|Sales": 43205.097, "Second Class|Quantity": 1249, "Standard Class|Sales": 116703.0659999999, - "Standard Class|Quantity": 3776 + "Standard Class|Quantity": 3776, }, { __ROW_PATH__: ["East", "Technology"], @@ -100,19 +100,19 @@ export const splitData = [ "Second Class|Sales": 29304.48999999999, "Second Class|Quantity": 344, "Standard Class|Sales": 166626.71300000005, - "Standard Class|Quantity": 1170 - } + "Standard Class|Quantity": 1170, + }, ]; export const mainValues = [ { name: "Sales", - type: "float" + type: "float", }, { name: "Quantity", - type: "integer" - } + type: "integer", + }, ]; export const realValues = ["Sales", "Quantity"]; @@ -120,10 +120,10 @@ export const realValues = ["Sales", "Quantity"]; export const crossValues = [ { name: "Region", - type: "string" + type: "string", }, { name: "Category", - type: "string" - } + type: "string", + }, ]; diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js index ed882c44bc..f1f7450115 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/data/treeData.spec.js @@ -8,21 +8,46 @@ */ import {treeData} from "../../../../src/js/data/treeData"; -import {data, splitData, mainValues, crossValues, realValues, agg_paths} from "./testTreeData"; +import { + data, + splitData, + mainValues, + crossValues, + realValues, + agg_paths, +} from "./testTreeData"; describe("treeData should", () => { test("create a structure with the right number of levels", () => { - const {data: result} = treeData({data, agg_paths, mainValues, crossValues, realValues})[0]; + const {data: result} = treeData({ + data, + agg_paths, + mainValues, + crossValues, + realValues, + })[0]; expect(result.height).toEqual(2); }); test("calculate the correct color extents", () => { - const {extents} = treeData({data, agg_paths, mainValues, crossValues, realValues})[0]; + const {extents} = treeData({ + data, + agg_paths, + mainValues, + crossValues, + realValues, + })[0]; expect(extents).toEqual([1544, 4156]); }); test("produce tree data for each split", () => { - const result = treeData({data: splitData, agg_paths, mainValues, crossValues, realValues}); + const result = treeData({ + data: splitData, + agg_paths, + mainValues, + crossValues, + realValues, + }); expect(result.length).toEqual(4); }); }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js index 1574d6d4a5..52b54f8140 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js @@ -34,7 +34,7 @@ const styleVariables = { "--d3fc-negative--gradient": `linear-gradient( #feeb65 100%, #4d342f 0% - )` + )`, }; describe("colorStyles should", () => { @@ -47,7 +47,7 @@ describe("colorStyles should", () => { settings = {}; window.ShadyCSS = { - getComputedStyleValue: sinon.spy((e, d) => styleVariables[d]) + getComputedStyleValue: sinon.spy((e, d) => styleVariables[d]), }; }); @@ -62,7 +62,9 @@ describe("colorStyles should", () => { expect(result.opacity).toEqual(0.5); expect(result.series).toEqual(styleVariables["--d3fc-series"]); for (let n = 1; n <= 10; n++) { - expect(result[`series-${n}`]).toEqual(styleVariables[`--d3fc-series-${n}`]); + expect(result[`series-${n}`]).toEqual( + styleVariables[`--d3fc-series-${n}`] + ); } }); @@ -73,15 +75,15 @@ describe("colorStyles should", () => { expect(result.gradient.full).toEqual([ [0, "rgba(77, 52, 47, 0.5)"], [0.5, "rgba(240, 240, 240, 0.5)"], - [1, "rgba(26, 35, 126, 0.5)"] + [1, "rgba(26, 35, 126, 0.5)"], ]); expect(result.gradient.positive).toEqual([ [0, "rgba(220, 237, 200, 0.5)"], - [1, "rgba(26, 35, 126, 0.5)"] + [1, "rgba(26, 35, 126, 0.5)"], ]); expect(result.gradient.negative).toEqual([ [0, "rgba(77, 52, 47, 0.5)"], - [1, "rgba(254, 235, 101, 0.5)"] + [1, "rgba(254, 235, 101, 0.5)"], ]); }); }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js index b6928088fa..acfe146701 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js @@ -15,19 +15,19 @@ const settings = { gradient: { positive: [ [0, "rgb(0, 0, 0)"], - [1, "rgb(100, 0, 0)"] + [1, "rgb(100, 0, 0)"], ], negative: [ [0, "rgb(0, 0, 0)"], - [1, "rgb(0, 100, 0)"] + [1, "rgb(0, 100, 0)"], ], full: [ [0, "rgb(100, 0, 0)"], [0.5, "rgb(0, 0, 0)"], - [1, "rgb(0, 0, 100)"] - ] - } - } + [1, "rgb(0, 0, 100)"], + ], + }, + }, }; describe("seriesRange", () => { @@ -58,7 +58,11 @@ describe("seriesRange", () => { }); test("create linear range from data extent", () => { - const result = seriesRange.seriesLinearRange(settings, [], "test-value"); + const result = seriesRange.seriesLinearRange( + settings, + [], + "test-value" + ); expect(result.domain()).toEqual([100, 1100]); result.range([0, 100]); @@ -70,7 +74,12 @@ describe("seriesRange", () => { }); test("create linear range from custom extent", () => { - const result = seriesRange.seriesLinearRange(settings, [], "test-value", [200, 300]); + const result = seriesRange.seriesLinearRange( + settings, + [], + "test-value", + [200, 300] + ); sinon.assert.notCalled(domainStub); expect(result.domain()).toEqual([200, 300]); @@ -89,7 +98,11 @@ describe("seriesRange", () => { test("return color range from data extent", () => { const data = []; - const result = seriesRange.seriesColorRange(settings, data, "test-value"); + const result = seriesRange.seriesColorRange( + settings, + data, + "test-value" + ); expect(result.domain()).toEqual([100, 1100]); @@ -100,14 +113,24 @@ describe("seriesRange", () => { }); test("create linear range from custom extent", () => { - const result = seriesRange.seriesColorRange(settings, [], "test-value", [200, 300]); + const result = seriesRange.seriesColorRange( + settings, + [], + "test-value", + [200, 300] + ); sinon.assert.notCalled(domainStub); expect(result.domain()).toEqual([200, 300]); }); test("return negative color range from custom extent", () => { - const result = seriesRange.seriesColorRange(settings, [], "test-value", [-200, -100]); + const result = seriesRange.seriesColorRange( + settings, + [], + "test-value", + [-200, -100] + ); expect(result(-200)).toEqual("rgb(0, 0, 0)"); expect(result(-160)).toEqual("rgb(0, 40, 0)"); @@ -116,7 +139,12 @@ describe("seriesRange", () => { }); test("return full color range from custom extent", () => { - const result = seriesRange.seriesColorRange(settings, [], "test-value", [-100, 100]); + const result = seriesRange.seriesColorRange( + settings, + [], + "test-value", + [-100, 100] + ); expect(result(-100)).toEqual("rgb(100, 0, 0)"); expect(result(-40)).toEqual("rgb(40, 0, 0)"); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js index 12ea6aeb13..8fe8b9e7ed 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/generateHTML.spec.js @@ -15,16 +15,14 @@ describe("tooltip generateHTML should", () => { let settings = null; beforeEach(() => { - tooltip = select("body") - .append("div") - .classed("tooltip-test", true); + tooltip = select("body").append("div").classed("tooltip-test", true); tooltip.append("div").attr("id", "tooltip-values"); settings = { crossValues: [], splitValues: [], mainValues: [{name: "main-1", type: "integer"}], - realValues: ["main-1"] + realValues: ["main-1"], }; }); afterEach(() => { @@ -41,7 +39,7 @@ describe("tooltip generateHTML should", () => { test("show single mainValue", () => { const data = { - mainValue: 101 + mainValue: 101, }; generateHtml(tooltip, data, settings); expect(getContent()).toEqual(["main-1: 101"]); @@ -51,7 +49,7 @@ describe("tooltip generateHTML should", () => { settings.mainValues.push({name: "main-2", type: "float"}); settings.realValues.push("main-2"); const data = { - mainValues: [101, 202.22] + mainValues: [101, 202.22], }; generateHtml(tooltip, data, settings); expect(getContent()).toEqual(["main-1: 101", "main-2: 202.22"]); @@ -61,16 +59,21 @@ describe("tooltip generateHTML should", () => { settings.mainValues[0].type = "datetime"; const testDate = new Date("2019-04-03T15:15Z"); const data = { - mainValue: testDate.getTime() + mainValue: testDate.getTime(), }; generateHtml(tooltip, data, settings); - expect(getContent()).toEqual([`main-1: ${testDate.toLocaleString("en-us", get_type_config("datetime").format)}`]); + expect(getContent()).toEqual([ + `main-1: ${testDate.toLocaleString( + "en-us", + get_type_config("datetime").format + )}`, + ]); }); test("format mainValue as integer", () => { settings.mainValues[0].type = "integer"; const data = { - mainValue: 12345.6789 + mainValue: 12345.6789, }; generateHtml(tooltip, data, settings); expect(getContent()).toEqual(["main-1: 12,345"]); @@ -79,7 +82,7 @@ describe("tooltip generateHTML should", () => { test("format mainValue as decimal", () => { settings.mainValues[0].type = "float"; const data = { - mainValue: 12345.6789 + mainValue: 12345.6789, }; generateHtml(tooltip, data, settings); expect(getContent()).toEqual(["main-1: 12,345.679"]); @@ -89,7 +92,7 @@ describe("tooltip generateHTML should", () => { settings.crossValues.push({name: "cross-1", type: "string"}); const data = { crossValue: "tc-1", - mainValue: 101 + mainValue: 101, }; generateHtml(tooltip, data, settings); @@ -101,18 +104,22 @@ describe("tooltip generateHTML should", () => { settings.crossValues.push({name: "cross-2", type: "integer"}); const data = { crossValue: "tc-1|1001", - mainValue: 101 + mainValue: 101, }; generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["cross-1: tc-1", "cross-2: 1,001", "main-1: 101"]); + expect(getContent()).toEqual([ + "cross-1: tc-1", + "cross-2: 1,001", + "main-1: 101", + ]); }); test("show with single splitValue", () => { settings.splitValues.push({name: "split-1", type: "string"}); const data = { key: "ts-1", - mainValue: 101 + mainValue: 101, }; generateHtml(tooltip, data, settings); @@ -124,10 +131,14 @@ describe("tooltip generateHTML should", () => { settings.splitValues.push({name: "split-2", type: "integer"}); const data = { key: "ts-1|1001", - mainValue: 101 + mainValue: 101, }; generateHtml(tooltip, data, settings); - expect(getContent()).toEqual(["split-1: ts-1", "split-2: 1,001", "main-1: 101"]); + expect(getContent()).toEqual([ + "split-1: ts-1", + "split-2: 1,001", + "main-1: 101", + ]); }); }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js index 99aa6597b5..1768a38d57 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/tooltip/tooltip.spec.js @@ -15,8 +15,8 @@ describe("tooltip with", () => { let settings = null; const data = [{mainValue: 101}]; - const awaitTransition = selection => { - return new Promise(resolve => { + const awaitTransition = (selection) => { + return new Promise((resolve) => { const transition = selection.transition(); let n = transition.size(); if (n === 0) { @@ -51,7 +51,7 @@ describe("tooltip with", () => { settings = { crossValues: [], splitValues: [], - mainValues: [{name: "main-1", type: "integer"}] + mainValues: [{name: "main-1", type: "integer"}], }; }); afterEach(() => { @@ -82,9 +82,7 @@ describe("tooltip with", () => { let tooltipDiv; let tooltipComponent; beforeEach(async () => { - tooltipComponent = tooltip() - .settings(settings) - .alwaysShow(true); + tooltipComponent = tooltip().settings(settings).alwaysShow(true); tooltipComponent(testElement); tooltipDiv = container.select("div.tooltip"); @@ -98,7 +96,9 @@ describe("tooltip with", () => { test("hide a tooltip if no element", async () => { tooltipComponent(container.select("div.notexists")); await awaitTransition(tooltipDiv); - expect(Math.floor(parseFloat(tooltipDiv.style("opacity")))).toEqual(0); + expect(Math.floor(parseFloat(tooltipDiv.style("opacity")))).toEqual( + 0 + ); }); }); }); diff --git a/packages/perspective-viewer-datagrid/babel.config.js b/packages/perspective-viewer-datagrid/babel.config.js index b4fc2ffc80..74d4268de7 100644 --- a/packages/perspective-viewer-datagrid/babel.config.js +++ b/packages/perspective-viewer-datagrid/babel.config.js @@ -6,13 +6,13 @@ module.exports = { targets: { chrome: "70", node: "12", - ios: "13" + ios: "13", }, modules: process.env.BABEL_MODULE || false, useBuiltIns: "usage", - corejs: 3 - } - ] + corejs: 3, + }, + ], ], sourceType: "unambiguous", plugins: [ @@ -20,6 +20,6 @@ module.exports = { ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining" - ] + "@babel/plugin-proposal-optional-chaining", + ], }; diff --git a/packages/perspective-viewer-datagrid/package.json b/packages/perspective-viewer-datagrid/package.json index 393bf0fe10..7541f847a8 100644 --- a/packages/perspective-viewer-datagrid/package.json +++ b/packages/perspective-viewer-datagrid/package.json @@ -46,4 +46,4 @@ "devDependencies": { "@finos/perspective-test": "^0.10.3" } -} \ No newline at end of file +} diff --git a/packages/perspective-viewer-datagrid/src/config/rollup.config.js b/packages/perspective-viewer-datagrid/src/config/rollup.config.js index a58c3e57a9..b7f39bef86 100644 --- a/packages/perspective-viewer-datagrid/src/config/rollup.config.js +++ b/packages/perspective-viewer-datagrid/src/config/rollup.config.js @@ -11,31 +11,34 @@ export default () => { return [ { input: "src/js/plugin.js", - external: id => { - let include = id.startsWith(".") || require.resolve(id).indexOf(PROJECT_PATH) === 0 || id.endsWith(".less"); + external: (id) => { + let include = + id.startsWith(".") || + require.resolve(id).indexOf(PROJECT_PATH) === 0 || + id.endsWith(".less"); return !include; }, output: { sourcemap: true, - file: "dist/esm/perspective-viewer-datagrid.js" + file: "dist/esm/perspective-viewer-datagrid.js", }, plugins: [ nodeResolve(), babel({ exclude: "node_modules/**", - babelHelpers: "bundled" + babelHelpers: "bundled", }), filesize(), postcss({ inject: false, sourceMap: false, - minimize: {preset: "lite"} + minimize: {preset: "lite"}, }), - sourcemaps() - ].filter(x => x), + sourcemaps(), + ].filter((x) => x), watch: { - clearScreen: false - } - } + clearScreen: false, + }, + }, ]; }; diff --git a/packages/perspective-viewer-datagrid/src/config/webpack.config.js b/packages/perspective-viewer-datagrid/src/config/webpack.config.js index 46ddb01a98..c9e38fa25f 100644 --- a/packages/perspective-viewer-datagrid/src/config/webpack.config.js +++ b/packages/perspective-viewer-datagrid/src/config/webpack.config.js @@ -1,14 +1,14 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/esm/perspective-viewer-datagrid.js", output: { filename: "perspective-viewer-datagrid.js", library: "perspective-viewer-datagrid", libraryTarget: "umd", - path: path.resolve(__dirname, "../../dist/umd") - } + path: path.resolve(__dirname, "../../dist/umd"), + }, }) ); diff --git a/packages/perspective-viewer-datagrid/src/js/click.js b/packages/perspective-viewer-datagrid/src/js/click.js index 469c3e6e63..e3433a4499 100644 --- a/packages/perspective-viewer-datagrid/src/js/click.js +++ b/packages/perspective-viewer-datagrid/src/js/click.js @@ -23,8 +23,8 @@ async function clickListener(table, viewer, event) { detail: { row, column_names, - config - } + config, + }, }) ); } diff --git a/packages/perspective-viewer-datagrid/src/js/color_utils.js b/packages/perspective-viewer-datagrid/src/js/color_utils.js index eeb87e95a2..e27a058d6b 100644 --- a/packages/perspective-viewer-datagrid/src/js/color_utils.js +++ b/packages/perspective-viewer-datagrid/src/js/color_utils.js @@ -20,5 +20,7 @@ export function rgbaToRgb([r, g, b, a], source = [255, 255, 255]) { // Chroma does this but why bother? export function infer_foreground_from_background([r, g, b]) { // TODO Implement dark/light themes. - return Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114) > 130 ? "#161616" : "#ffffff"; + return Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114) > 130 + ? "#161616" + : "#ffffff"; } diff --git a/packages/perspective-viewer-datagrid/src/js/editing.js b/packages/perspective-viewer-datagrid/src/js/editing.js index c807252ca7..2e745a1395 100644 --- a/packages/perspective-viewer-datagrid/src/js/editing.js +++ b/packages/perspective-viewer-datagrid/src/js/editing.js @@ -11,13 +11,13 @@ const selected_position_map = new WeakMap(); function lock(body) { let lock; - return async function(...args) { + return async function (...args) { if (!!lock && (await lock) && !!lock) { return; } let resolve; - lock = new Promise(x => (resolve = x)); + lock = new Promise((x) => (resolve = x)); await body.apply(this, args); lock = undefined; resolve(); @@ -56,7 +56,7 @@ function write(table, model, active_cell) { const msg = { __INDEX__: id, - [model._column_paths[meta.x]]: text + [model._column_paths[meta.x]]: text, }; model._table.update([msg], {port_id: model._edit_port}); @@ -65,13 +65,15 @@ function write(table, model, active_cell) { } function isEditable(viewer) { - const has_pivots = this._config.row_pivots.length === 0 && this._config.column_pivots.length === 0; + const has_pivots = + this._config.row_pivots.length === 0 && + this._config.column_pivots.length === 0; const selectable = viewer.hasAttribute("selectable"); const editable = viewer.hasAttribute("editable"); return has_pivots && !selectable && editable; } -const moveSelection = lock(async function(table, active_cell, dx, dy) { +const moveSelection = lock(async function (table, active_cell, dx, dy) { const meta = table.getMeta(active_cell); const num_columns = this._column_paths.length; const num_rows = this._num_rows; @@ -94,7 +96,13 @@ const moveSelection = lock(async function(table, active_cell, dx, dy) { const ymax = Math.min(meta.y0 + 10, num_rows); let x = meta.x0 + dx, y = meta.y0 + dy; - while (!focusStyleListener(table) && x >= xmin && x < xmax && y >= ymin && y < ymax) { + while ( + !focusStyleListener(table) && + x >= xmin && + x < xmax && + y >= ymin && + y < ymax + ) { await table.scrollToCell(x, y, num_columns, num_rows); selected_position_map.set(table, selected_position); x += dx; @@ -118,20 +126,26 @@ function editableStyleListener(table, viewer) { } } -const focusStyleListener = table => { +const focusStyleListener = (table) => { const tds = table.querySelectorAll("td"); const selected_position = selected_position_map.get(table); if (selected_position) { for (const td of tds) { const meta = table.getMeta(td); - if (meta.x === selected_position.x && meta.y === selected_position.y) { + if ( + meta.x === selected_position.x && + meta.y === selected_position.y + ) { if (document.activeElement !== td) { td.focus({preventScroll: true}); } return true; } } - if (document.activeElement !== document.body && table.contains(document.activeElement)) { + if ( + document.activeElement !== document.body && + table.contains(document.activeElement) + ) { document.activeElement.blur(); } } @@ -196,7 +210,11 @@ function focusoutListener(table, viewer, event) { function focusinListener(table, viewer, event) { const meta = table.getMeta(event.target); if (meta) { - const new_state = {x: meta.x, y: meta.y, content: event.target.textContent}; + const new_state = { + x: meta.x, + y: meta.y, + content: event.target.textContent, + }; selected_position_map.set(table, new_state); } } @@ -207,7 +225,16 @@ export async function configureEditable(table, viewer) { this._edit_port = await viewer.getEditPort(); table.addStyleListener(editableStyleListener.bind(this, table, viewer)); table.addStyleListener(focusStyleListener.bind(this, table, viewer)); - table.addEventListener("focusin", focusinListener.bind(this, table, viewer)); - table.addEventListener("focusout", focusoutListener.bind(this, table, viewer)); - table.addEventListener("keydown", keydownListener.bind(this, table, viewer)); + table.addEventListener( + "focusin", + focusinListener.bind(this, table, viewer) + ); + table.addEventListener( + "focusout", + focusoutListener.bind(this, table, viewer) + ); + table.addEventListener( + "keydown", + keydownListener.bind(this, table, viewer) + ); } diff --git a/packages/perspective-viewer-datagrid/src/js/getCellConfig.js b/packages/perspective-viewer-datagrid/src/js/getCellConfig.js index d91dd1a794..8e0130921e 100644 --- a/packages/perspective-viewer-datagrid/src/js/getCellConfig.js +++ b/packages/perspective-viewer-datagrid/src/js/getCellConfig.js @@ -7,20 +7,24 @@ * */ -export default async function getCellConfig({_view, _config}, row_idx, col_idx) { +export default async function getCellConfig( + {_view, _config}, + row_idx, + col_idx +) { const row_pivots = _config.row_pivots; const column_pivots = _config.column_pivots; const start_row = row_idx >= 0 ? row_idx : 0; const end_row = start_row + 1; const r = await _view.to_json({start_row, end_row}); - const row_paths = r.map(x => x.__ROW_PATH__); + const row_paths = r.map((x) => x.__ROW_PATH__); const row_pivots_values = row_paths[0] || []; const row_filters = row_pivots .map((pivot, index) => { const pivot_value = row_pivots_values[index]; return pivot_value ? [pivot, "==", pivot_value] : undefined; }) - .filter(x => x); + .filter((x) => x); const column_index = row_pivots.length > 0 ? col_idx + 1 : col_idx; const column_paths = Object.keys(r[0])[column_index]; @@ -28,13 +32,15 @@ export default async function getCellConfig({_view, _config}, row_idx, col_idx) let column_filters = []; if (column_paths) { const column_pivot_values = column_paths.split("|"); - result.column_names = [column_pivot_values[column_pivot_values.length - 1]]; + result.column_names = [ + column_pivot_values[column_pivot_values.length - 1], + ]; column_filters = column_pivots .map((pivot, index) => { const pivot_value = column_pivot_values[index]; return pivot_value ? [pivot, "==", pivot_value] : undefined; }) - .filter(x => x) + .filter((x) => x) .filter(([, , value]) => value !== "__ROW_PATH__"); } diff --git a/packages/perspective-viewer-datagrid/src/js/plugin.js b/packages/perspective-viewer-datagrid/src/js/plugin.js index 846ca5a2d7..9f5dc66bd3 100644 --- a/packages/perspective-viewer-datagrid/src/js/plugin.js +++ b/packages/perspective-viewer-datagrid/src/js/plugin.js @@ -9,7 +9,12 @@ import "regular-table"; -import {createModel, configureRegularTable, formatters, create_color_record} from "./regular_table_handlers.js"; +import { + createModel, + configureRegularTable, + formatters, + create_color_record, +} from "./regular_table_handlers.js"; import MATERIAL_STYLE from "../less/regular_table.less"; import {configureRowSelectable, deselect} from "./row_selection.js"; import {configureClick} from "./click.js"; @@ -34,7 +39,11 @@ customElements.define( this.appendChild(this.datagrid); this.model = await createModel(this.datagrid, table, view); configureRegularTable(this.datagrid, this.model); - await configureRowSelectable.call(this.model, this.datagrid, viewer); + await configureRowSelectable.call( + this.model, + this.datagrid, + viewer + ); await configureClick.call(this.model, this.datagrid, viewer); await configureEditable.call(this.model, this.datagrid, viewer); await configureSortable.call(this.model, this.datagrid, viewer); @@ -102,7 +111,10 @@ customElements.define( if (datagrid[PLUGIN_SYMBOL]) { const token = {}; for (const col of Object.keys(datagrid[PLUGIN_SYMBOL])) { - const config = Object.assign({}, datagrid[PLUGIN_SYMBOL][col]); + const config = Object.assign( + {}, + datagrid[PLUGIN_SYMBOL][col] + ); if (config?.pos_color) { config.pos_color = config.pos_color[0]; config.neg_color = config.neg_color[0]; @@ -158,5 +170,7 @@ function _register_global_styles() { * */ -customElements.get("perspective-viewer").registerPlugin("perspective-viewer-datagrid"); +customElements + .get("perspective-viewer") + .registerPlugin("perspective-viewer-datagrid"); _register_global_styles(); diff --git a/packages/perspective-viewer-datagrid/src/js/plugin_menu.js b/packages/perspective-viewer-datagrid/src/js/plugin_menu.js index c546c94b53..c9195ffb34 100644 --- a/packages/perspective-viewer-datagrid/src/js/plugin_menu.js +++ b/packages/perspective-viewer-datagrid/src/js/plugin_menu.js @@ -15,21 +15,26 @@ let MENU = undefined; export function make_gradient(chromahex) { const [r, g, b] = chromahex.rgb(); - const [r1, g1, b1] = chromahex.set("hsl.h", (chromahex.get("hsl.h") - 15) % 360).rgb(); - const [r2, g2, b2] = chromahex.set("hsl.h", (chromahex.get("hsl.h") + 15) % 360).rgb(); + const [r1, g1, b1] = chromahex + .set("hsl.h", (chromahex.get("hsl.h") - 15) % 360) + .rgb(); + const [r2, g2, b2] = chromahex + .set("hsl.h", (chromahex.get("hsl.h") + 15) % 360) + .rgb(); return `linear-gradient(to right top,rgb(${r1},${g1},${b1}),rgb(${r},${g},${b}) 50%,rgb(${r2},${g2},${b2}))`; } export function activate_plugin_menu(regularTable, target, column_max) { MENU = MENU || document.createElement("perspective-column-style"); const target_meta = regularTable.getMeta(target); - const column_name = target_meta.column_header[target_meta.column_header.length - 1]; + const column_name = + target_meta.column_header[target_meta.column_header.length - 1]; const column_type = this._schema[column_name]; const default_config = { gradient: column_max, pos_color: this._pos_color[0], neg_color: this._neg_color[0], - color_mode: "foreground" + color_mode: "foreground", }; if (column_type === "float") { @@ -43,26 +48,44 @@ export function activate_plugin_menu(regularTable, target, column_max) { } const scroll_handler = () => MENU.blur(); - const update_handler = event => { + const update_handler = (event) => { const config = event.detail; if (config.pos_color) { - config.pos_color = [config.pos_color, ...chroma(config.pos_color).rgb(), make_gradient(chroma(config.pos_color))]; - config.neg_color = [config.neg_color, ...chroma(config.neg_color).rgb(), make_gradient(chroma(config.neg_color))]; + config.pos_color = [ + config.pos_color, + ...chroma(config.pos_color).rgb(), + make_gradient(chroma(config.pos_color)), + ]; + config.neg_color = [ + config.neg_color, + ...chroma(config.neg_color).rgb(), + make_gradient(chroma(config.neg_color)), + ]; } regularTable[PLUGIN_SYMBOL] = regularTable[PLUGIN_SYMBOL] || {}; regularTable[PLUGIN_SYMBOL][column_name] = config; regularTable.draw({preserve_width: true}); - regularTable.parentElement.dispatchEvent(new Event("perspective-config-update")); + regularTable.parentElement.dispatchEvent( + new Event("perspective-config-update") + ); }; const blur_handler = async () => { - regularTable.removeEventListener("regular-table-scroll", scroll_handler); - MENU.removeEventListener("perspective-column-style-change", update_handler); + regularTable.removeEventListener( + "regular-table-scroll", + scroll_handler + ); + MENU.removeEventListener( + "perspective-column-style-change", + update_handler + ); MENU.removeEventListener("blur", blur_handler); this._open_column_styles_menu.pop(); await regularTable.draw(); - regularTable.parentElement.dispatchEvent(new Event("perspective-config-update")); + regularTable.parentElement.dispatchEvent( + new Event("perspective-config-update") + ); }; MENU.addEventListener("perspective-column-style-change", update_handler); @@ -71,7 +94,10 @@ export function activate_plugin_menu(regularTable, target, column_max) { // Get the current column style config const pset = regularTable[PLUGIN_SYMBOL] || {}; - const config = Object.assign({}, (pset[column_name] = pset[column_name] || {})); + const config = Object.assign( + {}, + (pset[column_name] = pset[column_name] || {}) + ); if (config.pos_color) { config.pos_color = config.pos_color[0]; config.neg_color = config.neg_color[0]; diff --git a/packages/perspective-viewer-datagrid/src/js/regular_table_handlers.js b/packages/perspective-viewer-datagrid/src/js/regular_table_handlers.js index c418f2c0bb..0f0d840690 100644 --- a/packages/perspective-viewer-datagrid/src/js/regular_table_handlers.js +++ b/packages/perspective-viewer-datagrid/src/js/regular_table_handlers.js @@ -8,39 +8,60 @@ */ import {get_type_config} from "@finos/perspective/dist/esm/config/index.js"; -import {activate_plugin_menu, PLUGIN_SYMBOL, make_gradient} from "./plugin_menu.js"; +import { + activate_plugin_menu, + PLUGIN_SYMBOL, + make_gradient, +} from "./plugin_menu.js"; import {rgbaToRgb, infer_foreground_from_background} from "./color_utils.js"; import chroma from "chroma-js"; function styleListener(regularTable) { const header_depth = regularTable._view_cache.config.row_pivots.length - 1; - let group_headers = Array.from(regularTable.children[0].children[0].children); + let group_headers = Array.from( + regularTable.children[0].children[0].children + ); let [col_headers] = group_headers.splice(group_headers.length - 1, 1); const plugins = regularTable[PLUGIN_SYMBOL] || {}; for (const td of col_headers?.children) { const metadata = regularTable.getMeta(td); - const column_name = metadata.column_header?.[metadata.column_header?.length - 1]; - const sort = this._config.sort.find(x => x[0] === column_name); + const column_name = + metadata.column_header?.[metadata.column_header?.length - 1]; + const sort = this._config.sort.find((x) => x[0] === column_name); let needs_border = metadata.row_header_x === header_depth; const is_corner = typeof metadata.x === "undefined"; - needs_border = needs_border || (metadata.x + 1) % this._config.columns.length === 0; + needs_border = + needs_border || + (metadata.x + 1) % this._config.columns.length === 0; td.classList.toggle("psp-header-border", needs_border); td.classList.toggle("psp-header-group", false); td.classList.toggle("psp-header-leaf", true); td.classList.toggle("psp-is-top", false); td.classList.toggle("psp-header-corner", is_corner); td.classList.toggle("psp-header-sort-asc", !!sort && sort[1] === "asc"); - td.classList.toggle("psp-header-sort-desc", !!sort && sort[1] === "desc"); - td.classList.toggle("psp-header-sort-col-asc", !!sort && sort[1] === "col asc"); - td.classList.toggle("psp-header-sort-col-desc", !!sort && sort[1] === "col desc"); + td.classList.toggle( + "psp-header-sort-desc", + !!sort && sort[1] === "desc" + ); + td.classList.toggle( + "psp-header-sort-col-asc", + !!sort && sort[1] === "col asc" + ); + td.classList.toggle( + "psp-header-sort-col-desc", + !!sort && sort[1] === "col desc" + ); let type = get_psp_type.call(this, metadata); const is_numeric = type === "integer" || type === "float"; td.classList.toggle("psp-align-right", is_numeric); td.classList.toggle("psp-align-left", !is_numeric); - td.classList.toggle("psp-menu-open", this._open_column_styles_menu[0] === metadata._virtual_x); + td.classList.toggle( + "psp-menu-open", + this._open_column_styles_menu[0] === metadata._virtual_x + ); td.classList.toggle("psp-menu-enabled", is_numeric && !is_corner); } @@ -54,13 +75,17 @@ function styleListener(regularTable) { const td = row.cells[x]; td.style.backgroundColor = ""; const metadata = regularTable.getMeta(td); - let needs_border = metadata.row_header_x === header_depth || metadata.x >= 0; + let needs_border = + metadata.row_header_x === header_depth || metadata.x >= 0; td.classList.toggle("psp-align-right", false); td.classList.toggle("psp-align-left", false); td.classList.toggle("psp-header-group", true); td.classList.toggle("psp-header-leaf", false); td.classList.toggle("psp-header-border", needs_border); - td.classList.toggle("psp-header-group-corner", typeof metadata.x === "undefined"); + td.classList.toggle( + "psp-header-group-corner", + typeof metadata.x === "undefined" + ); td.classList.toggle("psp-color-mode-bar", false); td.classList.toggle("psp-header-sort-asc", false); @@ -90,7 +115,8 @@ function styleListener(regularTable) { for (const tr of regularTable.children[0].children[1].children) { for (const td of tr.children) { const metadata = regularTable.getMeta(td); - const column_name = metadata.column_header?.[metadata.column_header?.length - 1]; + const column_name = + metadata.column_header?.[metadata.column_header?.length - 1]; const plugin = plugins[column_name]; let type = get_psp_type.call(this, metadata); @@ -101,22 +127,37 @@ function styleListener(regularTable) { const is_negative = metadata.user < 0; const [hex, r, g, b, gradhex] = (() => { if (plugin?.pos_color !== undefined) { - return is_positive ? plugin.pos_color : is_negative ? plugin.neg_color : ["", 0, 0, 0, ""]; + return is_positive + ? plugin.pos_color + : is_negative + ? plugin.neg_color + : ["", 0, 0, 0, ""]; } else { - return is_positive ? this._pos_color : is_negative ? this._neg_color : ["", 0, 0, 0, ""]; + return is_positive + ? this._pos_color + : is_negative + ? this._neg_color + : ["", 0, 0, 0, ""]; } })(); td.style.position = ""; if (plugin?.color_mode === "background") { const source = this._plugin_background; - const foreground = infer_foreground_from_background(rgbaToRgb([r, g, b, 1], source)); + const foreground = infer_foreground_from_background( + rgbaToRgb([r, g, b, 1], source) + ); td.style.color = foreground; td.style.backgroundColor = hex; } else if (plugin?.color_mode === "gradient") { - const a = Math.max(0, Math.min(1, Math.abs(metadata.user / plugin.gradient))); + const a = Math.max( + 0, + Math.min(1, Math.abs(metadata.user / plugin.gradient)) + ); const source = this._plugin_background; - const foreground = infer_foreground_from_background(rgbaToRgb([r, g, b, a], source)); + const foreground = infer_foreground_from_background( + rgbaToRgb([r, g, b, a], source) + ); td.style.color = foreground; td.style.backgroundColor = `rgba(${r},${g},${b},${a})`; } else if (plugin?.color_mode === "disabled") { @@ -126,7 +167,11 @@ function styleListener(regularTable) { td.style.backgroundColor = ""; td.style.color = ""; td.style.position = "relative"; - if (gradhex !== "" && td.children.length > 0 && td.children[0].nodeType === Node.ELEMENT_NODE) { + if ( + gradhex !== "" && + td.children.length > 0 && + td.children[0].nodeType === Node.ELEMENT_NODE + ) { td.children[0].style.background = gradhex; } } else { @@ -140,19 +185,38 @@ function styleListener(regularTable) { const is_th = td.tagName === "TH"; if (is_th) { - const is_not_empty = !!metadata.value && metadata.value.toString().trim().length > 0; - const is_leaf = metadata.row_header_x >= this._config.row_pivots.length; - const next = regularTable.getMeta({dx: 0, dy: metadata.y - metadata.y0 + 1}); - const is_collapse = next && next.row_header && typeof next.row_header[metadata.row_header_x + 1] !== "undefined"; + const is_not_empty = + !!metadata.value && + metadata.value.toString().trim().length > 0; + const is_leaf = + metadata.row_header_x >= this._config.row_pivots.length; + const next = regularTable.getMeta({ + dx: 0, + dy: metadata.y - metadata.y0 + 1, + }); + const is_collapse = + next && + next.row_header && + typeof next.row_header[metadata.row_header_x + 1] !== + "undefined"; td.classList.toggle("psp-tree-label", is_not_empty && !is_leaf); - td.classList.toggle("psp-tree-label-expand", is_not_empty && !is_leaf && !is_collapse); - td.classList.toggle("psp-tree-label-collapse", is_not_empty && !is_leaf && is_collapse); + td.classList.toggle( + "psp-tree-label-expand", + is_not_empty && !is_leaf && !is_collapse + ); + td.classList.toggle( + "psp-tree-label-collapse", + is_not_empty && !is_leaf && is_collapse + ); td.classList.toggle("psp-tree-leaf", is_not_empty && is_leaf); } td.classList.toggle("psp-align-right", !is_th && is_numeric); td.classList.toggle("psp-align-left", is_th || !is_numeric); - td.classList.toggle("psp-color-mode-bar", plugin?.color_mode === "bar"); + td.classList.toggle( + "psp-color-mode-bar", + plugin?.color_mode === "bar" + ); } } } @@ -173,7 +237,9 @@ async function sortHandler(regularTable, event, target) { const column_name = meta.column_header[meta.column_header.length - 1]; const sort_method = event.shiftKey ? append_sort : override_sort; const sort = sort_method.call(this, column_name); - regularTable.dispatchEvent(new CustomEvent("regular-table-psp-sort", {detail: {sort}})); + regularTable.dispatchEvent( + new CustomEvent("regular-table-psp-sort", {detail: {sort}}) + ); } function append_sort(column_name) { @@ -215,15 +281,26 @@ function create_sort(column_name, sort_dir) { } const ROW_SORT_ORDER = {desc: "asc", asc: undefined}; -const ROW_COL_SORT_ORDER = {desc: "asc", asc: "col desc", "col desc": "col asc", "col asc": undefined}; +const ROW_COL_SORT_ORDER = { + desc: "asc", + asc: "col desc", + "col desc": "col asc", + "col asc": undefined, +}; async function expandCollapseHandler(regularTable, event) { const meta = regularTable.getMeta(event.target); - const is_collapse = event.target.classList.contains("psp-tree-label-collapse"); + const is_collapse = event.target.classList.contains( + "psp-tree-label-collapse" + ); if (event.shiftKey && is_collapse) { - this._view.set_depth(meta.row_header.filter(x => x !== undefined).length - 2); + this._view.set_depth( + meta.row_header.filter((x) => x !== undefined).length - 2 + ); } else if (event.shiftKey) { - this._view.set_depth(meta.row_header.filter(x => x !== undefined).length - 1); + this._view.set_depth( + meta.row_header.filter((x) => x !== undefined).length - 1 + ); } else if (is_collapse) { this._view.collapse(meta.y); } else { @@ -254,16 +331,23 @@ async function mousedownListener(regularTable, event) { } const rect = target.getBoundingClientRect(); - if (target.classList.contains("psp-menu-enabled") && event.clientY - rect.top > 16) { + if ( + target.classList.contains("psp-menu-enabled") && + event.clientY - rect.top > 16 + ) { const meta = regularTable.getMeta(target); - const column_name = meta.column_header?.[meta.column_header?.length - 1]; + const column_name = + meta.column_header?.[meta.column_header?.length - 1]; const [, max] = await this._view.get_min_max(column_name); this._open_column_styles_menu.unshift(meta._virtual_x); regularTable.draw(); activate_plugin_menu.call(this, regularTable, target, max); event.preventDefault(); event.stopImmediatePropagation(); - } else if (target.classList.contains("psp-header-leaf") && !target.classList.contains("psp-header-corner")) { + } else if ( + target.classList.contains("psp-header-leaf") && + !target.classList.contains("psp-header-corner") + ) { sortHandler.call(this, regularTable, event, target); event.stopImmediatePropagation(); } @@ -284,7 +368,10 @@ function clickListener(regularTable, event) { if (target.classList.contains("psp-tree-label") && event.offsetX < 26) { event.stopImmediatePropagation(); - } else if (target.classList.contains("psp-header-leaf") && !target.classList.contains("psp-header-corner")) { + } else if ( + target.classList.contains("psp-header-leaf") && + !target.classList.contains("psp-header-corner") + ) { event.stopImmediatePropagation(); } } @@ -295,7 +382,7 @@ const FORMATTER_CONS = { datetime: Intl.DateTimeFormat, date: Intl.DateTimeFormat, integer: Intl.NumberFormat, - float: Intl.NumberFormat + float: Intl.NumberFormat, }; export const formatters = FORMATTERS; @@ -307,30 +394,55 @@ function _format(parts, val, plugins = {}, use_table_schema = false) { const title = parts[parts.length - 1]; const plugin = plugins[title]; - const type = (use_table_schema && this._table_schema[title]) || this._schema[title] || "string"; + const type = + (use_table_schema && this._table_schema[title]) || + this._schema[title] || + "string"; const is_numeric = type === "integer" || type === "float"; if (is_numeric && plugin?.color_mode === "bar") { - const a = Math.max(0, Math.min(0.95, Math.abs(val / plugin.gradient) * 0.95)); + const a = Math.max( + 0, + Math.min(0.95, Math.abs(val / plugin.gradient) * 0.95) + ); const div = this._div_factory.get(); const anchor = val >= 0 ? "left" : "right"; - div.setAttribute("style", `width:${(a * 100).toFixed(2)}%;position:absolute;${anchor}:0;height:80%;top:10%;`); + div.setAttribute( + "style", + `width:${(a * 100).toFixed( + 2 + )}%;position:absolute;${anchor}:0;height:80%;top:10%;` + ); return div; } else { - const is_plugin_override = is_numeric && plugin && plugin.fixed !== undefined; - let formatter_key = is_plugin_override ? `${type}${plugin.fixed}` : type; + const is_plugin_override = + is_numeric && plugin && plugin.fixed !== undefined; + let formatter_key = is_plugin_override + ? `${type}${plugin.fixed}` + : type; if (FORMATTERS[formatter_key] === undefined) { const type_config = get_type_config(type); if (is_plugin_override) { - const opts = {minimumFractionDigits: plugin.fixed, maximumFractionDigits: plugin.fixed}; - FORMATTERS[formatter_key] = new FORMATTER_CONS[type]("en-us", opts); + const opts = { + minimumFractionDigits: plugin.fixed, + maximumFractionDigits: plugin.fixed, + }; + FORMATTERS[formatter_key] = new FORMATTER_CONS[type]( + "en-us", + opts + ); } else if (FORMATTER_CONS[type] && type_config.format) { - FORMATTERS[formatter_key] = new FORMATTER_CONS[type]("en-us", type_config.format); + FORMATTERS[formatter_key] = new FORMATTER_CONS[type]( + "en-us", + type_config.format + ); } else { FORMATTERS[formatter_key] = false; } } - return FORMATTERS[formatter_key] ? FORMATTERS[formatter_key].format(val) : val; + return FORMATTERS[formatter_key] + ? FORMATTERS[formatter_key].format(val) + : val; } } @@ -340,7 +452,13 @@ function* _tree_header(paths = [], row_headers, regularTable) { path = ["TOTAL", ...path]; const last = path[path.length - 1]; path = path.slice(0, path.length - 1).fill(""); - const formatted = _format.call(this, [row_headers[path.length - 1]], last, plugins, true); + const formatted = _format.call( + this, + [row_headers[path.length - 1]], + last, + plugins, + true + ); path = path.concat({toString: () => formatted}); path.length = row_headers.length + 1; yield path; @@ -355,7 +473,7 @@ async function dataListener(regularTable, x0, y0, x1, y1) { start_col: x0, end_row: y1, end_col: x1, - id: true + id: true, }); this._ids = columns.__ID__; } else { @@ -368,7 +486,11 @@ async function dataListener(regularTable, x0, y0, x1, y1) { for (const path of this._column_paths.slice(x0, x1)) { const path_parts = path.split("|"); const column = columns[path] || new Array(y1 - y0).fill(null); - data.push(column.map(x => _format.call(this, path_parts, x, regularTable[PLUGIN_SYMBOL]))); + data.push( + column.map((x) => + _format.call(this, path_parts, x, regularTable[PLUGIN_SYMBOL]) + ) + ); metadata.push(column); column_headers.push(path_parts); } @@ -376,18 +498,22 @@ async function dataListener(regularTable, x0, y0, x1, y1) { return { num_rows: this._num_rows, num_columns: this._column_paths.length, - row_headers: Array.from(_tree_header.call(this, columns.__ROW_PATH__, this._config.row_pivots, regularTable)), + row_headers: Array.from( + _tree_header.call( + this, + columns.__ROW_PATH__, + this._config.row_pivots, + regularTable + ) + ), column_headers, data, - metadata + metadata, }; } function get_rule(regular, tag, def) { - let color = window - .getComputedStyle(regular) - .getPropertyValue(tag) - .trim(); + let color = window.getComputedStyle(regular).getPropertyValue(tag).trim(); if (color.length > 0) { return color; } else { @@ -428,24 +554,40 @@ export async function createModel(regular, table, view, extend = {}) { // Extract the entire expression string as typed by the user, so we can // feed it into `validate_expressions` and get back the data types for // each column without it being affected by a pivot. - const expressions = config.expressions.map(expr => expr[1]); - - const [table_schema, validated_expressions, num_rows, schema, expression_schema, column_paths] = await Promise.all([ + const expressions = config.expressions.map((expr) => expr[1]); + + const [ + table_schema, + validated_expressions, + num_rows, + schema, + expression_schema, + column_paths, + ] = await Promise.all([ table.schema(), table.validate_expressions(expressions), view.num_rows(), view.schema(), view.expression_schema(), - view.column_paths() + view.column_paths(), ]); - const _plugin_background = chroma(get_rule(regular, "--plugin--background", "#FFFFFF")).rgb(); - const _pos_color = create_color_record(get_rule(regular, "--rt-pos-cell--color", "#338DCD")); - const _neg_color = create_color_record(get_rule(regular, "--rt-neg-cell--color", "#FF5942")); + const _plugin_background = chroma( + get_rule(regular, "--plugin--background", "#FFFFFF") + ).rgb(); + const _pos_color = create_color_record( + get_rule(regular, "--rt-pos-cell--color", "#338DCD") + ); + const _neg_color = create_color_record( + get_rule(regular, "--rt-neg-cell--color", "#FF5942") + ); const model = Object.assign(extend, { _view: view, _table: table, - _table_schema: {...table_schema, ...validated_expressions.expression_schema}, + _table_schema: { + ...table_schema, + ...validated_expressions.expression_schema, + }, _config: config, _num_rows: num_rows, _schema: {...schema, ...expression_schema}, @@ -454,9 +596,9 @@ export async function createModel(regular, table, view, extend = {}) { _plugin_background, _pos_color, _neg_color, - _column_paths: column_paths.filter(path => { + _column_paths: column_paths.filter((path) => { return path !== "__ROW_PATH__" && path !== "__ID__"; - }) + }), }); // Re-use div factory @@ -468,7 +610,10 @@ export async function createModel(regular, table, view, extend = {}) { export async function configureRegularTable(regular, model) { regular.addStyleListener(styleListener.bind(model, regular)); - regular.addEventListener("mousedown", mousedownListener.bind(model, regular)); + regular.addEventListener( + "mousedown", + mousedownListener.bind(model, regular) + ); regular.addEventListener("click", clickListener.bind(model, regular)); await regular.draw(); } diff --git a/packages/perspective-viewer-datagrid/src/js/row_selection.js b/packages/perspective-viewer-datagrid/src/js/row_selection.js index 3b30ee8937..c8a9d3e6e1 100644 --- a/packages/perspective-viewer-datagrid/src/js/row_selection.js +++ b/packages/perspective-viewer-datagrid/src/js/row_selection.js @@ -25,8 +25,11 @@ async function selectionListener(regularTable, viewer, event) { const id = this._ids[meta.y - meta.y0]; if (meta && meta.y >= 0) { const selected = selected_rows_map.get(regularTable); - const key_match = !!selected && selected.reduce((agg, x, i) => agg && x === id[i], true); - const is_deselect = !!selected && id.length === selected.length && key_match; + const key_match = + !!selected && + selected.reduce((agg, x, i) => agg && x === id[i], true); + const is_deselect = + !!selected && id.length === selected.length && key_match; let filters = []; if (is_deselect) { selected_rows_map.delete(regularTable); @@ -44,8 +47,8 @@ async function selectionListener(regularTable, viewer, event) { composed: true, detail: { selected: !is_deselect, - config: {filters} - } + config: {filters}, + }, }) ); } @@ -62,9 +65,18 @@ function selectionStyleListener(regularTable, viewer) { } else { const meta = regularTable.getMeta(td); const id = this._ids[meta.y - meta.y0]; - const key_match = selected.reduce((agg, x, i) => agg && x === id[i], true); - td.classList.toggle("psp-row-selected", id.length === selected.length && key_match); - td.classList.toggle("psp-row-subselected", id.length !== selected.length && key_match); + const key_match = selected.reduce( + (agg, x, i) => agg && x === id[i], + true + ); + td.classList.toggle( + "psp-row-selected", + id.length === selected.length && key_match + ); + td.classList.toggle( + "psp-row-subselected", + id.length !== selected.length && key_match + ); } } @@ -75,16 +87,28 @@ function selectionStyleListener(regularTable, viewer) { th.classList.toggle("psp-row-selected", false); th.classList.toggle("psp-row-subselected", false); } else { - const key_match = selected.reduce((agg, x, i) => agg && x === id[i], true); - th.classList.toggle("psp-row-selected", id.length === selected.length && key_match); - th.classList.toggle("psp-row-subselected", id.length !== selected.length && key_match); + const key_match = selected.reduce( + (agg, x, i) => agg && x === id[i], + true + ); + th.classList.toggle( + "psp-row-selected", + id.length === selected.length && key_match + ); + th.classList.toggle( + "psp-row-subselected", + id.length !== selected.length && key_match + ); } } } export function configureRowSelectable(table, viewer) { table.addStyleListener(selectionStyleListener.bind(this, table, viewer)); - table.addEventListener("mousedown", selectionListener.bind(this, table, viewer)); + table.addEventListener( + "mousedown", + selectionListener.bind(this, table, viewer) + ); } export async function deselect(regularTable) { diff --git a/packages/perspective-viewer-datagrid/src/js/sorting.js b/packages/perspective-viewer-datagrid/src/js/sorting.js index 5713579d28..fb4992e95c 100644 --- a/packages/perspective-viewer-datagrid/src/js/sorting.js +++ b/packages/perspective-viewer-datagrid/src/js/sorting.js @@ -8,7 +8,7 @@ */ export async function configureSortable(table, viewer) { - table.addEventListener("regular-table-psp-sort", event => { + table.addEventListener("regular-table-psp-sort", (event) => { this._preserve_focus_state = true; viewer.restore({sort: event.detail.sort}); }); diff --git a/packages/perspective-viewer-datagrid/src/less/column-plugin.less b/packages/perspective-viewer-datagrid/src/less/column-plugin.less index fcb74c9033..9efdc05053 100644 --- a/packages/perspective-viewer-datagrid/src/less/column-plugin.less +++ b/packages/perspective-viewer-datagrid/src/less/column-plugin.less @@ -21,7 +21,7 @@ perspective-viewer[settings] regular-table { width: calc(100% - 8px); left: 5px; bottom: 0px; - color: var(--inactive--color, #B4B7BE); + color: var(--inactive--color, #b4b7be); } .psp-header-leaf.psp-menu-enabled:not(.psp-header-corner):before { @@ -30,14 +30,14 @@ perspective-viewer[settings] regular-table { } .psp-header-leaf.psp-menu-open:not(.psp-header-corner) { - pointer-events: none; + pointer-events: none; &:before { content: "expand_less"; } } .psp-header-leaf.psp-menu-enabled:hover:before { - color: #338DCD; + color: #338dcd; } // Pin the column resize handle to the top so it does not overlap the @@ -45,4 +45,4 @@ perspective-viewer[settings] regular-table { .psp-header-leaf .rt-column-resize { height: 18px; } -} \ No newline at end of file +} diff --git a/packages/perspective-viewer-datagrid/src/less/column-style.less b/packages/perspective-viewer-datagrid/src/less/column-style.less index c6aed7c887..3d248c5d59 100644 --- a/packages/perspective-viewer-datagrid/src/less/column-style.less +++ b/packages/perspective-viewer-datagrid/src/less/column-style.less @@ -7,7 +7,7 @@ * */ - :host { +:host { position: absolute; padding: 6px; outline: none; @@ -28,7 +28,9 @@ outline: none; } -:host input[type=radio], :host input[type=checkbox], :host > div > div > span:first-child { +:host input[type="radio"], +:host input[type="checkbox"], +:host > div > div > span:first-child { width: 24px; margin: 0; } @@ -43,13 +45,13 @@ margin-bottom: 8px; } -:host input[type=color] { +:host input[type="color"] { width: 24px; } :host .operator { font-family: "Roboto Mono", monospace; - white-space:pre; + white-space: pre; } :host input.parameter[disabled] { @@ -58,4 +60,4 @@ :host .indent1 { margin-left: 24px; -} \ No newline at end of file +} diff --git a/packages/perspective-viewer-datagrid/src/less/material.less b/packages/perspective-viewer-datagrid/src/less/material.less index 5d7e3f92b8..d5324ce63e 100644 --- a/packages/perspective-viewer-datagrid/src/less/material.less +++ b/packages/perspective-viewer-datagrid/src/less/material.less @@ -7,229 +7,224 @@ * */ - @row-height: 19px; - - regular-table { - padding-top: 12px; - padding-left: 12px; - padding-bottom: 0; - padding-right: 0; - - // firefox scrollbar styling - scrollbar-color: transparent transparent; - scrollbar-width: thin; - outline: none; - } - - regular-table:hover { - scrollbar-color: rgba(0,0,0,0.3) transparent; - } - - regular-table { - font-family: "Open Sans"; - - div[tabindex] { - outline: none; - } - - & > div { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; - } - - th { - text-align: center; - } - - // Header groups should overflow and not contribute to auto-sizing. - thead tr:not(:last-child) th { - overflow: hidden; - max-width: 0px; - } - - thead tr:last-child .rt-float, - tbody .rt-float { - text-align: right; - } - - thead .rt-integer, - tbody .rt-integer { - text-align: right; - } - - tbody th { - text-align: left; - } - - - - - span.rt-tree-container { - display:flex; - align-items:center; - height:100%; - } - - thead .rt-string, - tbody .rt-string, - thead .rt-date, - tbody .rt-date, - thead .rt-datetime, - tbody .rt-datetime { - text-align: left; - } - - // frozen rows - - - - thead tr:last-child th { - border-bottom: 1px solid #ddd; - } - - th { - position: relative; - } - - tr th span.rt-tree-group { - margin-left: 5px; - margin-right: 15px; - border-left: 1px solid #eee; - height: 100%; - } - - td, th { - white-space: nowrap; - font-size: 12px; - padding-right: 5px; - padding-left: 5px; - padding-top: 0px; - padding-bottom: 0px; - height: @row-height; - } - - - - tr:hover td { - background: #eee; - opacity: 1; - } - - tr:hover { - color: #333; - } - - table * { - box-sizing: border-box; - } - - table { - position: absolute; - overflow: hidden; - color: #666; - outline: none; - } - - span.rt-row-header-icon { - color: #AAA; - padding-right: 4px; - font-family: "Material Icons"; - } - - span.rt-column-header-icon { - font-size: 10px; - padding-left: 3px; - display: inline-block; - width: 10px; - font-family: "Material Icons"; - } - - span.rt-row-header-icon:hover { - color: #1a7da1; - text-shadow: 0px 0px 3px #1a7da1; - } - - .rt-selected td { - background-color: #eee; - } - - .rt-cell-clip { - overflow: hidden; - text-overflow: ellipsis; - } - - // OPTIONAL zebra striping - - // @zebra-stripe-color: rgb(238,238,238); - - // tbody tr:nth-child(even) td:not(.rt-group-header) { - // background: @zebra-stripe-color; - // } - - // tbody tr:nth-child(even) span.rt-group-name { - // background: @zebra-stripe-color; - // } - - td span.rt-group-name, th span.rt-group-name { - margin-right: -5px; - padding-right: 5px; - padding-left: 8px; - flex: 1; - height: 100%; - } - - th span.rt-group-name { - text-align: left; - } - - td th span.rt-group-leaf, th span.rt-group-leaf { - margin-left: 16px; - height: 100%; - } - - .rt-column-resize { - height: 100%; - width: 10px; - position: absolute; - top: 0; - right: 0; - cursor: col-resize; - } - - // webkit (chrome, safari, etc) scrollbar styling - - &::-webkit-scrollbar, - &::-webkit-scrollbar-corner { - background-color: transparent; - height: 12px; - width: 12px; - } - - &::-webkit-scrollbar-thumb { - background-clip: content-box; - background-color: rgba(0,0,0,0); - border-radius: 5px; - } - - &::-webkit-scrollbar-thumb:horizontal { - border-bottom: 2px solid transparent; - border-top: 2px solid transparent; - } - - &::-webkit-scrollbar-thumb:vertical { - border-left: 2px solid transparent; - border-right: 2px solid transparent; - } - - &:hover::-webkit-scrollbar-thumb { - background-color: rgba(0,0,0,0.15); - } - &::-webkit-scrollbar-thumb:hover { - background-color: rgba(0,0,0,0.3); - } - } - \ No newline at end of file +@row-height: 19px; + +regular-table { + padding-top: 12px; + padding-left: 12px; + padding-bottom: 0; + padding-right: 0; + + // firefox scrollbar styling + scrollbar-color: transparent transparent; + scrollbar-width: thin; + outline: none; +} + +regular-table:hover { + scrollbar-color: rgba(0, 0, 0, 0.3) transparent; +} + +regular-table { + font-family: "Open Sans"; + + div[tabindex] { + outline: none; + } + + & > div { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + } + + th { + text-align: center; + } + + // Header groups should overflow and not contribute to auto-sizing. + thead tr:not(:last-child) th { + overflow: hidden; + max-width: 0px; + } + + thead tr:last-child .rt-float, + tbody .rt-float { + text-align: right; + } + + thead .rt-integer, + tbody .rt-integer { + text-align: right; + } + + tbody th { + text-align: left; + } + + span.rt-tree-container { + display: flex; + align-items: center; + height: 100%; + } + + thead .rt-string, + tbody .rt-string, + thead .rt-date, + tbody .rt-date, + thead .rt-datetime, + tbody .rt-datetime { + text-align: left; + } + + // frozen rows + + thead tr:last-child th { + border-bottom: 1px solid #ddd; + } + + th { + position: relative; + } + + tr th span.rt-tree-group { + margin-left: 5px; + margin-right: 15px; + border-left: 1px solid #eee; + height: 100%; + } + + td, + th { + white-space: nowrap; + font-size: 12px; + padding-right: 5px; + padding-left: 5px; + padding-top: 0px; + padding-bottom: 0px; + height: @row-height; + } + + tr:hover td { + background: #eee; + opacity: 1; + } + + tr:hover { + color: #333; + } + + table * { + box-sizing: border-box; + } + + table { + position: absolute; + overflow: hidden; + color: #666; + outline: none; + } + + span.rt-row-header-icon { + color: #aaa; + padding-right: 4px; + font-family: "Material Icons"; + } + + span.rt-column-header-icon { + font-size: 10px; + padding-left: 3px; + display: inline-block; + width: 10px; + font-family: "Material Icons"; + } + + span.rt-row-header-icon:hover { + color: #1a7da1; + text-shadow: 0px 0px 3px #1a7da1; + } + + .rt-selected td { + background-color: #eee; + } + + .rt-cell-clip { + overflow: hidden; + text-overflow: ellipsis; + } + + // OPTIONAL zebra striping + + // @zebra-stripe-color: rgb(238,238,238); + + // tbody tr:nth-child(even) td:not(.rt-group-header) { + // background: @zebra-stripe-color; + // } + + // tbody tr:nth-child(even) span.rt-group-name { + // background: @zebra-stripe-color; + // } + + td span.rt-group-name, + th span.rt-group-name { + margin-right: -5px; + padding-right: 5px; + padding-left: 8px; + flex: 1; + height: 100%; + } + + th span.rt-group-name { + text-align: left; + } + + td th span.rt-group-leaf, + th span.rt-group-leaf { + margin-left: 16px; + height: 100%; + } + + .rt-column-resize { + height: 100%; + width: 10px; + position: absolute; + top: 0; + right: 0; + cursor: col-resize; + } + + // webkit (chrome, safari, etc) scrollbar styling + + &::-webkit-scrollbar, + &::-webkit-scrollbar-corner { + background-color: transparent; + height: 12px; + width: 12px; + } + + &::-webkit-scrollbar-thumb { + background-clip: content-box; + background-color: rgba(0, 0, 0, 0); + border-radius: 5px; + } + + &::-webkit-scrollbar-thumb:horizontal { + border-bottom: 2px solid transparent; + border-top: 2px solid transparent; + } + + &::-webkit-scrollbar-thumb:vertical { + border-left: 2px solid transparent; + border-right: 2px solid transparent; + } + + &:hover::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.15); + } + &::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.3); + } +} diff --git a/packages/perspective-viewer-datagrid/src/less/mitered-headers.less b/packages/perspective-viewer-datagrid/src/less/mitered-headers.less index fd51803547..19a55b24c9 100644 --- a/packages/perspective-viewer-datagrid/src/less/mitered-headers.less +++ b/packages/perspective-viewer-datagrid/src/less/mitered-headers.less @@ -9,38 +9,36 @@ .psp-header-border:not(.psp-is-top):not(.psp-header-leaf) { // right - box-shadow: 1px 0px var(--pv-divider--color, #EAEDEF); + box-shadow: 1px 0px var(--pv-divider--color, #eaedef); } .psp-header-group { // bottom - box-shadow: 0px 10px 0 -9px var(--pv-divider--color, #EAEDEF); + box-shadow: 0px 10px 0 -9px var(--pv-divider--color, #eaedef); } .psp-is-top { // top-miter-right - box-shadow: 5px 4px 0px -4px var(--pv-divider--color, #EAEDEF); + box-shadow: 5px 4px 0px -4px var(--pv-divider--color, #eaedef); } .psp-is-top.psp-header-group:not(.psp-header-group-corner) { // top-miter-right and bottom - box-shadow: - 5px 4px 0px -4px var(--pv-divider--color, #EAEDEF), - 0px 10px 0 -9px var(--pv-divider--color, #EAEDEF); + box-shadow: 5px 4px 0px -4px var(--pv-divider--color, #eaedef), + 0px 10px 0 -9px var(--pv-divider--color, #eaedef); } .psp-header-border.psp-header-group:not(.psp-is-top):not(.psp-header-group-corner) { // right and bottom - box-shadow: - 1px 0px var(--pv-divider--color, #EAEDEF), - 0px 10px 0 -9px var(--pv-divider--color, #EAEDEF); + box-shadow: 1px 0px var(--pv-divider--color, #eaedef), + 0px 10px 0 -9px var(--pv-divider--color, #eaedef); } .psp-header-leaf.psp-header-border { // bottom-miter-right - box-shadow: 5px -4px 0px -4px var(--pv-divider--color, #EAEDEF); + box-shadow: 5px -4px 0px -4px var(--pv-divider--color, #eaedef); } tr:only-child th { box-shadow: none !important; -} \ No newline at end of file +} diff --git a/packages/perspective-viewer-datagrid/src/less/regular_table.less b/packages/perspective-viewer-datagrid/src/less/regular_table.less index 5db3e5704b..87e4bf4641 100644 --- a/packages/perspective-viewer-datagrid/src/less/regular_table.less +++ b/packages/perspective-viewer-datagrid/src/less/regular_table.less @@ -12,22 +12,28 @@ @import (less) "./row-hover.less"; @import (less) "./column-plugin.less"; -@positive: #338DCD; // @blue300 -@negative: #FF5942; // @red300 +@positive: #338dcd; // @blue300 +@negative: #ff5942; // @red300 // Row Selection -.psp-row-selected, :hover .psp-row-selected, :hover th.psp-tree-leaf.psp-row-selected, :hover th.psp-tree-label.psp-row-selected { +.psp-row-selected, +:hover .psp-row-selected, +:hover th.psp-tree-leaf.psp-row-selected, +:hover th.psp-tree-label.psp-row-selected { color: white !important; - background-color: #EA7319 !important; - border-color: #EA7319 !important; + background-color: #ea7319 !important; + border-color: #ea7319 !important; } .psp-row-selected.psp-tree-label:not(:hover):before { color: white; } -.psp-row-subselected, :hover .psp-row-subselected, :hover th.psp-tree-leaf.psp-row-subselected, :hover th.psp-tree-label.psp-row-subselected { +.psp-row-subselected, +:hover .psp-row-subselected, +:hover th.psp-tree-leaf.psp-row-subselected, +:hover th.psp-tree-label.psp-row-subselected { background: rgba(234, 115, 25, 0.2) !important; } @@ -52,22 +58,22 @@ perspective-viewer.dragging regular-table { .psp-header-sort-desc:after { font-family: "Material Icons"; font-size: 10px; - content: "arrow_downward" + content: "arrow_downward"; } .psp-header-sort-asc:after { font-family: "Material Icons"; font-size: 10px; - content: "arrow_upward" + content: "arrow_upward"; } .psp-header-sort-col-desc:after { font-family: "Material Icons"; font-size: 10px; - content: "arrow_back" + content: "arrow_back"; } .psp-header-sort-col-asc:after { font-family: "Material Icons"; font-size: 10px; - content: "arrow_forward" + content: "arrow_forward"; } tbody th:last-of-type { @@ -76,7 +82,12 @@ tbody th:last-of-type { text-overflow: ellipsis; } tbody th:empty { - background-image: linear-gradient(to right, transparent 9px, #eee 10px, transparent 11px); + background-image: linear-gradient( + to right, + transparent 9px, + #eee 10px, + transparent 11px + ); background-repeat: no-repeat; min-width: 20px; max-width: 20px; @@ -93,10 +104,10 @@ tbody th:empty { vertical-align: -1px; } .psp-tree-label-expand:before { - content: "add" + content: "add"; } .psp-tree-label-collapse:before { - content: "remove" + content: "remove"; } .psp-tree-label:hover:before { @@ -138,7 +149,8 @@ regular-table table { font-weight: 400; } - td, th { + td, + th { border-color: #eaedef; height: 23px; } @@ -147,7 +159,11 @@ regular-table table { border-bottom-width: 0px; } - td, th.psp-tree-label, th.psp-tree-label, th.psp-tree-leaf, tbody tr:first-child th { + td, + th.psp-tree-label, + th.psp-tree-label, + th.psp-tree-leaf, + tbody tr:first-child th { border-style: solid; border-width: 0px; border-top-width: 1px; diff --git a/packages/perspective-viewer-datagrid/src/less/row-hover.less b/packages/perspective-viewer-datagrid/src/less/row-hover.less index f3879d9f9c..0e02c1a3bb 100644 --- a/packages/perspective-viewer-datagrid/src/less/row-hover.less +++ b/packages/perspective-viewer-datagrid/src/less/row-hover.less @@ -9,22 +9,26 @@ regular-table { tbody { - tr:hover th.psp-tree-leaf:not(.psp-row-selected):not(.psp-row-subselected), - tr:hover th.psp-tree-label:not(.psp-row-selected):not(.psp-row-subselected), - tr:hover td:not(.psp-row-selected):not(.psp-row-subselected) { - border-color: var(--rt-hover--border-color, #C5C9D080) !important; + tr:hover + th.psp-tree-leaf:not(.psp-row-selected):not(.psp-row-subselected), + tr:hover + th.psp-tree-label:not(.psp-row-selected):not(.psp-row-subselected), + tr:hover td:not(.psp-row-selected):not(.psp-row-subselected) { + border-color: var(--rt-hover--border-color, #c5c9d080) !important; background-color: transparent; - box-shadow: - // 0px -2px 0px rgba(0,0,0,0.05), + box-shadow: // 0px -2px 0px rgba(0,0,0,0.05), // 0px -4px 0px rgba(0,0,0,0.01), - 0px 1px 0px var(--rt-hover--border-color, #C5C9D080), - 0px 3px 0px rgba(0,0,0,0.05), - 0px 5px 0px rgba(0,0,0,0.01); + 0px 1px 0px var(--rt-hover--border-color, #c5c9d080), + 0px 3px 0px rgba(0, 0, 0, 0.05), 0px 5px 0px rgba(0, 0, 0, 0.01); } - tr:hover + tr th.psp-tree-leaf:not(.psp-row-selected):not(.psp-row-subselected), - tr:hover + tr th.psp-tree-label:not(.psp-row-selected):not(.psp-row-subselected), - tr:hover + tr td:not(.psp-row-selected):not(.psp-row-subselected) { + tr:hover + + tr + th.psp-tree-leaf:not(.psp-row-selected):not(.psp-row-subselected), + tr:hover + + tr + th.psp-tree-label:not(.psp-row-selected):not(.psp-row-subselected), + tr:hover + tr td:not(.psp-row-selected):not(.psp-row-subselected) { border-top-color: transparent; } @@ -37,7 +41,8 @@ regular-table { border-left-color: transparent; } - th:last-child, td:last-child { + th:last-child, + td:last-child { border-right-width: 1px; border-right-color: transparent; } @@ -49,13 +54,19 @@ regular-table { th:first-child:empty + th:not(:empty), th:first-child:empty ~ th:empty + th:not(:empty), td:first-child { - border-left-color: var(--rt-hover--border-color, #C5C9D080) !important; + border-left-color: var( + --rt-hover--border-color, + #c5c9d080 + ) !important; } - th:last-child, td:last-child { - border-right-color: var(--rt-hover--border-color, #C5C9D080) !important; + th:last-child, + td:last-child { + border-right-color: var( + --rt-hover--border-color, + #c5c9d080 + ) !important; } } } } - diff --git a/packages/perspective-viewer-datagrid/test/html/superstore.html b/packages/perspective-viewer-datagrid/test/html/superstore.html index 797f3ed247..a32fdecd8b 100644 --- a/packages/perspective-viewer-datagrid/test/html/superstore.html +++ b/packages/perspective-viewer-datagrid/test/html/superstore.html @@ -9,54 +9,46 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/packages/perspective-viewer-datagrid/test/js/superstore.spec.js b/packages/perspective-viewer-datagrid/test/js/superstore.spec.js index a19344c1ad..465dd5bf7c 100644 --- a/packages/perspective-viewer-datagrid/test/js/superstore.spec.js +++ b/packages/perspective-viewer-datagrid/test/js/superstore.spec.js @@ -14,7 +14,9 @@ const simple_tests = require("@finos/perspective-viewer/test/js/simple_tests.js" async function get_contents(page) { return await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer perspective-viewer-datagrid regular-table"); + const viewer = document.querySelector( + "perspective-viewer perspective-viewer-datagrid regular-table" + ); return viewer.innerHTML || "MISSING"; }); } diff --git a/packages/perspective-webpack-plugin/index.js b/packages/perspective-webpack-plugin/index.js index 305a45d281..7b0e9b9cca 100644 --- a/packages/perspective-webpack-plugin/index.js +++ b/packages/perspective-webpack-plugin/index.js @@ -20,11 +20,17 @@ class PerspectiveWebpackPlugin { inline: false, inlineWasm: false, inlineWorker: false, - wasmPath: path.dirname(require.resolve("@finos/perspective/package.json")), - viewerPath: path.dirname(require.resolve("@finos/perspective-viewer/package.json")), - workerPath: path.dirname(require.resolve("@finos/perspective/package.json")), + wasmPath: path.dirname( + require.resolve("@finos/perspective/package.json") + ), + viewerPath: path.dirname( + require.resolve("@finos/perspective-viewer/package.json") + ), + workerPath: path.dirname( + require.resolve("@finos/perspective/package.json") + ), wasmName: "[name].wasm", - workerName: "[name].js" + workerName: "[name].js", }, options ); @@ -32,7 +38,8 @@ class PerspectiveWebpackPlugin { apply(compiler) { const compilerOptions = compiler.options; - const moduleOptions = compilerOptions.module || (compilerOptions.module = {}); + const moduleOptions = + compilerOptions.module || (compilerOptions.module = {}); const rules = []; rules.push({ test: /perspective\.worker\.js$/, @@ -41,9 +48,9 @@ class PerspectiveWebpackPlugin { use: { loader: require.resolve("worker-loader"), options: { - filename: this.options.workerName - } - } + filename: this.options.workerName, + }, + }, }); rules.push({ @@ -53,9 +60,9 @@ class PerspectiveWebpackPlugin { use: { loader: require.resolve("worker-loader"), options: { - filename: "editor.worker.js" - } - } + filename: "editor.worker.js", + }, + }, }); if (this.options.inline || this.options.inlineWorker) { @@ -70,10 +77,10 @@ class PerspectiveWebpackPlugin { loader: require.resolve("string-replace-loader"), options: { search: /webpackMode:\s*?"eager"/g, - replace: "" - } - } - ] + replace: "", + }, + }, + ], }); } @@ -85,16 +92,16 @@ class PerspectiveWebpackPlugin { use: { loader: require.resolve("file-loader"), options: { - name: this.options.wasmName - } - } + name: this.options.wasmName, + }, + }, }); } else { rules.push({ test: /\.wasm$/, type: "javascript/auto", include: [this.options.wasmPath, this.options.viewerPath], - loader: require.resolve("arraybuffer-loader") + loader: require.resolve("arraybuffer-loader"), }); } @@ -102,7 +109,10 @@ class PerspectiveWebpackPlugin { test: /\.css$/, include: /monaco\-editor/, use: [ - {loader: require.resolve("css-loader"), options: {sourceMap: false}}, + { + loader: require.resolve("css-loader"), + options: {sourceMap: false}, + }, { loader: require.resolve("postcss-loader"), options: { @@ -113,19 +123,19 @@ class PerspectiveWebpackPlugin { plugins: [ cssnano({ preset: "lite", - discardComments: {removeAll: true} - }) - ] - } - } - } - ] + discardComments: {removeAll: true}, + }), + ], + }, + }, + }, + ], }); rules.push({ test: /\.ttf$/, include: /monaco\-editor/, - use: [require.resolve("file-loader")] + use: [require.resolve("file-loader")], }); const perspective_config = get_config(); @@ -138,14 +148,21 @@ class PerspectiveWebpackPlugin { loader: require.resolve("string-replace-loader"), options: { search: "global.__TEMPLATE_CONFIG__", - replace: JSON.stringify(perspective_config, null, 4) - } - } - ] + replace: JSON.stringify( + perspective_config, + null, + 4 + ), + }, + }, + ], }); } - const plugin_replace = new webpack.NormalModuleReplacementPlugin(/@finos\/perspective$/, "@finos/perspective/dist/esm/perspective.parallel.js"); + const plugin_replace = new webpack.NormalModuleReplacementPlugin( + /@finos\/perspective$/, + "@finos/perspective/dist/esm/perspective.parallel.js" + ); plugin_replace.apply(compiler); moduleOptions.rules = (moduleOptions.rules || []).concat(rules); diff --git a/packages/perspective-workspace/babel.config.js b/packages/perspective-workspace/babel.config.js index 58fa3d30f2..147cdc7301 100644 --- a/packages/perspective-workspace/babel.config.js +++ b/packages/perspective-workspace/babel.config.js @@ -7,13 +7,13 @@ module.exports = { chrome: "70", node: "12", ios: "13", - firefox: "68.5.0" + firefox: "68.5.0", }, modules: process.env.BABEL_MODULE || false, useBuiltIns: "usage", - corejs: 3 - } - ] + corejs: 3, + }, + ], ], sourceType: "unambiguous", plugins: [ @@ -21,6 +21,6 @@ module.exports = { ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining" - ] + "@babel/plugin-proposal-optional-chaining", + ], }; diff --git a/packages/perspective-workspace/config/cjs.config.js b/packages/perspective-workspace/config/cjs.config.js index d71706bc36..584cd18fd3 100644 --- a/packages/perspective-workspace/config/cjs.config.js +++ b/packages/perspective-workspace/config/cjs.config.js @@ -1,7 +1,7 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/esm/index.js", externals: [/^[a-z0-9@]/], @@ -9,10 +9,10 @@ module.exports = common({}, config => filename: "perspective-workspace.js", library: "perspective-workspace", libraryTarget: "commonjs2", - path: path.resolve(__dirname, "../dist/cjs") + path: path.resolve(__dirname, "../dist/cjs"), }, experiments: { - syncWebAssembly: true - } + syncWebAssembly: true, + }, }) ); diff --git a/packages/perspective-workspace/config/jest.integration.config.js b/packages/perspective-workspace/config/jest.integration.config.js index 837561ef84..90609325dd 100644 --- a/packages/perspective-workspace/config/jest.integration.config.js +++ b/packages/perspective-workspace/config/jest.integration.config.js @@ -1,5 +1,5 @@ const config = require("@finos/perspective-test/jest.config"); module.exports = Object.assign({}, config, { - roots: ["../test/js/integration"] + roots: ["../test/js/integration"], }); diff --git a/packages/perspective-workspace/config/jest.unit.config.js b/packages/perspective-workspace/config/jest.unit.config.js index c9b65f3569..e226d18f13 100644 --- a/packages/perspective-workspace/config/jest.unit.config.js +++ b/packages/perspective-workspace/config/jest.unit.config.js @@ -1,6 +1,6 @@ module.exports = { transform: { - ".js$": "@finos/perspective-test/src/js/transform.js" + ".js$": "@finos/perspective-test/src/js/transform.js", }, - rootDir: "../test/js/unit/" + rootDir: "../test/js/unit/", }; diff --git a/packages/perspective-workspace/config/theme.config.js b/packages/perspective-workspace/config/theme.config.js index 5b66ca8bd9..9af6ac6683 100644 --- a/packages/perspective-workspace/config/theme.config.js +++ b/packages/perspective-workspace/config/theme.config.js @@ -2,37 +2,43 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const path = require("path"); const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries"); -module.exports = ["material", "material.dark"].map(x => ({ +module.exports = ["material", "material.dark"].map((x) => ({ mode: "production", entry: { - [x]: path.join(__dirname, `../src/theme/${x}.less`) + [x]: path.join(__dirname, `../src/theme/${x}.less`), }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", - chunkFilename: "[id].css" + chunkFilename: "[id].css", }), - new FixStyleOnlyEntriesPlugin() + new FixStyleOnlyEntriesPlugin(), ], performance: { hints: false, maxEntrypointSize: 512000, - maxAssetSize: 512000 + maxAssetSize: 512000, + }, + stats: { + modules: false, + hash: false, + version: false, + builtAt: false, + entrypoints: false, }, - stats: {modules: false, hash: false, version: false, builtAt: false, entrypoints: false}, output: { - path: path.resolve(__dirname, "../dist/umd/") + path: path.resolve(__dirname, "../dist/umd/"), }, module: { rules: [ { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, "css-loader"] + use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.less$/, - use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"] - } - ] - } + use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"], + }, + ], + }, })); diff --git a/packages/perspective-workspace/config/umd.config.js b/packages/perspective-workspace/config/umd.config.js index 71d10b9393..2ed45c1f0b 100644 --- a/packages/perspective-workspace/config/umd.config.js +++ b/packages/perspective-workspace/config/umd.config.js @@ -1,17 +1,17 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/esm/index.js", output: { filename: "perspective-workspace.js", library: "perspective-workspace", libraryTarget: "umd", - path: path.resolve(__dirname, "../dist/umd") + path: path.resolve(__dirname, "../dist/umd"), }, experiments: { - syncWebAssembly: true - } + syncWebAssembly: true, + }, }) ); diff --git a/packages/perspective-workspace/config/watch.config.js b/packages/perspective-workspace/config/watch.config.js index 8f518a0379..eb88b293cf 100644 --- a/packages/perspective-workspace/config/watch.config.js +++ b/packages/perspective-workspace/config/watch.config.js @@ -1,10 +1,10 @@ const path = require("path"); const common = require("@finos/perspective/src/config/common.config.js"); -module.exports = common({}, config => { +module.exports = common({}, (config) => { config.module.rules.push({ test: /\.js$/, - use: [{loader: "babel-loader"}] + use: [{loader: "babel-loader"}], }); return Object.assign(config, { entry: "./src/js/index.js", @@ -14,10 +14,10 @@ module.exports = common({}, config => { filename: "perspective-workspace.js", library: "perspective-workspace", libraryTarget: "commonjs2", - path: path.resolve(__dirname, "../dist/cjs") + path: path.resolve(__dirname, "../dist/cjs"), }, experiments: { - syncWebAssembly: true - } + syncWebAssembly: true, + }, }); }); diff --git a/packages/perspective-workspace/package.json b/packages/perspective-workspace/package.json index 5862950276..b0d49b47ca 100644 --- a/packages/perspective-workspace/package.json +++ b/packages/perspective-workspace/package.json @@ -54,4 +54,4 @@ "babel-runtime": "^6.26.0", "lodash.clonedeep": "^4.5.0" } -} \ No newline at end of file +} diff --git a/packages/perspective-workspace/src/html/workspace.html b/packages/perspective-workspace/src/html/workspace.html index a7a3424eee..ac80cf3713 100644 --- a/packages/perspective-workspace/src/html/workspace.html +++ b/packages/perspective-workspace/src/html/workspace.html @@ -8,4 +8,4 @@ --> \ No newline at end of file + diff --git a/packages/perspective-workspace/src/js/index.js b/packages/perspective-workspace/src/js/index.js index 08af0f0ed5..7c17282f94 100644 --- a/packages/perspective-workspace/src/js/index.js +++ b/packages/perspective-workspace/src/js/index.js @@ -142,7 +142,11 @@ class PerspectiveWorkspaceElement extends HTMLElement { } async flush() { - await Promise.all(Array.from(this.querySelectorAll("perspective-viewer")).map(x => x.flush())); + await Promise.all( + Array.from(this.querySelectorAll("perspective-viewer")).map((x) => + x.flush() + ) + ); } addTable(name, table) { @@ -183,7 +187,11 @@ class PerspectiveWorkspaceElement extends HTMLElement { _light_dom_changed() { const viewers = Array.from(this.childNodes); for (const viewer of viewers) { - if ([Node.TEXT_NODE, document.COMMENT_NODE].indexOf(viewer.nodeType) > -1) { + if ( + [Node.TEXT_NODE, document.COMMENT_NODE].indexOf( + viewer.nodeType + ) > -1 + ) { continue; } this.workspace.update_widget_for_viewer(viewer); diff --git a/packages/perspective-workspace/src/js/workspace/commands.js b/packages/perspective-workspace/src/js/workspace/commands.js index e44ca5d5ea..67e150e79e 100644 --- a/packages/perspective-workspace/src/js/workspace/commands.js +++ b/packages/perspective-workspace/src/js/workspace/commands.js @@ -10,68 +10,80 @@ import {CommandRegistry} from "@lumino/commands"; import {MODE} from "./workspace"; -export const createCommands = workspace => { +export const createCommands = (workspace) => { const commands = new CommandRegistry(); commands.addCommand("workspace:export", { - execute: args => args.widget.viewer.download(), + execute: (args) => args.widget.viewer.download(), iconClass: "menu-export", label: "Export CSV", - mnemonic: 0 + mnemonic: 0, }); commands.addCommand("workspace:copy", { - execute: args => args.widget.viewer.copy(), + execute: (args) => args.widget.viewer.copy(), iconClass: "menu-copy", label: "Copy To Clipboard", - mnemonic: 0 + mnemonic: 0, }); commands.addCommand("workspace:reset", { - execute: args => args.widget.viewer.reset(), + execute: (args) => args.widget.viewer.reset(), iconClass: "menu-reset", label: "Reset", - mnemonic: 0 + mnemonic: 0, }); commands.addCommand("workspace:duplicate", { execute: ({widget}) => workspace.duplicate(widget), iconClass: "menu-duplicate", - isVisible: args => (args.widget.parent === workspace.dockpanel ? true : false), + isVisible: (args) => + args.widget.parent === workspace.dockpanel ? true : false, label: "Duplicate", - mnemonic: 0 + mnemonic: 0, }); commands.addCommand("workspace:master", { - execute: args => workspace.toggleMasterDetail(args.widget), + execute: (args) => workspace.toggleMasterDetail(args.widget), isVisible: () => workspace.mode === MODE.GLOBAL_FILTERS, - iconClass: args => (args.widget.parent === workspace.dockpanel ? "menu-master" : "menu-detail"), - label: args => (args.widget.parent === workspace.dockpanel ? "Create Global Filter" : "Remove Global Filter"), - mnemonic: 0 + iconClass: (args) => + args.widget.parent === workspace.dockpanel + ? "menu-master" + : "menu-detail", + label: (args) => + args.widget.parent === workspace.dockpanel + ? "Create Global Filter" + : "Remove Global Filter", + mnemonic: 0, }); commands.addCommand("workspace:link", { - execute: args => workspace.toggleLink(args.widget), + execute: (args) => workspace.toggleLink(args.widget), isVisible: () => workspace.mode === MODE.LINKED, - iconClass: args => (workspace.isLinked(args.widget) ? "menu-unlink" : "menu-link"), - label: args => (workspace.isLinked(args.widget) ? "Unlink" : "Link"), - mnemonic: 0 + iconClass: (args) => + workspace.isLinked(args.widget) ? "menu-unlink" : "menu-link", + label: (args) => (workspace.isLinked(args.widget) ? "Unlink" : "Link"), + mnemonic: 0, }); commands.addCommand("workspace:maximize", { - execute: args => workspace.toggleSingleDocument(args.widget), - isVisible: args => args.widget.parent === workspace.dockpanel && workspace.dockpanel.mode !== "single-document", + execute: (args) => workspace.toggleSingleDocument(args.widget), + isVisible: (args) => + args.widget.parent === workspace.dockpanel && + workspace.dockpanel.mode !== "single-document", iconClass: "menu-maximize", label: () => "Maximize", - mnemonic: 0 + mnemonic: 0, }); commands.addCommand("workspace:minimize", { - execute: args => workspace.toggleSingleDocument(args.widget), - isVisible: args => args.widget.parent === workspace.dockpanel && workspace.dockpanel.mode === "single-document", + execute: (args) => workspace.toggleSingleDocument(args.widget), + isVisible: (args) => + args.widget.parent === workspace.dockpanel && + workspace.dockpanel.mode === "single-document", iconClass: "menu-minimize", label: () => "Minimize", - mnemonic: 0 + mnemonic: 0, }); return commands; diff --git a/packages/perspective-workspace/src/js/workspace/discrete.js b/packages/perspective-workspace/src/js/workspace/discrete.js index d3c6be995d..95722903f7 100644 --- a/packages/perspective-workspace/src/js/workspace/discrete.js +++ b/packages/perspective-workspace/src/js/workspace/discrete.js @@ -46,7 +46,9 @@ class DiscreteContext { const old = this._offset_drag_current[key]; const method = client < old ? "ceil" : "floor"; const diff = client - val; - return Math[method](diff / DISCRETE_STEP_SIZE) * DISCRETE_STEP_SIZE + val; + return ( + Math[method](diff / DISCRETE_STEP_SIZE) * DISCRETE_STEP_SIZE + val + ); } _check_prev(clientX, clientY) { @@ -63,8 +65,14 @@ class DiscreteContext { clientY -= this._rect.top; const blockX = this._block_move("x", clientX); const blockY = this._block_move("y", clientY); - clientX = this._clamp("width", clientX) || this._clamp("width", blockX) || blockX; - clientY = this._clamp("height", clientY) || this._clamp("height", blockY) || blockY; + clientX = + this._clamp("width", clientX) || + this._clamp("width", blockX) || + blockX; + clientY = + this._clamp("height", clientY) || + this._clamp("height", blockY) || + blockY; if (this._check_prev(clientX, clientY)) { return {x: clientX + this._rect.left, y: clientY + this._rect.top}; } @@ -74,7 +82,7 @@ class DiscreteContext { this._rect = rect; this._offset_drag_start = this._offset_drag_current = { x: x - rect.left, - y: y - rect.top + y: y - rect.top, }; } } @@ -89,7 +97,13 @@ function extend(base, handle) { if (!elem.classList.contains(handle)) { break; } - if ([elem, elem.parentElement].some(x => x.getAttribute("data-orientation") === "horizontal")) { + if ( + [elem, elem.parentElement].some( + (x) => + x.getAttribute("data-orientation") === + "horizontal" + ) + ) { this.addClass("ew"); } else { this.addClass("ns"); @@ -98,7 +112,11 @@ function extend(base, handle) { const {clientX, clientY} = event; this._offset_target = elem; this._offset_target.classList.add("resizing"); - this.context = new DiscreteContext(clientX, clientY, this.node.getBoundingClientRect()); + this.context = new DiscreteContext( + clientX, + clientY, + this.node.getBoundingClientRect() + ); } break; case "mousemove": @@ -109,7 +127,11 @@ function extend(base, handle) { event.stopPropagation(); const update = this.context.calculate_move(event); if (update) { - const new_event = cloneMouseEvent(update.x, update.y, event); + const new_event = cloneMouseEvent( + update.x, + update.y, + event + ); super.handleEvent(new_event); } return; @@ -132,8 +154,14 @@ function extend(base, handle) { }; } -export class DiscreteDockPanel extends extend(DockPanel, "p-DockPanel-handle") {} -export class DiscreteSplitPanel extends extend(SplitPanel, "p-SplitPanel-handle") { +export class DiscreteDockPanel extends extend( + DockPanel, + "p-DockPanel-handle" +) {} +export class DiscreteSplitPanel extends extend( + SplitPanel, + "p-SplitPanel-handle" +) { constructor(...args) { super(...args); this.layoutModified = new Signal(this); diff --git a/packages/perspective-workspace/src/js/workspace/dockpanel.js b/packages/perspective-workspace/src/js/workspace/dockpanel.js index 4c7b78f35a..f39b9f3f52 100644 --- a/packages/perspective-workspace/src/js/workspace/dockpanel.js +++ b/packages/perspective-workspace/src/js/workspace/dockpanel.js @@ -14,7 +14,9 @@ import {PerspectiveTabBarRenderer} from "./tabbarrenderer"; class PerspectiveDockPanelRenderer extends DockPanel.Renderer { createTabBar() { - const tabbar = new PerspectiveTabBar({renderer: new PerspectiveTabBarRenderer()}); + const tabbar = new PerspectiveTabBar({ + renderer: new PerspectiveTabBarRenderer(), + }); tabbar.addClass("p-DockPanel-tabBar"); return tabbar; } @@ -43,7 +45,9 @@ export class PerspectiveDockPanel extends DiscreteDockPanel { if (layout?.hasOwnProperty("main")) { return PerspectiveDockPanel.getWidgets(layout.main); } else if (layout?.children) { - return layout.children.flatMap(widget => PerspectiveDockPanel.getWidgets(widget)); + return layout.children.flatMap((widget) => + PerspectiveDockPanel.getWidgets(widget) + ); } else if (layout?.widgets) { return layout.widgets; } @@ -52,16 +56,22 @@ export class PerspectiveDockPanel extends DiscreteDockPanel { static mapWidgets(widgetFunc, layout) { if (layout.main) { - layout.main = PerspectiveDockPanel.mapWidgets(widgetFunc, layout.main); + layout.main = PerspectiveDockPanel.mapWidgets( + widgetFunc, + layout.main + ); } else if (layout.children) { - layout.children = layout.children.map(widget => PerspectiveDockPanel.mapWidgets(widgetFunc, widget)); + layout.children = layout.children.map((widget) => + PerspectiveDockPanel.mapWidgets(widgetFunc, widget) + ); } else if (layout.widgets) { - layout.widgets = layout.widgets.map(widget => widgetFunc(widget)); + layout.widgets = layout.widgets.map((widget) => widgetFunc(widget)); } return layout; } onAfterAttach() { - this.spacing = parseInt(window.getComputedStyle(this.node).padding) || 0; + this.spacing = + parseInt(window.getComputedStyle(this.node).padding) || 0; } } diff --git a/packages/perspective-workspace/src/js/workspace/tabbar.js b/packages/perspective-workspace/src/js/workspace/tabbar.js index 416ac5618f..7c734002db 100644 --- a/packages/perspective-workspace/src/js/workspace/tabbar.js +++ b/packages/perspective-workspace/src/js/workspace/tabbar.js @@ -30,7 +30,13 @@ export class PerspectiveTabBar extends TabBar { } _check_shade() { - if (Array.from(this.contentNode.children).filter(x => x.classList.contains("settings_open") && x.classList.contains("p-mod-current")).length > 0) { + if ( + Array.from(this.contentNode.children).filter( + (x) => + x.classList.contains("settings_open") && + x.classList.contains("p-mod-current") + ).length > 0 + ) { this.contentNode.classList.add("inactive-blur"); } else { this.contentNode.classList.remove("inactive-blur"); @@ -57,8 +63,12 @@ export class PerspectiveTabBar extends TabBar { const tabs = this.contentNode.children; // Find the index of the released tab. - const index = ArrayExt.findFirstIndex(tabs, tab => { - return ElementExt.hitTest(tab, event.clientX, event.clientY); + const index = ArrayExt.findFirstIndex(tabs, (tab) => { + return ElementExt.hitTest( + tab, + event.clientX, + event.clientY + ); }); if (index < 0) { @@ -77,7 +87,7 @@ export class PerspectiveTabBar extends TabBar { onTitleChangeRequest(event) { const oldValue = event.target.value; - const stopEvents = event => event.stopPropagation(); + const stopEvents = (event) => event.stopPropagation(); const onCancel = () => { this.title.label = oldValue; @@ -86,7 +96,7 @@ export class PerspectiveTabBar extends TabBar { removeEventListeners(); }; - const onEnter = event => { + const onEnter = (event) => { if (event.keyCode === 13) { removeEventListeners(); this.currentTitle.owner.name = event.target.value; @@ -101,12 +111,15 @@ export class PerspectiveTabBar extends TabBar { mousedown: stopEvents, mouseup: stopEvents, keydown: onEnter, - blur: onCancel + blur: onCancel, }; const removeEventListeners = () => { for (let eventName in listeners) { - event.target.removeEventListener(eventName, listeners[eventName]); + event.target.removeEventListener( + eventName, + listeners[eventName] + ); } }; @@ -129,7 +142,9 @@ export class PerspectiveTabBar extends TabBar { } checkCondensed(msg) { - const approxWidth = (msg ? msg.width : this.node.offsetWidth) / this.contentNode.children.length; + const approxWidth = + (msg ? msg.width : this.node.offsetWidth) / + this.contentNode.children.length; if (approxWidth < 400) { this.node.classList.add("condensed"); } else { @@ -144,7 +159,10 @@ export class PerspectiveTabBar extends TabBar { * */ retargetEvent(event) { - Object.defineProperty(event, "target", {value: event.composedPath()[0], enumerable: true}); + Object.defineProperty(event, "target", { + value: event.composedPath()[0], + enumerable: true, + }); return event; } diff --git a/packages/perspective-workspace/src/js/workspace/tabbarrenderer.js b/packages/perspective-workspace/src/js/workspace/tabbarrenderer.js index 8130762faa..8371159932 100644 --- a/packages/perspective-workspace/src/js/workspace/tabbarrenderer.js +++ b/packages/perspective-workspace/src/js/workspace/tabbarrenderer.js @@ -12,7 +12,7 @@ import {TabBar} from "@lumino/widgets"; export const TabBarItems = { Config: "config", - Label: "label" + Label: "label", }; export const DEFAULT_TITLE = "[untitled]"; @@ -24,7 +24,12 @@ export class PerspectiveTabBarRenderer extends TabBar.Renderer { } renderLabel(data) { - return h.input({className: "p-TabBar-tabLabel", readonly: true, id: TabBarItems.Label, value: data.title.label || DEFAULT_TITLE}); + return h.input({ + className: "p-TabBar-tabLabel", + readonly: true, + id: TabBarItems.Label, + value: data.title.label || DEFAULT_TITLE, + }); } renderTab(data) { @@ -45,6 +50,9 @@ export class PerspectiveTabBarRenderer extends TabBar.Renderer { } renderConfigIcon() { - return h.div({className: "p-TabBar-tabConfigIcon", id: TabBarItems.Config}); + return h.div({ + className: "p-TabBar-tabConfigIcon", + id: TabBarItems.Config, + }); } } diff --git a/packages/perspective-workspace/src/js/workspace/utils.js b/packages/perspective-workspace/src/js/workspace/utils.js index 91812678db..fcade7b6bc 100644 --- a/packages/perspective-workspace/src/js/workspace/utils.js +++ b/packages/perspective-workspace/src/js/workspace/utils.js @@ -50,9 +50,13 @@ export function registerElement(templateString, styleString, proto) { const template = importTemplate(templateString); setTemplateContent(template); if (styleString) { - template.innerHTML = `` + template.innerHTML; + template.innerHTML = + `` + template.innerHTML; } - template.innerHTML = `` + template.innerHTML; + template.innerHTML = + `` + template.innerHTML; let is_locked = 0; const _perspective_element = class extends proto { @@ -65,7 +69,11 @@ export function registerElement(templateString, styleString, proto) { value = "null"; } - if (name[0] !== "_" && old != value && !!Object.getOwnPropertyDescriptor(proto.prototype, name).set) { + if ( + name[0] !== "_" && + old != value && + !!Object.getOwnPropertyDescriptor(proto.prototype, name).set + ) { this[name] = value; } } @@ -100,7 +108,12 @@ export function registerElement(templateString, styleString, proto) { // Call all attributes bound to setters on the proto for (let key of Object.getOwnPropertyNames(proto.prototype)) { if (key !== "connectedCallback") { - if (this.hasAttribute(key) && key[0] !== "_" && !!Object.getOwnPropertyDescriptor(proto.prototype, key).set) { + if ( + this.hasAttribute(key) && + key[0] !== "_" && + !!Object.getOwnPropertyDescriptor(proto.prototype, key) + .set + ) { this[key] = this.getAttribute(key); } } @@ -118,7 +131,7 @@ export function registerElement(templateString, styleString, proto) { let descriptor = Object.getOwnPropertyDescriptor(proto.prototype, key); if (descriptor && descriptor.set) { let old = descriptor.set; - descriptor.set = function(val) { + descriptor.set = function (val) { if (!this.hasAttribute(key) || this.getAttribute(key) !== val) { this.setAttribute(key, val); return; @@ -139,8 +152,8 @@ export function registerElement(templateString, styleString, proto) { } export function bindTemplate(template, ...styleStrings) { - const style = styleStrings.map(x => x.toString()).join("\n"); - return function(cls) { + const style = styleStrings.map((x) => x.toString()).join("\n"); + return function (cls) { return registerElement(template, {toString: () => style}, cls); }; } diff --git a/packages/perspective-workspace/src/js/workspace/widget.js b/packages/perspective-workspace/src/js/workspace/widget.js index 4ff39aa57d..664cfa5794 100644 --- a/packages/perspective-workspace/src/js/workspace/widget.js +++ b/packages/perspective-workspace/src/js/workspace/widget.js @@ -83,7 +83,8 @@ export class PerspectiveViewerWidget extends Widget { } restore(config) { - const {master, table, linked, name, selectable, ...viewerConfig} = config; + const {master, table, linked, name, selectable, ...viewerConfig} = + config; this.master = master; this.name = name; this.linked = linked; @@ -110,7 +111,7 @@ export class PerspectiveViewerWidget extends Widget { master: this.master, name: this.viewer.getAttribute("name"), table: this.viewer.getAttribute("table"), - linked: this.linked + linked: this.linked, }; } diff --git a/packages/perspective-workspace/src/js/workspace/workspace.js b/packages/perspective-workspace/src/js/workspace/workspace.js index 5c6ebcce93..ab80b18d91 100644 --- a/packages/perspective-workspace/src/js/workspace/workspace.js +++ b/packages/perspective-workspace/src/js/workspace/workspace.js @@ -26,12 +26,12 @@ let ID_COUNTER = 0; export const SIDE = { LEFT: "left", - RIGHT: "right" + RIGHT: "right", }; export const MODE = { GLOBAL_FILTERS: "globalFilters", - LINKED: "linked" + LINKED: "linked", }; class ObservableMap extends Map { @@ -68,7 +68,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { this.addClass("perspective-workspace"); this.dockpanel = new PerspectiveDockPanel("main", { - enableContextMenu: false + enableContextMenu: false, }); this.detailPanel = new Panel(); this.detailPanel.layout.fitPolicy = "set-no-constraint"; @@ -127,9 +127,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { } if (this._side !== value && this.masterPanel.isAttached) { - const newSizes = this.relativeSizes() - .slice() - .reverse(); + const newSizes = this.relativeSizes().slice().reverse(); this.detailPanel.close(); this.masterPanel.close(); @@ -153,13 +151,18 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { async save() { const layout = { sizes: [...this.relativeSizes()], - detail: PerspectiveDockPanel.mapWidgets(widget => widget.viewer.getAttribute("slot"), this.dockpanel.saveLayout()), - mode: this.mode + detail: PerspectiveDockPanel.mapWidgets( + (widget) => widget.viewer.getAttribute("slot"), + this.dockpanel.saveLayout() + ), + mode: this.mode, }; if (this.masterPanel.isAttached) { const master = { - widgets: this.masterPanel.widgets.map(widget => widget.viewer.getAttribute("slot")), - sizes: [...this.masterPanel.relativeSizes()] + widgets: this.masterPanel.widgets.map((widget) => + widget.viewer.getAttribute("slot") + ), + sizes: [...this.masterPanel.relativeSizes()], }; layout.master = master; } @@ -167,20 +170,33 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { for (const widget of this.masterPanel.widgets) { viewers[widget.viewer.getAttribute("slot")] = await widget.save(); } - const widgets = PerspectiveDockPanel.getWidgets(this.dockpanel.saveLayout()); + const widgets = PerspectiveDockPanel.getWidgets( + this.dockpanel.saveLayout() + ); await Promise.all( - widgets.map(async widget => { - viewers[widget.viewer.getAttribute("slot")] = await widget.save(); + widgets.map(async (widget) => { + viewers[widget.viewer.getAttribute("slot")] = + await widget.save(); }) ); return {...layout, viewers}; } async restore(value) { - const {sizes, master, detail, viewers: viewer_configs = [], mode = MODE.GLOBAL_FILTERS} = cloneDeep(value); + const { + sizes, + master, + detail, + viewers: viewer_configs = [], + mode = MODE.GLOBAL_FILTERS, + } = cloneDeep(value); this.mode = mode; - if (this.mode === MODE.GLOBAL_FILTERS && master && master.widgets.length > 0) { + if ( + this.mode === MODE.GLOBAL_FILTERS && + master && + master.widgets.length > 0 + ) { this.setupMasterPanel(sizes || DEFAULT_WORKSPACE_SIZE); } else { if (this.masterPanel.isAttached) { @@ -195,22 +211,42 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { // Using ES generators as context managers .. for (const viewers of this._capture_viewers()) { for (const widgets of this._capture_widgets()) { - const callback = this._restore_callback.bind(this, viewer_configs, viewers, widgets); + const callback = this._restore_callback.bind( + this, + viewer_configs, + viewers, + widgets + ); if (detail) { - const detailLayout = PerspectiveDockPanel.mapWidgets(callback.bind(this, false), detail); + const detailLayout = PerspectiveDockPanel.mapWidgets( + callback.bind(this, false), + detail + ); this.dockpanel.restoreLayout(detailLayout); - tasks = tasks.concat(PerspectiveDockPanel.getWidgets(detailLayout).map(x => x.task)); + tasks = tasks.concat( + PerspectiveDockPanel.getWidgets(detailLayout).map( + (x) => x.task + ) + ); } if (master) { if (this.mode === MODE.GLOBAL_FILTERS) { - tasks = tasks.concat(master.widgets.map(x => callback.bind(this, true)(x).task)); - master.sizes && this.masterPanel.setRelativeSizes(master.sizes); + tasks = tasks.concat( + master.widgets.map( + (x) => callback.bind(this, true)(x).task + ) + ); + master.sizes && + this.masterPanel.setRelativeSizes(master.sizes); } else { tasks = tasks.concat( - master.widgets.map(config => { - const widget = callback.bind(this, undefined)(config); + master.widgets.map((config) => { + const widget = callback.bind( + this, + undefined + )(config); this.dockpanel.addWidget(widget); return widget.task; }) @@ -238,15 +274,24 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { yield viewers; const ending_widgets = this.getAllWidgets(); for (const viewer of viewers) { - let widget = ending_widgets.find(x => x.viewer === viewer); - if (!widget && Array.from(this.element.children).indexOf(viewer) > -1) { + let widget = ending_widgets.find((x) => x.viewer === viewer); + if ( + !widget && + Array.from(this.element.children).indexOf(viewer) > -1 + ) { this.element.removeChild(viewer); viewer.delete(); } } } - _restore_callback(viewers, starting_viewers, starting_widgets, master, widgetName) { + _restore_callback( + viewers, + starting_viewers, + starting_widgets, + master, + widgetName + ) { let viewer_config; if (typeof widgetName === "string") { viewer_config = viewers[widgetName]; @@ -254,29 +299,39 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { viewer_config = widgetName; widgetName = viewer_config.slot; } - let viewer = !!widgetName && starting_viewers.find(x => x.getAttribute("slot") === widgetName); + let viewer = + !!widgetName && + starting_viewers.find((x) => x.getAttribute("slot") === widgetName); let widget; if (viewer) { - widget = starting_widgets.find(x => x.viewer === viewer); + widget = starting_widgets.find((x) => x.viewer === viewer); if (widget) { widget.restore({...viewer_config, master}); } else { widget = this._createWidget({ config: {...viewer_config, master}, - viewer + viewer, }); } } else if (viewer_config) { widget = this._createWidgetAndNode({ config: {...viewer_config, master}, - slot: widgetName + slot: widgetName, }); } else { - console.error(`Could not find or create for slot "${widgetName}"`); + console.error( + `Could not find or create for slot "${widgetName}"` + ); } if (master || this.mode === MODE.LINKED) { - widget.viewer.addEventListener("perspective-select", this.onPerspectiveSelect); - widget.viewer.addEventListener("perspective-click", this.onPerspectiveSelect); + widget.viewer.addEventListener( + "perspective-select", + this.onPerspectiveSelect + ); + widget.viewer.addEventListener( + "perspective-click", + this.onPerspectiveSelect + ); // TODO remove event listener this.masterPanel.addWidget(widget); } @@ -289,7 +344,9 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { _validate(table) { if (!table || !("view" in table) || typeof table?.view !== "function") { - throw new Error("Only `perspective.Table()` instances can be added to `tables`"); + throw new Error( + "Only `perspective.Table()` instances can be added to `tables`" + ); } return table; } @@ -300,7 +357,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { } else { this._validate(table); } - this.getAllWidgets().forEach(widget => { + this.getAllWidgets().forEach((widget) => { if (widget.viewer.getAttribute("table") === name) { widget.load(table); } @@ -308,9 +365,13 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { } _delete_listener(name) { - const isUsed = this.getAllWidgets().some(widget => widget.viewer.getAttribute("table") === name); + const isUsed = this.getAllWidgets().some( + (widget) => widget.viewer.getAttribute("table") === name + ); if (isUsed) { - console.error(`Cannot remove table: '${name}' because it's still bound to widget(s)`); + console.error( + `Cannot remove table: '${name}' because it's still bound to widget(s)` + ); return false; } return true; @@ -327,12 +388,14 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { const slot = this.node.querySelector(`slot[name=${slot_name}]`); if (!slot) { //const name = viewer.getAttribute("name"); - console.warn(`Undocked ${viewer.outerHTML}, creating default layout`); + console.warn( + `Undocked ${viewer.outerHTML}, creating default layout` + ); const widget = this._createWidget({ name: viewer.getAttribute("name"), table: viewer.getAttribute("table"), config: {master: false}, - viewer + viewer, }); this.dockpanel.addWidget(widget); this.dockpanel.activateWidget(widget); @@ -372,7 +435,10 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { const index = this.masterPanel.widgets.indexOf(widget) + 1; this.masterPanel.insertWidget(index, duplicate); } else { - this.dockpanel.addWidget(duplicate, {mode: "split-right", ref: widget}); + this.dockpanel.addWidget(duplicate, { + mode: "split-right", + ref: widget, + }); } await duplicate.task; @@ -385,8 +451,8 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { new CustomEvent("workspace-toggle-global-filter", { detail: { widget, - isGlobalFilter: !isGlobalFilter - } + isGlobalFilter: !isGlobalFilter, + }, }) ); @@ -428,32 +494,44 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { const table = await viewer.getTable(); const availableColumns = Object.keys(await table.schema()); const currentFilters = config.filters || []; - const columnAvailable = filter => filter[0] && availableColumns.includes(filter[0]); + const columnAvailable = (filter) => + filter[0] && availableColumns.includes(filter[0]); const validFilters = filters.filter(columnAvailable); - validFilters.push(...currentFilters.filter(x => !candidates.has(x[0]))); - const newFilters = uniqBy(validFilters, item => item[0]); + validFilters.push( + ...currentFilters.filter((x) => !candidates.has(x[0])) + ); + const newFilters = uniqBy(validFilters, (item) => item[0]); await viewer.restore({filters: newFilters}); } - onPerspectiveSelect = async event => { + onPerspectiveSelect = async (event) => { const config = await event.target.save(); // perspective-select is already handled for hypergrid - if (event.type === "perspective-click" && (config.plugin === "Datagrid" || config.plugin === null)) { + if ( + event.type === "perspective-click" && + (config.plugin === "Datagrid" || config.plugin === null) + ) { return; } - const candidates = new Set([...(config["row-pivots"] || []), ...(config["column-pivots"] || []), ...(config.filters || []).map(x => x[0])]); + const candidates = new Set([ + ...(config["row-pivots"] || []), + ...(config["column-pivots"] || []), + ...(config.filters || []).map((x) => x[0]), + ]); const filters = [...event.detail.config.filters]; if (this.mode === MODE.LINKED) { - this._linkedViewers.forEach(viewer => { + this._linkedViewers.forEach((viewer) => { if (viewer !== event.target) { this._filterViewer(viewer, filters, candidates); } }); } else { - toArray(this.dockpanel.widgets()).forEach(widget => this._filterViewer(widget.viewer, filters, candidates)); + toArray(this.dockpanel.widgets()).forEach((widget) => + this._filterViewer(widget.viewer, filters, candidates) + ); } }; @@ -473,8 +551,14 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { widget.isHidden && widget.show(); widget.viewer.restyleElement(); - widget.viewer.addEventListener("perspective-click", this.onPerspectiveSelect); - widget.viewer.addEventListener("perspective-select", this.onPerspectiveSelect); + widget.viewer.addEventListener( + "perspective-click", + this.onPerspectiveSelect + ); + widget.viewer.addEventListener( + "perspective-select", + this.onPerspectiveSelect + ); } makeDetail(widget) { @@ -489,8 +573,14 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { } widget.viewer.restyleElement(); - widget.viewer.removeEventListener("perspective-click", this.onPerspectiveSelect); - widget.viewer.removeEventListener("perspective-select", this.onPerspectiveSelect); + widget.viewer.removeEventListener( + "perspective-click", + this.onPerspectiveSelect + ); + widget.viewer.removeEventListener( + "perspective-select", + this.onPerspectiveSelect + ); } isLinked(widget) { @@ -504,7 +594,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { // if this is the first linked viewer, make viewers with // row-pivots selectable if (this._linkedViewers.length === 1) { - this.getAllWidgets().forEach(async widget => { + this.getAllWidgets().forEach(async (widget) => { const config = await widget.viewer.save(); if (config["row-pivots"]) { await widget.viewer.restore({selectable: true}); @@ -519,10 +609,17 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { if (widget.linked) { this._linkWidget(widget); } else { - widget.title.className = widget.title.className.replace(/ linked/g, ""); - this._linkedViewers = this._linkedViewers.filter(viewer => viewer !== widget.viewer); + widget.title.className = widget.title.className.replace( + / linked/g, + "" + ); + this._linkedViewers = this._linkedViewers.filter( + (viewer) => viewer !== widget.viewer + ); if (this._linkedViewers.length === 0) { - this.getAllWidgets().forEach(widget => widget.viewer.restore({selectable: false})); + this.getAllWidgets().forEach((widget) => + widget.viewer.restore({selectable: false}) + ); } } } @@ -536,7 +633,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { createContextMenu(widget) { const contextMenu = new Menu({ commands: this.commands, - renderer: this.menuRenderer + renderer: this.menuRenderer, }); contextMenu.addItem({command: "workspace:maximize", args: {widget}}); @@ -557,7 +654,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { if (this.customCommands.length > 0) { contextMenu.addItem({type: "separator"}); - this.customCommands.forEach(command => { + this.customCommands.forEach((command) => { contextMenu.addItem({command, args: {widget}}); }); } @@ -566,7 +663,10 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { showContextMenu(widget, event) { const menu = this.createContextMenu(widget); - const tabbar = find(this.dockpanel.tabBars(), bar => bar.currentTitle.owner === widget); + const tabbar = find( + this.dockpanel.tabBars(), + (bar) => bar.currentTitle.owner === widget + ); widget.addClass("context-focus"); widget.viewer.classList.add("context-focus"); @@ -600,8 +700,8 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { */ clearLayout() { - this.getAllWidgets().forEach(widget => widget.close()); - this.widgets.forEach(widget => widget.close()); + this.getAllWidgets().forEach((widget) => widget.close()); + this.widgets.forEach((widget) => widget.close()); this.detailPanel.close(); this._linkedViewers = []; @@ -625,10 +725,10 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { _addContextMenuItem(item) { this.customCommands.push(item.id); this.commands.addCommand(item.id, { - execute: args => item.execute({widget: args.widget}), + execute: (args) => item.execute({widget: args.widget}), label: item.label, - isVisible: args => item.isVisible({widget: args.widget}), - mnemonic: 0 + isVisible: (args) => item.isVisible({widget: args.widget}), + mnemonic: 0, }); } @@ -659,7 +759,10 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { const node = this._createNode(slotname); const table = config.table; const viewer = document.createElement("perspective-viewer"); - viewer.setAttribute("slot", node.querySelector("slot").getAttribute("name")); + viewer.setAttribute( + "slot", + node.querySelector("slot").getAttribute("name") + ); if (table) { viewer.setAttribute("table", table); } @@ -703,7 +806,9 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { node = node.parentElement.parentElement; } } - const table = this.tables.get(viewer.getAttribute("table") || config.table); + const table = this.tables.get( + viewer.getAttribute("table") || config.table + ); const widget = new PerspectiveViewerWidget({node, viewer}); widget.task = (async () => { if (table) { @@ -714,7 +819,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { })(); const event = new CustomEvent("workspace-new-view", { - detail: {config, widget} + detail: {config, widget}, }); this.element.dispatchEvent(event); widget.title.closable = true; @@ -727,20 +832,25 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { if (this.listeners.has(widget)) { this.listeners.get(widget)(); } - const settings = event => { + const settings = (event) => { if (event.detail) { widget.title.className += " settings_open"; } else { - widget.title.className = widget.title.className.replace(/settings_open/g, ""); + widget.title.className = widget.title.className.replace( + /settings_open/g, + "" + ); } }; - const contextMenu = event => this.showContextMenu(widget, event); - const updated = async event => { + const contextMenu = (event) => this.showContextMenu(widget, event); + const updated = async (event) => { this.workspaceUpdated(); if (this.mode === MODE.LINKED) { const config = await event.target?.save(); if (config) { - const selectable = this._linkedViewers.length > 0 && !!config["row-pivots"]; + const selectable = + this._linkedViewers.length > 0 && + !!config["row-pivots"]; if (selectable !== !!config.selectable) { event.target.restore({selectable}); } @@ -750,20 +860,35 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { widget.node.addEventListener("contextmenu", contextMenu); widget.viewer.addEventListener("perspective-toggle-settings", settings); widget.viewer.addEventListener("perspective-config-update", updated); - widget.viewer.addEventListener("perspective-plugin-update", this.workspaceUpdated); + widget.viewer.addEventListener( + "perspective-plugin-update", + this.workspaceUpdated + ); widget.title.changed.connect(updated); this.listeners.set(widget, () => { widget.node.removeEventListener("contextmenu", contextMenu); - widget.viewer.removeEventListener("perspective-toggle-settings", settings); - widget.viewer.removeEventListener("perspective-config-update", updated); - widget.viewer.removeEventListener("perspective-plugin-update", this.workspaceUpdated); + widget.viewer.removeEventListener( + "perspective-toggle-settings", + settings + ); + widget.viewer.removeEventListener( + "perspective-config-update", + updated + ); + widget.viewer.removeEventListener( + "perspective-plugin-update", + this.workspaceUpdated + ); widget.title.changed.disconnect(updated); }); } getAllWidgets() { - return [...this.masterPanel.widgets, ...toArray(this.dockpanel.widgets())]; + return [ + ...this.masterPanel.widgets, + ...toArray(this.dockpanel.widgets()), + ]; } /*************************************************************************** @@ -781,7 +906,7 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { }); this.element.dispatchEvent( new CustomEvent("workspace-layout-update", { - detail: {tables, layout} + detail: {tables, layout}, }) ); } @@ -789,7 +914,12 @@ export class PerspectiveWorkspace extends DiscreteSplitPanel { workspaceUpdated = () => { if (!this._save) { - this._save = debounce(() => this.dockpanel.mode !== "single-document" && this._fireUpdateEvent(), 500); + this._save = debounce( + () => + this.dockpanel.mode !== "single-document" && + this._fireUpdateEvent(), + 500 + ); } this._save(); }; diff --git a/packages/perspective-workspace/src/less/dockpanel.less b/packages/perspective-workspace/src/less/dockpanel.less index 6632567dfd..f82991e8a6 100644 --- a/packages/perspective-workspace/src/less/dockpanel.less +++ b/packages/perspective-workspace/src/less/dockpanel.less @@ -21,7 +21,6 @@ } } - .p-DockPanel.ew, .p-DockPanel.ew .p-Widget, .p-SplitPanel.ew, @@ -65,10 +64,11 @@ background-color: none; } -.p-SplitPanel-handle, .p-DockPanel-handle { +.p-SplitPanel-handle, +.p-DockPanel-handle { transition: background-color 0.3s ease-out; &:hover { - background-color: rgba(0,0,0,0.05); + background-color: rgba(0, 0, 0, 0.05); } } diff --git a/packages/perspective-workspace/src/less/menu.less b/packages/perspective-workspace/src/less/menu.less index c217b0d5a5..a0a546b6df 100644 --- a/packages/perspective-workspace/src/less/menu.less +++ b/packages/perspective-workspace/src/less/menu.less @@ -82,13 +82,12 @@ } .p-MenuBar-item.p-mod-active { - background: rgba(0,0,0,0.2); + background: rgba(0, 0, 0, 0.2); } - .p-MenuBar.p-mod-active .p-MenuBar-item.p-mod-active { z-index: 10001; - background: rgba(0,0,0,0.2); + background: rgba(0, 0, 0, 0.2); // border-left: 1px solid #c0c0c0; // border-right: 1px solid #c0c0c0; } diff --git a/packages/perspective-workspace/src/less/tabbar.less b/packages/perspective-workspace/src/less/tabbar.less index f4f6d6a6a9..68567b424f 100644 --- a/packages/perspective-workspace/src/less/tabbar.less +++ b/packages/perspective-workspace/src/less/tabbar.less @@ -22,7 +22,7 @@ .p-TabBar-tabLabel:focus { outline: none; - color: var(--workspace-secondary--color, #666) !important; + color: var(--workspace-secondary--color, #666) !important; cursor: text; } @@ -169,11 +169,14 @@ .p-TabBar-content > .p-TabBar-tab, .p-TabBar-content > .p-TabBar-tab.p-mod-current.settings_open { - background-color: #F2F4F6; + background-color: #f2f4f6; } .p-TabBar-content.inactive-blur > .p-TabBar-tab { - background-color:var(--workspace-inactive-blur--background-color, rgba(0,0,0,0.05)); + background-color: var( + --workspace-inactive-blur--background-color, + rgba(0, 0, 0, 0.05) + ); } .condensed .p-TabBar-content { @@ -248,7 +251,7 @@ } .p-TabBar-content > .p-TabBar-tab .p-TabBar-tabLabel { - color: var(--workspace-secondary--color, #666); + color: var(--workspace-secondary--color, #666); } .p-TabBar-content > .p-TabBar-tab:hover { @@ -256,7 +259,7 @@ } .p-TabBar-content > .p-TabBar-tab.p-mod-current { - color: var(--workspace-secondary--color, #666); + color: var(--workspace-secondary--color, #666); } .p-TabBar-tab.p-mod-current .p-TabBar-tabLabel { diff --git a/packages/perspective-workspace/src/less/widget.less b/packages/perspective-workspace/src/less/widget.less index 59dfa7672f..29fb8327f7 100644 --- a/packages/perspective-workspace/src/less/widget.less +++ b/packages/perspective-workspace/src/less/widget.less @@ -7,7 +7,7 @@ * */ -@border-color: 1px solid #EAEDEF; +@border-color: 1px solid #eaedef; .viewer-container { flex: 1; @@ -19,7 +19,7 @@ .workspace-widget { min-width: 300px; min-height: 200px; - } + } } .workspace-widget { @@ -27,4 +27,4 @@ flex-direction: column; border: @border-color; border-width: 0px !important; -} +} diff --git a/packages/perspective-workspace/src/less/workspace.less b/packages/perspective-workspace/src/less/workspace.less index 7ffb69534a..2f00a6c421 100644 --- a/packages/perspective-workspace/src/less/workspace.less +++ b/packages/perspective-workspace/src/less/workspace.less @@ -13,7 +13,7 @@ @import "./dockpanel.less"; @import "./widget.less"; - background-color: #F2F4F6; + background-color: #f2f4f6; width: 100%; height: 100%; diff --git a/packages/perspective-workspace/test/html/index.html b/packages/perspective-workspace/test/html/index.html index d84f4d90c4..93ac6d9b17 100644 --- a/packages/perspective-workspace/test/html/index.html +++ b/packages/perspective-workspace/test/html/index.html @@ -9,32 +9,30 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/packages/perspective-workspace/test/js/integration/dom.spec.js b/packages/perspective-workspace/test/js/integration/dom.spec.js index d25d50842a..e9fa2abd19 100644 --- a/packages/perspective-workspace/test/js/integration/dom.spec.js +++ b/packages/perspective-workspace/test/js/integration/dom.spec.js @@ -16,7 +16,7 @@ const PATHS = [ path.join(TEST_ROOT, "dist", "theme"), path.join(TEST_ROOT, "test", "html"), path.join(TEST_ROOT, "test", "css"), - path.join(TEST_ROOT, "test", "csv") + path.join(TEST_ROOT, "test", "csv"), ]; utils.with_server({paths: PATHS}, () => { @@ -24,11 +24,24 @@ utils.with_server({paths: PATHS}, () => { "index.html", () => { describe("Light DOM", () => { - tests(page => page.evaluate(async () => document.getElementById("workspace").outerHTML)); + tests((page) => + page.evaluate( + async () => + document.getElementById("workspace").outerHTML + ) + ); }); describe("Shadow DOM", () => { - tests(page => page.evaluate(async () => document.getElementById("workspace").shadowRoot.querySelector("#container").innerHTML)); + tests((page) => + page.evaluate( + async () => + document + .getElementById("workspace") + .shadowRoot.querySelector("#container") + .innerHTML + ) + ); }); }, {root: TEST_ROOT} @@ -37,7 +50,7 @@ utils.with_server({paths: PATHS}, () => { function tests(extract) { describe("removeChild", () => { - test.capture("Remove One", async page => { + test.capture("Remove One", async (page) => { await page.waitForFunction(() => !!window.__TABLE__); await page.evaluate(async () => { const viewer = document.createElement("perspective-viewer"); @@ -55,7 +68,9 @@ function tests(extract) { }); await page.evaluate(async () => { - const viewer = document.body.querySelector('perspective-viewer[name="one"]'); + const viewer = document.body.querySelector( + 'perspective-viewer[name="one"]' + ); const workspace = document.getElementById("workspace"); workspace.removeChild(viewer); await workspace.flush(); diff --git a/packages/perspective-workspace/test/js/integration/html.spec.js b/packages/perspective-workspace/test/js/integration/html.spec.js index a0d7c775cd..24e177efda 100644 --- a/packages/perspective-workspace/test/js/integration/html.spec.js +++ b/packages/perspective-workspace/test/js/integration/html.spec.js @@ -16,7 +16,7 @@ const PATHS = [ path.join(TEST_ROOT, "dist", "theme"), path.join(TEST_ROOT, "test", "html"), path.join(TEST_ROOT, "test", "css"), - path.join(TEST_ROOT, "test", "csv") + path.join(TEST_ROOT, "test", "csv"), ]; utils.with_server({paths: PATHS}, () => { @@ -24,11 +24,25 @@ utils.with_server({paths: PATHS}, () => { "index.html", () => { describe("Light DOM", () => { - tests(page => page.evaluate(async () => document.querySelector("perspective-workspace").outerHTML)); + tests((page) => + page.evaluate( + async () => + document.querySelector("perspective-workspace") + .outerHTML + ) + ); }); describe("Shadow DOM", () => { - tests(page => page.evaluate(async () => document.querySelector("perspective-workspace").shadowRoot.querySelector("#container").innerHTML)); + tests((page) => + page.evaluate( + async () => + document + .querySelector("perspective-workspace") + .shadowRoot.querySelector("#container") + .innerHTML + ) + ); }); }, {root: TEST_ROOT} @@ -36,7 +50,7 @@ utils.with_server({paths: PATHS}, () => { }); function tests(extract) { - test.capture("Create One", async page => { + test.capture("Create One", async (page) => { await page.waitForFunction(() => !!window.__TABLE__); await page.evaluate(async () => { document.body.innerHTML = ` @@ -44,7 +58,9 @@ function tests(extract) { `; - const workspace = document.body.querySelector("perspective-workspace"); + const workspace = document.body.querySelector( + "perspective-workspace" + ); workspace.tables.set("superstore", window.__TABLE__); await workspace.flush(); }); @@ -52,7 +68,7 @@ function tests(extract) { return extract(page); }); - test.capture("Create Multiple", async page => { + test.capture("Create Multiple", async (page) => { await page.evaluate(async () => { document.body.innerHTML = ` @@ -60,7 +76,9 @@ function tests(extract) { `; - const workspace = document.body.querySelector("perspective-workspace"); + const workspace = document.body.querySelector( + "perspective-workspace" + ); workspace.tables.set("superstore", window.__TABLE__); await workspace.flush(); }); @@ -68,7 +86,7 @@ function tests(extract) { return extract(page); }); - test.capture("Create multiple with names", async page => { + test.capture("Create multiple with names", async (page) => { await page.evaluate(async () => { document.body.innerHTML = ` @@ -76,7 +94,9 @@ function tests(extract) { `; - const workspace = document.body.querySelector("perspective-workspace"); + const workspace = document.body.querySelector( + "perspective-workspace" + ); workspace.tables.set("superstore", window.__TABLE__); await workspace.flush(); }); diff --git a/packages/perspective-workspace/test/js/integration/restore.spec.js b/packages/perspective-workspace/test/js/integration/restore.spec.js index b1874840a6..e599e13848 100644 --- a/packages/perspective-workspace/test/js/integration/restore.spec.js +++ b/packages/perspective-workspace/test/js/integration/restore.spec.js @@ -16,26 +16,26 @@ const PATHS = [ path.join(TEST_ROOT, "dist", "theme"), path.join(TEST_ROOT, "test", "html"), path.join(TEST_ROOT, "test", "css"), - path.join(TEST_ROOT, "test", "csv") + path.join(TEST_ROOT, "test", "csv"), ]; function tests(extract) { - test.capture("restore workspace with detail only", async page => { + test.capture("restore workspace with detail only", async (page) => { await page.waitForFunction(() => !!window.__TABLE__); const config = { viewers: { - One: {table: "superstore", name: "One"} + One: {table: "superstore", name: "One"}, }, detail: { main: { currentIndex: 0, type: "tab-area", - widgets: ["One"] - } - } + widgets: ["One"], + }, + }, }; - await page.evaluate(async config => { + await page.evaluate(async (config) => { const workspace = document.getElementById("workspace"); await workspace.restore(config); }, config); @@ -43,25 +43,30 @@ function tests(extract) { return extract(page); }); - test.capture("restore workspace with master and detail", async page => { + test.capture("restore workspace with master and detail", async (page) => { const config = { viewers: { - One: {table: "superstore", name: "Test", row_pivots: ["State"], columns: ["Sales", "Profit"]}, - Two: {table: "superstore", name: "One"} + One: { + table: "superstore", + name: "Test", + row_pivots: ["State"], + columns: ["Sales", "Profit"], + }, + Two: {table: "superstore", name: "One"}, }, master: { - widgets: ["One"] + widgets: ["One"], }, detail: { main: { currentIndex: 0, type: "tab-area", - widgets: ["Two"] - } - } + widgets: ["Two"], + }, + }, }; - await page.evaluate(async config => { + await page.evaluate(async (config) => { const workspace = document.getElementById("workspace"); await workspace.restore(config); }, config); @@ -69,33 +74,42 @@ function tests(extract) { return extract(page); }); - test.capture("restore workspace with viewers with generated slotids", async page => { - const config = { - viewers: { - PERSPECTIVE_GENERATED_ID_0: {table: "superstore", name: "Test", row_pivots: ["State"], columns: ["Sales", "Profit"]} - }, - detail: { - main: { - currentIndex: 0, - type: "tab-area", - widgets: ["PERSPECTIVE_GENERATED_ID_0"] - } - } - }; + test.capture( + "restore workspace with viewers with generated slotids", + async (page) => { + const config = { + viewers: { + PERSPECTIVE_GENERATED_ID_0: { + table: "superstore", + name: "Test", + row_pivots: ["State"], + columns: ["Sales", "Profit"], + }, + }, + detail: { + main: { + currentIndex: 0, + type: "tab-area", + widgets: ["PERSPECTIVE_GENERATED_ID_0"], + }, + }, + }; - await page.evaluate(async config => { - const workspace = document.getElementById("workspace"); - await workspace.restore(config); - }, config); + await page.evaluate(async (config) => { + const workspace = document.getElementById("workspace"); + await workspace.restore(config); + }, config); - await page.evaluate(async () => { - const workspace = document.getElementById("workspace").workspace; - const widget = workspace.getAllWidgets()[0]; - await workspace.duplicate(widget); - }); + await page.evaluate(async () => { + const workspace = + document.getElementById("workspace").workspace; + const widget = workspace.getAllWidgets()[0]; + await workspace.duplicate(widget); + }); - return extract(page); - }); + return extract(page); + } + ); } utils.with_server({paths: PATHS}, () => { @@ -103,11 +117,24 @@ utils.with_server({paths: PATHS}, () => { "index.html", () => { describe("Light DOM", () => { - tests(page => page.evaluate(async () => document.getElementById("workspace").outerHTML)); + tests((page) => + page.evaluate( + async () => + document.getElementById("workspace").outerHTML + ) + ); }); describe("Shadow DOM", () => { - tests(page => page.evaluate(async () => document.getElementById("workspace").shadowRoot.querySelector("#container").innerHTML)); + tests((page) => + page.evaluate( + async () => + document + .getElementById("workspace") + .shadowRoot.querySelector("#container") + .innerHTML + ) + ); }); }, {root: TEST_ROOT} diff --git a/packages/perspective-workspace/test/js/unit/__mocks__/@finos/perspective-viewer.js b/packages/perspective-workspace/test/js/unit/__mocks__/@finos/perspective-viewer.js index fd6473253d..3be07617c4 100644 --- a/packages/perspective-workspace/test/js/unit/__mocks__/@finos/perspective-viewer.js +++ b/packages/perspective-workspace/test/js/unit/__mocks__/@finos/perspective-viewer.js @@ -9,16 +9,18 @@ const originalCreateElement = document.createElement; -document.createElement = name => { +document.createElement = (name) => { const element = originalCreateElement.call(document, name); - return name === "perspective-viewer" ? patchUnknownElement(element) : element; + return name === "perspective-viewer" + ? patchUnknownElement(element) + : element; }; -const patchUnknownElement = element => { +const patchUnknownElement = (element) => { let config = {}; element.load = jest.fn(); element.save = async () => config; - element.restore = value => { + element.restore = (value) => { config = {...config, ...value}; return Promise.resolve(); }; diff --git a/packages/perspective-workspace/test/js/unit/tables.spec.js b/packages/perspective-workspace/test/js/unit/tables.spec.js index 7af740b28f..4837883ee3 100644 --- a/packages/perspective-workspace/test/js/unit/tables.spec.js +++ b/packages/perspective-workspace/test/js/unit/tables.spec.js @@ -20,9 +20,9 @@ describe("tables", () => { main: { currentIndex: 0, type: "tab-area", - widgets: ["One"] - } - } + widgets: ["One"], + }, + }, }; const workspace = new PerspectiveWorkspace(document.body); @@ -56,9 +56,9 @@ describe("tables", () => { main: { currentIndex: 0, type: "tab-area", - widgets: ["One"] - } - } + widgets: ["One"], + }, + }, }; const workspace = new PerspectiveWorkspace(document.body); diff --git a/packages/perspective-workspace/test/js/unit/workspace.spec.js b/packages/perspective-workspace/test/js/unit/workspace.spec.js index 998dc854ed..a3fc206405 100644 --- a/packages/perspective-workspace/test/js/unit/workspace.spec.js +++ b/packages/perspective-workspace/test/js/unit/workspace.spec.js @@ -19,9 +19,9 @@ describe("workspace", () => { main: { currentIndex: 0, type: "tab-area", - widgets: ["One"] - } - } + widgets: ["One"], + }, + }, }; const workspace = new PerspectiveWorkspace(document.body); @@ -29,7 +29,12 @@ describe("workspace", () => { const widgets = toArray(workspace.dockpanel.widgets()); - const expected = {table: "superstore", name: "One", master: false, linked: false}; + const expected = { + table: "superstore", + name: "One", + master: false, + linked: false, + }; expect(widgets.length).toBe(1); expect(await widgets[0].save()).toStrictEqual(expected); }); @@ -39,8 +44,8 @@ describe("workspace", () => { const config = { viewers, master: { - widgets: ["One"] - } + widgets: ["One"], + }, }; const workspace = new PerspectiveWorkspace(document.body); @@ -48,25 +53,33 @@ describe("workspace", () => { const widgets = workspace.masterPanel.widgets; - const expected = {table: "superstore", name: "One", master: true, linked: false}; + const expected = { + table: "superstore", + name: "One", + master: true, + linked: false, + }; expect(widgets.length).toBe(1); expect(await widgets[0].save()).toStrictEqual(expected); }); test("restores master to masterpanel and detail to dockpanel", async () => { - const viewers = {One: {table: "superstore", name: "One"}, Two: {table: "superstore", name: "Two"}}; + const viewers = { + One: {table: "superstore", name: "One"}, + Two: {table: "superstore", name: "Two"}, + }; const config = { viewers, master: { - widgets: ["One"] + widgets: ["One"], }, detail: { main: { currentIndex: 0, type: "tab-area", - widgets: ["Two"] - } - } + widgets: ["Two"], + }, + }, }; const workspace = new PerspectiveWorkspace(document.body); @@ -75,8 +88,18 @@ describe("workspace", () => { const masterWidgets = workspace.masterPanel.widgets; const detailWidgets = toArray(workspace.dockpanel.widgets()); - const master = {table: "superstore", name: "One", master: true, linked: false}; - const detail = {table: "superstore", name: "Two", master: false, linked: false}; + const master = { + table: "superstore", + name: "One", + master: true, + linked: false, + }; + const detail = { + table: "superstore", + name: "Two", + master: false, + linked: false, + }; expect(masterWidgets.length).toBe(1); expect(await masterWidgets[0].save()).toStrictEqual(master); diff --git a/packages/perspective/babel.config.js b/packages/perspective/babel.config.js index 9878e5b6f5..4b0e3621ed 100644 --- a/packages/perspective/babel.config.js +++ b/packages/perspective/babel.config.js @@ -6,11 +6,11 @@ module.exports = { targets: { chrome: "74", node: "12", - ios: "13" + ios: "13", }, - modules: process.env.BABEL_MODULE || false - } - ] + modules: process.env.BABEL_MODULE || false, + }, + ], ], sourceType: "unambiguous", plugins: [ @@ -18,6 +18,6 @@ module.exports = { ["@babel/plugin-proposal-decorators", {legacy: true}], "transform-custom-element-classes", "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-optional-chaining" - ] + "@babel/plugin-proposal-optional-chaining", + ], }; diff --git a/packages/perspective/src/config/common.config.js b/packages/perspective/src/config/common.config.js index a3790fbf6e..7dbcd700a6 100644 --- a/packages/perspective/src/config/common.config.js +++ b/packages/perspective/src/config/common.config.js @@ -5,7 +5,10 @@ const plugins = []; function common({no_minify, inline} = {}) { return { - mode: process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG || no_minify ? "development" : process.env.NODE_ENV || "production", + mode: + process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG || no_minify + ? "development" + : process.env.NODE_ENV || "production", plugins: plugins, module: { rules: [ @@ -26,34 +29,34 @@ function common({no_minify, inline} = {}) { minimize: true, plugins: [ cssnano({ - preset: "lite" - }) - ] - } - } + preset: "lite", + }), + ], + }, + }, }, - {loader: "less-loader"} - ] + {loader: "less-loader"}, + ], }, { test: /\.(html)$/, use: { loader: "html-loader", - options: {} - } + options: {}, + }, }, { test: /\.js$/, exclude: /node_modules\/(?!regular-table)/, - loader: "source-map-loader" + loader: "source-map-loader", }, { test: /\.(arrow)$/, type: "javascript/auto", use: { loader: "arraybuffer-loader", - options: {} - } + options: {}, + }, }, { test: /\.css$/, @@ -69,52 +72,52 @@ function common({no_minify, inline} = {}) { plugins: [ cssnano({ preset: "lite", - discardComments: {removeAll: true} - }) - ] - } - } - } - ] + discardComments: {removeAll: true}, + }), + ], + }, + }, + }, + ], }, { test: /\.ttf$/, - use: ["file-loader"] + use: ["file-loader"], }, inline ? { test: /\.wasm$/, type: "javascript/auto", - loader: "arraybuffer-loader" + loader: "arraybuffer-loader", } : { test: /\.wasm$/, type: "javascript/auto", loader: "file-loader", - options: {name: "[name].[ext]"} + options: {name: "[name].[ext]"}, }, { test: /perspective\.worker\.js$/, type: "javascript/auto", loader: "worker-loader", options: { - inline: "no-fallback" - } + inline: "no-fallback", + }, }, { test: /editor\.worker/, type: "javascript/auto", loader: "worker-loader", options: { - inline: "no-fallback" - } - } - ] + inline: "no-fallback", + }, + }, + ], }, resolve: { fallback: { - crypto: false - } + crypto: false, + }, }, devtool: "source-map", optimization: { @@ -122,29 +125,37 @@ function common({no_minify, inline} = {}) { new TerserPlugin({ terserOptions: { output: { - ascii_only: true - } - } - }) - ] + ascii_only: true, + }, + }, + }), + ], }, performance: { hints: false, maxEntrypointSize: 512000, - maxAssetSize: 512000 + maxAssetSize: 512000, + }, + stats: { + modules: false, + hash: false, + version: false, + builtAt: false, + entrypoints: false, }, - stats: {modules: false, hash: false, version: false, builtAt: false, entrypoints: false} }; } // Remove absolute paths from webpack source-maps const ABS_PATH = path.resolve(__dirname, "..", "..", "..", ".."); -const devtoolModuleFilenameTemplate = info => `webpack:///${path.relative(ABS_PATH, info.absoluteResourcePath)}`; +const devtoolModuleFilenameTemplate = (info) => + `webpack:///${path.relative(ABS_PATH, info.absoluteResourcePath)}`; module.exports = (options, f) => { let new_config = Object.assign({}, common(options)); new_config = f(new_config); - new_config.output.devtoolModuleFilenameTemplate = devtoolModuleFilenameTemplate; + new_config.output.devtoolModuleFilenameTemplate = + devtoolModuleFilenameTemplate; return new_config; }; diff --git a/packages/perspective/src/config/perspective.config.js b/packages/perspective/src/config/perspective.config.js index c1a703d764..071757f624 100644 --- a/packages/perspective/src/config/perspective.config.js +++ b/packages/perspective/src/config/perspective.config.js @@ -1,7 +1,7 @@ const path = require("path"); const common = require("./common.config.js"); -module.exports = common({}, config => +module.exports = common({}, (config) => Object.assign(config, { entry: "./dist/esm/perspective.parallel.js", output: { @@ -10,7 +10,7 @@ module.exports = common({}, config => libraryTarget: "umd", libraryExport: "default", chunkFilename: "perspective.chunk_[id].js", - path: path.resolve(__dirname, "../../dist/umd") - } + path: path.resolve(__dirname, "../../dist/umd"), + }, }) ); diff --git a/packages/perspective/src/config/perspective.inline.config.js b/packages/perspective/src/config/perspective.inline.config.js index ebc0e31498..3c3bd1af4e 100644 --- a/packages/perspective/src/config/perspective.inline.config.js +++ b/packages/perspective/src/config/perspective.inline.config.js @@ -1,7 +1,7 @@ const path = require("path"); const common = require("./common.config.js"); -module.exports = common({inline: true}, config => +module.exports = common({inline: true}, (config) => Object.assign(config, { entry: "./dist/esm/perspective.parallel.js", devtool: undefined, @@ -11,7 +11,7 @@ module.exports = common({inline: true}, config => libraryTarget: "umd", libraryExport: "default", chunkFilename: "perspective.inline.chunk_[id].js", - path: path.resolve(__dirname, "../../dist/umd") - } + path: path.resolve(__dirname, "../../dist/umd"), + }, }) ); diff --git a/packages/perspective/src/js/api/client.js b/packages/perspective/src/js/api/client.js index a12017af55..2f8d8cd6f3 100644 --- a/packages/perspective/src/js/api/client.js +++ b/packages/perspective/src/js/api/client.js @@ -27,7 +27,7 @@ export class Client { transferable: false, msg_id: 0, handlers: {}, - messages: [] + messages: [], }; bindall(this); } @@ -49,7 +49,11 @@ export class Client { post(msg, resolve, reject, keep_alive = false) { ++this._worker.msg_id; if (resolve || reject) { - this._worker.handlers[this._worker.msg_id] = {resolve, reject, keep_alive}; + this._worker.handlers[this._worker.msg_id] = { + resolve, + reject, + keep_alive, + }; } msg.id = this._worker.msg_id; if (this._worker.initialized.value) { @@ -58,7 +62,11 @@ export class Client { this._worker.messages.push(() => { this.send(msg); - if ((msg.cmd === "table" || msg.cmd === "view") && !this._features?.wait_for_response && resolve) { + if ( + (msg.cmd === "table" || msg.cmd === "view") && + !this._features?.wait_for_response && + resolve + ) { resolve(); } }); @@ -81,7 +89,9 @@ export class Client { if (this._worker.initialized.value) { this.send({id: -1, cmd: "init_profile_thread"}); } else { - this._worker.messages.push(() => this.send({id: -1, cmd: "init_profile_thread"})); + this._worker.messages.push(() => + this.send({id: -1, cmd: "init_profile_thread"}) + ); } } @@ -110,7 +120,13 @@ export class Client { */ _handle(e) { if (!this._worker.initialized.value) { - if (!this._initialized && typeof document !== "undefined" && document && typeof window !== undefined && window) { + if ( + !this._initialized && + typeof document !== "undefined" && + document && + typeof window !== undefined && + window + ) { try { const event = document.createEvent("Event"); event.initEvent("perspective-ready", false, true); diff --git a/packages/perspective/src/js/api/dispatch.js b/packages/perspective/src/js/api/dispatch.js index 6ead5b9cf9..948fbe49b9 100644 --- a/packages/perspective/src/js/api/dispatch.js +++ b/packages/perspective/src/js/api/dispatch.js @@ -17,7 +17,7 @@ let __CALLBACK_INDEX__ = 0; * @param {*} cmd */ export function unsubscribe(method, cmd) { - return function() { + return function () { let resolve; let reject = () => {}; let args = Array.prototype.slice.call(arguments, 0, arguments.length); @@ -34,7 +34,7 @@ export function unsubscribe(method, cmd) { method: method, args: args, subscribe: true, - callback_id + callback_id, }; this._worker.post(msg, resolve, reject); this._worker.unsubscribe(cmd, resolve); @@ -48,7 +48,7 @@ export function unsubscribe(method, cmd) { * @param {*} cmd */ export function subscribe(method, cmd) { - return function() { + return function () { let resolve; let reject = () => {}; let args = Array.prototype.slice.call(arguments, 0, arguments.length); @@ -65,7 +65,7 @@ export function subscribe(method, cmd) { method: method, args: args, subscribe: true, - callback_id: __CALLBACK_INDEX__ + callback_id: __CALLBACK_INDEX__, }; this._worker.post(msg, resolve, reject, true); }; @@ -80,16 +80,16 @@ export function subscribe(method, cmd) { * @param {*} cmd */ export function async_queue(method, cmd) { - return function() { + return function () { var args = Array.prototype.slice.call(arguments, 0, arguments.length); return new Promise( - function(resolve, reject) { + function (resolve, reject) { var msg = { cmd: cmd || "view_method", name: this._name, method: method, args: args, - subscribe: false + subscribe: false, }; this._worker.post(msg, resolve, reject); }.bind(this) diff --git a/packages/perspective/src/js/api/server.js b/packages/perspective/src/js/api/server.js index fbf036ce34..689c945db8 100644 --- a/packages/perspective/src/js/api/server.js +++ b/packages/perspective/src/js/api/server.js @@ -12,7 +12,7 @@ import {override_config} from "../config"; function error_to_json(error) { const obj = {}; if (typeof error !== "string") { - Object.getOwnPropertyNames(error).forEach(key => { + Object.getOwnPropertyNames(error).forEach((key) => { obj[key] = error[key]; }, error); } else { @@ -88,7 +88,7 @@ export class Server { case "memory_usage": this.post({ id: msg.id, - data: this.perspective.memory_usage() + data: this.perspective.memory_usage(), }); break; case "init": @@ -104,14 +104,17 @@ export class Server { } else { try { const msgs = this._tables[msg.name]; - const table = this.perspective.table(msg.args[0], msg.options); + const table = this.perspective.table( + msg.args[0], + msg.options + ); // When using the Node server, the `table()` constructor // returns a Promise, but in the Web Worker version, // table() synchronously returns an instance of a Table. if (table && table.then) { table - .then(table => { + .then((table) => { this._tables[msg.name] = table; // Process cached messages for this table. @@ -124,10 +127,12 @@ export class Server { // Resolve the promise to return a Table. this.post({ id: msg.id, - data: msg.name + data: msg.name, }); }) - .catch(error => this.process_error(msg, error)); + .catch((error) => + this.process_error(msg, error) + ); } else { this._tables[msg.name] = table; @@ -141,7 +146,7 @@ export class Server { // Resolve the promise to return a Table. this.post({ id: msg.id, - data: msg.name + data: msg.name, }); } } catch (error) { @@ -153,11 +158,11 @@ export class Server { case "table_generate": let g; eval("g = " + msg.args); - g(function(tbl) { + g(function (tbl) { this._tables[msg.name] = tbl; this.post({ id: msg.id, - data: "created!" + data: "created!", }); }); break; @@ -189,12 +194,15 @@ export class Server { // When using the Node server, the `view()` constructor // returns a Promise, but in the Web Worker version, // view() synchronously returns an instance of a View. - const view = this._tables[msg.table_name].view(msg.config); + const view = this._tables[msg.table_name].view( + msg.config + ); if (view && view.then) { - view.then(view => { + view.then((view) => { this._views[msg.view_name] = view; - this._views[msg.view_name].client_id = client_id; + this._views[msg.view_name].client_id = + client_id; // Process cached messages for the view. if (msgs) { @@ -205,9 +213,9 @@ export class Server { this.post({ id: msg.id, - data: msg.view_name + data: msg.view_name, }); - }).catch(error => this.process_error(msg, error)); + }).catch((error) => this.process_error(msg, error)); } else { this._views[msg.view_name] = view; this._views[msg.view_name].client_id = client_id; @@ -221,7 +229,7 @@ export class Server { this.post({ id: msg.id, - data: msg.view_name + data: msg.view_name, }); } } catch (error) { @@ -241,15 +249,18 @@ export class Server { try { let callback; if (msg.method.slice(0, 2) === "on") { - callback = ev => { + callback = (ev) => { let result = { id: msg.id, - data: ev + data: ev, }; try { // post transferable data for arrow if (msg.args && msg.args[0]) { - if (msg.method === "on_update" && msg.args[0]["mode"] === "row") { + if ( + msg.method === "on_update" && + msg.args[0]["mode"] === "row" + ) { // actual arrow is in the `delta` this.post(result, [ev.delta]); return; @@ -258,7 +269,9 @@ export class Server { this.post(result); } catch (e) { - console.error(`Removing failed callback to \`${msg.method}()\` (presumably due to failed connection)`); + console.error( + `Removing failed callback to \`${msg.method}()\` (presumably due to failed connection)` + ); const remove_method = msg.method.substring(3); obj[`remove_${remove_method}`](callback); } @@ -273,7 +286,11 @@ export class Server { if (callback) { obj[msg.method](callback, ...msg.args); } else { - console.error(`Callback not found for remote call "${JSON.stringify(msg)}"`); + console.error( + `Callback not found for remote call "${JSON.stringify( + msg + )}"` + ); } } catch (error) { this.process_error(msg, error); @@ -291,7 +308,9 @@ export class Server { process_method_call(msg) { let obj, result; const name = msg.view_name || msg.name; - msg.cmd === "table_method" ? (obj = this._tables[name]) : (obj = this._views[name]); + msg.cmd === "table_method" + ? (obj = this._tables[name]) + : (obj = this._views[name]); if (!obj && msg.cmd === "view_method") { // cannot have a host without a table, but can have a host without a @@ -312,7 +331,11 @@ export class Server { } else { result = obj[msg.method].apply(obj, msg.args); if (result instanceof Promise) { - result.then(result => this.process_method_call_response(msg, result)).catch(error => this.process_error(msg, error)); + result + .then((result) => + this.process_method_call_response(msg, result) + ) + .catch((error) => this.process_error(msg, error)); } else { this.process_method_call_response(msg, result); } @@ -337,14 +360,14 @@ export class Server { this.post( { id: msg.id, - data: result + data: result, }, [result] ); } else { this.post({ id: msg.id, - data: result + data: result, }); } } @@ -355,7 +378,7 @@ export class Server { process_error(msg, error) { this.post({ id: msg.id, - error: error_to_json(error) + error: error_to_json(error), }); } diff --git a/packages/perspective/src/js/api/table_api.js b/packages/perspective/src/js/api/table_api.js index 9dcca86e9c..a5f5039621 100644 --- a/packages/perspective/src/js/api/table_api.js +++ b/packages/perspective/src/js/api/table_api.js @@ -31,19 +31,19 @@ export function table(worker, data, options) { cmd: "table", name: this._name, args: [], - options: options || {} + options: options || {}, }); - data.to_arrow().then(arrow => { + data.to_arrow().then((arrow) => { this._worker.post( { cmd: "table", name: this._name, args: [arrow], - options: options || {} + options: options || {}, }, () => { data.on_update( - updated => { + (updated) => { this.update(updated.delta); }, {mode: "row"} @@ -59,7 +59,7 @@ export function table(worker, data, options) { cmd: "table", name: this._name, args: [data], - options: options || {} + options: options || {}, }, () => { resolve(this); @@ -68,7 +68,10 @@ export function table(worker, data, options) { ); } - if (this._worker._initialized === true && !this._worker._features?.wait_for_response) { + if ( + this._worker._initialized === true && + !this._worker._features?.wait_for_response + ) { resolve(this); } }); @@ -91,7 +94,7 @@ export function proxy_table(worker, name) { proxy_table.prototype = table.prototype; // Dispatch table methods that create new objects to the worker -table.prototype.view = function(config) { +table.prototype.view = function (config) { return new view(this._worker, this._name, config); }; @@ -107,9 +110,15 @@ table.prototype.remove_port = async_queue("remove_port", "table_method"); table.prototype.schema = async_queue("schema", "table_method"); -table.prototype.validate_expressions = async_queue("validate_expressions", "table_method"); +table.prototype.validate_expressions = async_queue( + "validate_expressions", + "table_method" +); -table.prototype.is_valid_filter = async_queue("is_valid_filter", "table_method"); +table.prototype.is_valid_filter = async_queue( + "is_valid_filter", + "table_method" +); table.prototype.size = async_queue("size", "table_method"); @@ -125,16 +134,20 @@ table.prototype.on_delete = subscribe("on_delete", "table_method", true); table.prototype.remove = async_queue("remove", "table_method"); -table.prototype.remove_delete = unsubscribe("remove_delete", "table_method", true); +table.prototype.remove_delete = unsubscribe( + "remove_delete", + "table_method", + true +); -table.prototype.update = function(data, options) { +table.prototype.update = function (data, options) { return new Promise((resolve, reject) => { this._worker.post( { name: this._name, cmd: "table_method", method: "update", - args: [data, options || {}] + args: [data, options || {}], }, resolve, reject, @@ -143,10 +156,10 @@ table.prototype.update = function(data, options) { }); }; -table.prototype.execute = function(f) { +table.prototype.execute = function (f) { this._worker.post({ cmd: "table_execute", name: this._name, - f: f.toString() + f: f.toString(), }); }; diff --git a/packages/perspective/src/js/api/view_api.js b/packages/perspective/src/js/api/view_api.js index 17c1572d72..1b3f9f05c9 100644 --- a/packages/perspective/src/js/api/view_api.js +++ b/packages/perspective/src/js/api/view_api.js @@ -27,7 +27,7 @@ export function view(worker, table_name, config) { cmd: "view", view_name: this._name, table_name: table_name, - config: config + config: config, }, () => { // Resolve the Promise with this function, which is a proxy @@ -41,7 +41,10 @@ export function view(worker, table_name, config) { reject ); - if (this._worker._initialized === true && !this._worker._features?.wait_for_response) { + if ( + this._worker._initialized === true && + !this._worker._features?.wait_for_response + ) { resolve(this); } }); @@ -99,8 +102,16 @@ view.prototype.col_to_js_typed_array = async_queue("col_to_js_typed_array"); view.prototype.on_update = subscribe("on_update", "view_method", true); -view.prototype.remove_update = unsubscribe("remove_update", "view_method", true); +view.prototype.remove_update = unsubscribe( + "remove_update", + "view_method", + true +); view.prototype.on_delete = subscribe("on_delete", "view_method", true); -view.prototype.remove_delete = unsubscribe("remove_delete", "view_method", true); +view.prototype.remove_delete = unsubscribe( + "remove_delete", + "view_method", + true +); diff --git a/packages/perspective/src/js/config/constants.js b/packages/perspective/src/js/config/constants.js index 866fe27b9e..e2e898de57 100644 --- a/packages/perspective/src/js/config/constants.js +++ b/packages/perspective/src/js/config/constants.js @@ -14,7 +14,7 @@ export const DATA_TYPES = { boolean: "boolean", date: "date", datetime: "datetime", - object: "object" + object: "object", }; export const CONFIG_ALIASES = { @@ -27,10 +27,22 @@ export const CONFIG_ALIASES = { "column-pivot": "column_pivots", "column-pivots": "column_pivots", filters: "filter", - sorts: "sort" + sorts: "sort", }; -export const CONFIG_VALID_KEYS = ["viewport", "row_pivots", "column_pivots", "aggregates", "columns", "filter", "sort", "computed_columns", "expressions", "row_pivot_depth", "filter_op"]; +export const CONFIG_VALID_KEYS = [ + "viewport", + "row_pivots", + "column_pivots", + "aggregates", + "columns", + "filter", + "sort", + "computed_columns", + "expressions", + "row_pivot_depth", + "filter_op", +]; const NUMBER_AGGREGATES = [ "any", @@ -54,14 +66,45 @@ const NUMBER_AGGREGATES = [ "sum abs", "sum not null", "unique", - "var" + "var", ]; -const STRING_AGGREGATES = ["any", "count", "distinct count", "distinct leaf", "dominant", "first by index", "join", "last by index", "last", "unique"]; +const STRING_AGGREGATES = [ + "any", + "count", + "distinct count", + "distinct leaf", + "dominant", + "first by index", + "join", + "last by index", + "last", + "unique", +]; -const BOOLEAN_AGGREGATES = ["any", "count", "distinct count", "distinct leaf", "dominant", "first by index", "last by index", "last", "unique"]; +const BOOLEAN_AGGREGATES = [ + "any", + "count", + "distinct count", + "distinct leaf", + "dominant", + "first by index", + "last by index", + "last", + "unique", +]; -export const SORT_ORDERS = ["none", "asc", "desc", "col asc", "col desc", "asc abs", "desc abs", "col asc abs", "col desc abs"]; +export const SORT_ORDERS = [ + "none", + "asc", + "desc", + "col asc", + "col desc", + "asc abs", + "desc abs", + "col asc abs", + "col desc abs", +]; export const SORT_ORDER_IDS = [2, 0, 1, 0, 1, 3, 4, 3, 4]; @@ -71,7 +114,7 @@ export const TYPE_AGGREGATES = { integer: NUMBER_AGGREGATES, boolean: BOOLEAN_AGGREGATES, datetime: STRING_AGGREGATES, - date: STRING_AGGREGATES + date: STRING_AGGREGATES, }; export const FILTER_OPERATORS = { @@ -91,7 +134,7 @@ export const FILTER_OPERATORS = { and: "and", or: "or", beginsWith: "begins with", - endsWith: "ends with" + endsWith: "ends with", }; const BOOLEAN_FILTERS = [ @@ -102,7 +145,7 @@ const BOOLEAN_FILTERS = [ FILTER_OPERATORS.or, FILTER_OPERATORS.and, FILTER_OPERATORS.isNull, - FILTER_OPERATORS.isNotNull + FILTER_OPERATORS.isNotNull, ]; const NUMBER_FILTERS = [ @@ -113,7 +156,7 @@ const NUMBER_FILTERS = [ FILTER_OPERATORS.greaterThanOrEquals, FILTER_OPERATORS.doesNotEqual, FILTER_OPERATORS.isNull, - FILTER_OPERATORS.isNotNull + FILTER_OPERATORS.isNotNull, ]; const STRING_FILTERS = [ @@ -125,7 +168,7 @@ const STRING_FILTERS = [ FILTER_OPERATORS.beginsWith, FILTER_OPERATORS.endsWith, FILTER_OPERATORS.isNull, - FILTER_OPERATORS.isNotNull + FILTER_OPERATORS.isNotNull, ]; const DATETIME_FILTERS = [ @@ -136,7 +179,7 @@ const DATETIME_FILTERS = [ FILTER_OPERATORS.greaterThanOrEquals, FILTER_OPERATORS.doesNotEqual, FILTER_OPERATORS.isNull, - FILTER_OPERATORS.isNotNull + FILTER_OPERATORS.isNotNull, ]; export const COLUMN_SEPARATOR_STRING = "|"; @@ -147,5 +190,5 @@ export const TYPE_FILTERS = { integer: NUMBER_FILTERS, boolean: BOOLEAN_FILTERS, datetime: DATETIME_FILTERS, - date: DATETIME_FILTERS + date: DATETIME_FILTERS, }; diff --git a/packages/perspective/src/js/config/index.js b/packages/perspective/src/js/config/index.js index 26e8bbdd36..9c9da4914e 100644 --- a/packages/perspective/src/js/config/index.js +++ b/packages/perspective/src/js/config/index.js @@ -9,11 +9,11 @@ const DEFAULT_CONFIG = require("./settings.js").default; -module.exports.get_types = function() { +module.exports.get_types = function () { return Object.keys(module.exports.get_config().types); }; -module.exports.get_type_config = function(type) { +module.exports.get_type_config = function (type) { const config = {}; if (module.exports.get_config().types[type]) { Object.assign(config, module.exports.get_config().types[type]); @@ -51,7 +51,7 @@ function mergeDeep(target, ...sources) { global.__PERSPECTIVE_CONFIG__ = undefined; -module.exports.override_config = function(config) { +module.exports.override_config = function (config) { if (global.__PERSPECTIVE_CONFIG__) { console.warn("Config already initialized!"); } @@ -60,7 +60,10 @@ module.exports.override_config = function(config) { module.exports.get_config = function get_config() { if (!global.__PERSPECTIVE_CONFIG__) { - global.__PERSPECTIVE_CONFIG__ = mergeDeep(DEFAULT_CONFIG, global.__TEMPLATE_CONFIG__ || {}); + global.__PERSPECTIVE_CONFIG__ = mergeDeep( + DEFAULT_CONFIG, + global.__TEMPLATE_CONFIG__ || {} + ); } return global.__PERSPECTIVE_CONFIG__; }; diff --git a/packages/perspective/src/js/config/settings.js b/packages/perspective/src/js/config/settings.js index f068203fdf..69d7204ca5 100644 --- a/packages/perspective/src/js/config/settings.js +++ b/packages/perspective/src/js/config/settings.js @@ -38,21 +38,21 @@ module.exports.default = { format: { style: "decimal", minimumFractionDigits: 2, - maximumFractionDigits: 2 - } + maximumFractionDigits: 2, + }, }, string: { filter_operator: "==", - aggregate: "count" + aggregate: "count", }, integer: { filter_operator: "==", aggregate: "sum", - format: {} + format: {}, }, boolean: { filter_operator: "==", - aggregate: "count" + aggregate: "count", }, datetime: { filter_operator: "==", @@ -64,9 +64,9 @@ module.exports.default = { day: "numeric", hour: "numeric", minute: "numeric", - second: "numeric" + second: "numeric", }, - null_value: -1 + null_value: -1, }, date: { filter_operator: "==", @@ -75,9 +75,9 @@ module.exports.default = { week: "numeric", year: "numeric", month: "numeric", - day: "numeric" + day: "numeric", }, - null_value: -1 - } - } + null_value: -1, + }, + }, }; diff --git a/packages/perspective/src/js/data_accessor/index.js b/packages/perspective/src/js/data_accessor/index.js index 7fe8bc97b0..3e4006bf12 100644 --- a/packages/perspective/src/js/data_accessor/index.js +++ b/packages/perspective/src/js/data_accessor/index.js @@ -15,7 +15,7 @@ export class DataAccessor { this.data_formats = { row: 0, column: 1, - schema: 2 + schema: 2, }; this.format = undefined; this.data = undefined; @@ -29,10 +29,15 @@ export class DataAccessor { return this.data_formats.row; } else if (Array.isArray(data[Object.keys(data)[0]])) { return this.data_formats.column; - } else if (typeof data[Object.keys(data)[0]] === "string" || typeof data[Object.keys(data)[0]] === "function") { + } else if ( + typeof data[Object.keys(data)[0]] === "string" || + typeof data[Object.keys(data)[0]] === "function" + ) { return this.data_formats.schema; } else { - throw `Could not determine data format for ${JSON.stringify(data)}, with JS typeof ${typeof data}`; + throw `Could not determine data format for ${JSON.stringify( + data + )}, with JS typeof ${typeof data}`; } } @@ -152,7 +157,9 @@ export class DataAccessor { for (const name of this.names) { const new_type = get_type_config(data[name]); if (new_type.type) { - console.debug(`Converting "${data[name]}" to "${new_type.type}"`); + console.debug( + `Converting "${data[name]}" to "${new_type.type}"` + ); overridden_types[name] = data[name]; data[name] = new_type.type; } diff --git a/packages/perspective/src/js/emscripten.js b/packages/perspective/src/js/emscripten.js index 1ad10993f5..08f6fdb764 100644 --- a/packages/perspective/src/js/emscripten.js +++ b/packages/perspective/src/js/emscripten.js @@ -11,7 +11,7 @@ * structures that were previously handled in non-portable perspective.js */ -export const extract_vector = function(vector) { +export const extract_vector = function (vector) { // handles deletion already - do not call delete() on the input vector again let extracted = []; for (let i = 0; i < vector.size(); i++) { @@ -22,7 +22,7 @@ export const extract_vector = function(vector) { return extracted; }; -export const extract_map = function(map) { +export const extract_map = function (map) { // handles deletion already - do not call delete() on the input map again let extracted = {}; let keys = map.keys(); @@ -45,7 +45,7 @@ export const extract_map = function(map) { * * @private */ -export const fill_vector = function(vector, arr) { +export const fill_vector = function (vector, arr) { for (const elem of arr) { vector.push_back(elem); } diff --git a/packages/perspective/src/js/perspective.js b/packages/perspective/src/js/perspective.js index dc0e9c051a..7b8e92ab7f 100644 --- a/packages/perspective/src/js/perspective.js +++ b/packages/perspective/src/js/perspective.js @@ -35,7 +35,7 @@ const WARNED_KEYS = new Set(); * * @module perspective */ -export default function(Module) { +export default function (Module) { let __MODULE__ = Module; let accessor = new DataAccessor(); const SIDES = ["zero", "one", "two"]; @@ -68,7 +68,15 @@ export default function(Module) { } function memory_usage() { - const mem = performance.memory ? JSON.parse(JSON.stringify(performance.memory, ["totalJSHeapSize", "usedJSHeapSize", "jsHeapSizeLimit"])) : process.memoryUsage(); + const mem = performance.memory + ? JSON.parse( + JSON.stringify(performance.memory, [ + "totalJSHeapSize", + "usedJSHeapSize", + "jsHeapSizeLimit", + ]) + ) + : process.memoryUsage(); mem.wasmHeap = __MODULE__.HEAP8.length; return mem; } @@ -94,7 +102,17 @@ export default function(Module) { * @private * @returns {Table} An `std::shared_ptr
      ` to a `Table` inside C++. */ - function make_table(accessor, _Table, index, limit, op, is_update, is_arrow, is_csv, port_id) { + function make_table( + accessor, + _Table, + index, + limit, + op, + is_update, + is_arrow, + is_csv, + port_id + ) { // C++ constructor cannot take null values - use default values if // index or limit are null. if (!index) { @@ -105,7 +123,17 @@ export default function(Module) { limit = 4294967295; } - _Table = __MODULE__.make_table(_Table, accessor, limit, index, op, is_update, is_arrow, is_csv, port_id); + _Table = __MODULE__.make_table( + _Table, + accessor, + limit, + index, + op, + is_update, + is_arrow, + is_csv, + port_id + ); const pool = _Table.get_pool(); const table_id = _Table.get_id(); @@ -163,13 +191,37 @@ export default function(Module) { this.view_config.expressions.length === 0; if (this.is_unit_context) { - this._View = __MODULE__.make_view_unit(table._Table, name, defaults.COLUMN_SEPARATOR_STRING, this.view_config, null); + this._View = __MODULE__.make_view_unit( + table._Table, + name, + defaults.COLUMN_SEPARATOR_STRING, + this.view_config, + null + ); } else if (sides === 0) { - this._View = __MODULE__.make_view_zero(table._Table, name, defaults.COLUMN_SEPARATOR_STRING, this.view_config, null); + this._View = __MODULE__.make_view_zero( + table._Table, + name, + defaults.COLUMN_SEPARATOR_STRING, + this.view_config, + null + ); } else if (sides === 1) { - this._View = __MODULE__.make_view_one(table._Table, name, defaults.COLUMN_SEPARATOR_STRING, this.view_config, null); + this._View = __MODULE__.make_view_one( + table._Table, + name, + defaults.COLUMN_SEPARATOR_STRING, + this.view_config, + null + ); } else if (sides === 2) { - this._View = __MODULE__.make_view_two(table._Table, name, defaults.COLUMN_SEPARATOR_STRING, this.view_config, null); + this._View = __MODULE__.make_view_two( + table._Table, + name, + defaults.COLUMN_SEPARATOR_STRING, + this.view_config, + null + ); } this.ctx = this._View.get_context(); @@ -187,7 +239,7 @@ export default function(Module) { * @returns {Promise} Shared the same key/values properties as * {@link module:perspective~view} */ - view.prototype.get_config = function() { + view.prototype.get_config = function () { return JSON.parse(JSON.stringify(this.config)); }; @@ -199,7 +251,7 @@ export default function(Module) { * * @async */ - view.prototype.delete = function() { + view.prototype.delete = function () { _remove_process(this.table.get_id()); this._View.delete(); this.ctx.delete(); @@ -216,7 +268,7 @@ export default function(Module) { i++; } this.update_callbacks.length = j; - this._delete_callbacks.forEach(cb => cb()); + this._delete_callbacks.forEach((cb) => cb()); }; /** @@ -226,7 +278,7 @@ export default function(Module) { * @returns {number} sides The number of sides of this * {@link module:perspective~view}. */ - view.prototype.sides = function() { + view.prototype.sides = function () { return this._View.sides(); }; @@ -238,7 +290,7 @@ export default function(Module) { * @returns {number} sides The number of hidden columns in this * {@link module:perspective~view}. */ - view.prototype._num_hidden = function() { + view.prototype._num_hidden = function () { // Count hidden columns. let hidden = 0; for (const sort of this.config.sort) { @@ -260,7 +312,7 @@ export default function(Module) { return extracted; } - const extract_vector_scalar = function(vector) { + const extract_vector_scalar = function (vector) { // handles deletion already - do not call delete() on the input vector // again let extracted = []; @@ -293,13 +345,17 @@ export default function(Module) { * @returns {Promise} A Promise of this * {@link module:perspective~view}'s schema. */ - view.prototype.schema = function(override = true) { + view.prototype.schema = function (override = true) { const schema = extract_map(this._View.schema()); if (override) { for (const key of Object.keys(schema)) { let colname = key.split(defaults.COLUMN_SEPARATOR_STRING); colname = colname[colname.length - 1]; - if (this.overridden_types[colname] && get_type_config(this.overridden_types[colname]).type === schema[key]) { + if ( + this.overridden_types[colname] && + get_type_config(this.overridden_types[colname]).type === + schema[key] + ) { schema[key] = this.overridden_types[colname]; } } @@ -330,13 +386,17 @@ export default function(Module) { * @returns {Promise} A Promise of this * {@link module:perspective~view}'s expression schema. */ - view.prototype.expression_schema = function(override = true) { + view.prototype.expression_schema = function (override = true) { const schema = extract_map(this._View.expression_schema()); if (override) { for (const key of Object.keys(schema)) { let colname = key.split(defaults.COLUMN_SEPARATOR_STRING); colname = colname[colname.length - 1]; - if (this.overridden_types[colname] && get_type_config(this.overridden_types[colname]).type === schema[key]) { + if ( + this.overridden_types[colname] && + get_type_config(this.overridden_types[colname]).type === + schema[key] + ) { schema[key] = this.overridden_types[colname]; } } @@ -344,8 +404,10 @@ export default function(Module) { return schema; }; - view.prototype._column_names = function(skip = false, depth = 0) { - return extract_vector_scalar(this._View.column_names(skip, depth)).map(x => x.join(defaults.COLUMN_SEPARATOR_STRING)); + view.prototype._column_names = function (skip = false, depth = 0) { + return extract_vector_scalar(this._View.column_names(skip, depth)).map( + (x) => x.join(defaults.COLUMN_SEPARATOR_STRING) + ); }; /** @@ -357,17 +419,36 @@ export default function(Module) { * * @returns {Array} an Array of Strings containing the column paths. */ - view.prototype.column_paths = function() { - return extract_vector_scalar(this._View.column_paths()).map(x => x.join(defaults.COLUMN_SEPARATOR_STRING)); + view.prototype.column_paths = function () { + return extract_vector_scalar(this._View.column_paths()).map((x) => + x.join(defaults.COLUMN_SEPARATOR_STRING) + ); }; - view.prototype.get_data_slice = function(start_row, end_row, start_col, end_col) { + view.prototype.get_data_slice = function ( + start_row, + end_row, + start_col, + end_col + ) { if (this.is_unit_context) { - return __MODULE__.get_data_slice_unit(this._View, start_row, end_row, start_col, end_col); + return __MODULE__.get_data_slice_unit( + this._View, + start_row, + end_row, + start_col, + end_col + ); } else { const num_sides = this.sides(); const nidx = SIDES[num_sides]; - return __MODULE__[`get_data_slice_${nidx}`](this._View, start_row, end_row, start_col, end_col); + return __MODULE__[`get_data_slice_${nidx}`]( + this._View, + start_row, + end_row, + start_col, + end_col + ); } }; @@ -379,18 +460,36 @@ export default function(Module) { * @param {Object} options User-provided options for `to_format`. * @returns {Object} an Object containing the parsed options. */ - const _parse_format_options = function(options) { + const _parse_format_options = function (options) { options = options || {}; - const max_cols = this._View.num_columns() + (this.sides() === 0 ? 0 : 1); + const max_cols = + this._View.num_columns() + (this.sides() === 0 ? 0 : 1); const max_rows = this._View.num_rows(); const hidden = this._num_hidden(); const psp_offset = this.sides() > 0 || this.column_only ? 1 : 0; const viewport = this.config.viewport ? this.config.viewport : {}; - const start_row = options.start_row || (viewport.top ? viewport.top : 0); - const end_row = Math.min(max_rows, options.end_row !== undefined ? options.end_row : viewport.height ? start_row + viewport.height : max_rows); - const start_col = options.start_col || (viewport.left ? viewport.left : 0); - const end_col = Math.min(max_cols, (options.end_col !== undefined ? options.end_col + psp_offset : viewport.width ? start_col + viewport.width : max_cols) * (hidden + 1)); + const start_row = + options.start_row || (viewport.top ? viewport.top : 0); + const end_row = Math.min( + max_rows, + options.end_row !== undefined + ? options.end_row + : viewport.height + ? start_row + viewport.height + : max_rows + ); + const start_col = + options.start_col || (viewport.left ? viewport.left : 0); + const end_col = Math.min( + max_cols, + (options.end_col !== undefined + ? options.end_col + psp_offset + : viewport.width + ? start_col + viewport.width + : max_cols) * + (hidden + 1) + ); // Return the calculated values options.start_row = Math.floor(start_row); @@ -408,7 +507,7 @@ export default function(Module) { * @returns {Array} A tuple of [min, max], whose types are column * and aggregate dependent. */ - view.prototype.get_min_max = function(colname) { + view.prototype.get_min_max = function (colname) { if (this.is_unit_context) { return __MODULE__.get_min_max_unit(this._View, colname); } else { @@ -423,7 +522,7 @@ export default function(Module) { * * @private */ - const to_format = function(options, formatter) { + const to_format = function (options, formatter) { _call_process(this.table.get_id()); options = _parse_format_options.bind(this)(options); const start_row = options.start_row; @@ -448,16 +547,27 @@ export default function(Module) { get_from_data_slice = __MODULE__[`get_from_data_slice_${nidx}`]; } - const slice = this.get_data_slice(start_row, end_row, start_col, end_col); + const slice = this.get_data_slice( + start_row, + end_row, + start_col, + end_col + ); const ns = slice.get_column_names(); - const col_names = extract_vector_scalar(ns).map(x => x.join(defaults.COLUMN_SEPARATOR_STRING)); + const col_names = extract_vector_scalar(ns).map((x) => + x.join(defaults.COLUMN_SEPARATOR_STRING) + ); const schema = this.schema(); let data = formatter.initDataValue(); for (let ridx = start_row; ridx < end_row; ridx++) { let row_path = has_row_path ? slice.get_row_path(ridx) : undefined; - if (has_row_path && leaves_only && row_path.size() < this.config.row_pivots.length) { + if ( + has_row_path && + leaves_only && + row_path.size() < this.config.row_pivots.length + ) { row_path.delete(); continue; } @@ -477,15 +587,33 @@ export default function(Module) { formatter.initColumnValue(data, row, "__ROW_PATH__"); for (let i = 0; i < row_path.size(); i++) { const s = row_path.get(i); - const value = __MODULE__.scalar_to_val(s, false, false); + const value = __MODULE__.scalar_to_val( + s, + false, + false + ); s.delete(); - formatter.addColumnValue(data, row, "__ROW_PATH__", value); + formatter.addColumnValue( + data, + row, + "__ROW_PATH__", + value + ); if (get_ids) { - formatter.addColumnValue(data, row, "__ID__", value); + formatter.addColumnValue( + data, + row, + "__ID__", + value + ); } } } - } else if ((cidx - (num_sides > 0 ? 1 : 0)) % (this.config.columns.length + hidden) >= this.config.columns.length) { + } else if ( + (cidx - (num_sides > 0 ? 1 : 0)) % + (this.config.columns.length + hidden) >= + this.config.columns.length + ) { // Hidden columns are always at the end of the column names // list, and we need to skip them from the output. continue; @@ -498,7 +626,10 @@ export default function(Module) { // e.g., 10000 will format as CSV `"10,000.00" // Otherwise, this would not need to be conditional. value = new Date(value); - value = value.toLocaleString("en-us", type_config.format); + value = value.toLocaleString( + "en-us", + type_config.format + ); } } formatter.setColumnValue(data, row, col_name, value); @@ -547,7 +678,7 @@ export default function(Module) { * * @private */ - const column_to_format = function(col_name, options, format_function) { + const column_to_format = function (col_name, options, format_function) { const num_rows = this.num_rows(); const start_row = options.start_row || 0; const end_row = options.end_row || num_rows; @@ -612,7 +743,7 @@ export default function(Module) { * supplied, the keys of this object will be comma-prepended with their * comma-separated column paths. */ - view.prototype.to_columns = function(options) { + view.prototype.to_columns = function (options) { return to_format.call(this, options, formatters.jsonTableFormatter); }; @@ -640,7 +771,7 @@ export default function(Module) { * supplied, the keys of this object will be comma-prepended with their * comma-separated column paths. */ - view.prototype.to_json = function(options) { + view.prototype.to_json = function (options) { return to_format.call(this, options, formatters.jsonFormatter); }; @@ -667,7 +798,7 @@ export default function(Module) { * supplied, the keys of this object will be comma-prepended with their * comma-separated column paths. */ - view.prototype.to_csv = function(options) { + view.prototype.to_csv = function (options) { return to_format.call(this, options, formatters.csvFormatter); }; @@ -697,7 +828,7 @@ export default function(Module) { * or Float64Array. If the column cannot be found, or is not of an * integer/float type, the Promise returns undefined. */ - view.prototype.col_to_js_typed_array = function(col_name, options = {}) { + view.prototype.col_to_js_typed_array = function (col_name, options = {}) { _call_process(this.table.get_id()); const format_function = __MODULE__[`col_to_js_typed_array`]; return column_to_format.call(this, col_name, options, format_function); @@ -722,7 +853,7 @@ export default function(Module) { * @returns {Promise} An `ArrayBuffer` in the Apache Arrow * format containing data from the view. */ - view.prototype.to_arrow = function(options = {}) { + view.prototype.to_arrow = function (options = {}) { _call_process(this.table.get_id()); options = _parse_format_options.bind(this)(options); const start_row = options.start_row; @@ -732,13 +863,37 @@ export default function(Module) { const sides = this.sides(); if (this.is_unit_context) { - return __MODULE__.to_arrow_unit(this._View, start_row, end_row, start_col, end_col); + return __MODULE__.to_arrow_unit( + this._View, + start_row, + end_row, + start_col, + end_col + ); } else if (sides === 0) { - return __MODULE__.to_arrow_zero(this._View, start_row, end_row, start_col, end_col); + return __MODULE__.to_arrow_zero( + this._View, + start_row, + end_row, + start_col, + end_col + ); } else if (sides === 1) { - return __MODULE__.to_arrow_one(this._View, start_row, end_row, start_col, end_col); + return __MODULE__.to_arrow_one( + this._View, + start_row, + end_row, + start_col, + end_col + ); } else if (sides === 2) { - return __MODULE__.to_arrow_two(this._View, start_row, end_row, start_col, end_col); + return __MODULE__.to_arrow_two( + this._View, + start_row, + end_row, + start_col, + end_col + ); } }; @@ -751,7 +906,7 @@ export default function(Module) { * * @returns {Promise} The number of aggregated rows. */ - view.prototype.num_rows = function() { + view.prototype.num_rows = function () { _call_process(this.table.get_id()); return this._View.num_rows(); }; @@ -765,10 +920,12 @@ export default function(Module) { * * @returns {Promise} The number of aggregated columns. */ - view.prototype.num_columns = function() { + view.prototype.num_columns = function () { const ncols = this._View.num_columns(); const nhidden = this._num_hidden(); - return ncols - (ncols / (this.config.columns.length + nhidden)) * nhidden; + return ( + ncols - (ncols / (this.config.columns.length + nhidden)) * nhidden + ); }; /** @@ -778,7 +935,7 @@ export default function(Module) { * * @returns {Promise} Whether this row is expanded. */ - view.prototype.get_row_expanded = function(idx) { + view.prototype.get_row_expanded = function (idx) { return this._View.get_row_expanded(idx); }; @@ -789,7 +946,7 @@ export default function(Module) { * * @returns {Promise} */ - view.prototype.expand = function(idx) { + view.prototype.expand = function (idx) { return this._View.expand(idx, this.config.row_pivots.length); }; @@ -800,7 +957,7 @@ export default function(Module) { * * @returns {Promise} */ - view.prototype.collapse = function(idx) { + view.prototype.collapse = function (idx) { return this._View.collapse(idx); }; @@ -808,7 +965,7 @@ export default function(Module) { * Set expansion `depth` of the pivot tree. * */ - view.prototype.set_depth = function(depth) { + view.prototype.set_depth = function (depth) { return this._View.set_depth(depth, this.config.row_pivots.length); }; @@ -817,7 +974,7 @@ export default function(Module) { * contexts the entire dataset of the view. * @private */ - view.prototype._get_step_delta = async function() { + view.prototype._get_step_delta = async function () { let delta = this._View.get_step_delta(0, 2147483647); let data; if (delta.cells.size() === 0) { @@ -829,10 +986,10 @@ export default function(Module) { rows[delta.cells.get(x).row] = true; } rows = Object.keys(rows); - const results = rows.map(row => + const results = rows.map((row) => this.to_json({ start_row: Number.parseInt(row), - end_row: Number.parseInt(row) + 1 + end_row: Number.parseInt(row) + 1, }) ); data = [].concat.apply([], results); @@ -849,7 +1006,7 @@ export default function(Module) { * * @private */ - view.prototype._get_row_delta = async function() { + view.prototype._get_row_delta = async function () { if (this.is_unit_context) { return __MODULE__.get_row_delta_unit(this._View); } else { @@ -881,11 +1038,13 @@ export default function(Module) { * - "none" (default): `delta` is `undefined`. * - "row": `delta` is an Arrow of the updated rows. */ - view.prototype.on_update = function(callback, {mode = "none"} = {}) { + view.prototype.on_update = function (callback, {mode = "none"} = {}) { _call_process(this.table.get_id()); if (["none", "row"].indexOf(mode) === -1) { - throw new Error(`Invalid update mode "${mode}" - valid modes are "none" and "row".`); + throw new Error( + `Invalid update mode "${mode}" - valid modes are "none" and "row".` + ); } if (mode === "row") { @@ -910,7 +1069,8 @@ export default function(Module) { if (mode === "row") { if (cache[port_id]["row_delta"] === undefined) { - cache[port_id]["row_delta"] = await this._get_row_delta(); + cache[port_id]["row_delta"] = + await this._get_row_delta(); } updated.delta = cache[port_id]["row_delta"]; } @@ -918,7 +1078,7 @@ export default function(Module) { // Call the callback with the updated object containing // `port_id` and `delta`. callback(updated); - } + }, }); }; @@ -947,11 +1107,17 @@ export default function(Module) { * * @param {function} callback A update callback function to be removed */ - view.prototype.remove_update = function(callback) { + view.prototype.remove_update = function (callback) { _call_process(this.table.get_id()); const total = this.update_callbacks.length; - filterInPlace(this.update_callbacks, x => x.orig_callback !== callback); - console.assert(total > this.update_callbacks.length, `"callback" does not match a registered updater`); + filterInPlace( + this.update_callbacks, + (x) => x.orig_callback !== callback + ); + console.assert( + total > this.update_callbacks.length, + `"callback" does not match a registered updater` + ); }; /** @@ -965,7 +1131,7 @@ export default function(Module) { * * @param {function} callback A callback function invoked on delete. */ - view.prototype.on_delete = function(callback) { + view.prototype.on_delete = function (callback) { this._delete_callbacks.push(callback); }; @@ -980,10 +1146,13 @@ export default function(Module) { * * @param {function} callback A delete callback function to be removed */ - view.prototype.remove_delete = function(callback) { + view.prototype.remove_delete = function (callback) { const initial_length = this._delete_callbacks.length; - filterInPlace(this._delete_callbacks, cb => cb !== callback); - console.assert(initial_length > this._delete_callbacks.length, `"callback" does not match a registered delete callbacks`); + filterInPlace(this._delete_callbacks, (cb) => cb !== callback); + console.assert( + initial_length > this._delete_callbacks.length, + `"callback" does not match a registered delete callbacks` + ); }; /** @@ -1026,22 +1195,22 @@ export default function(Module) { * * @private */ - view_config.prototype.get_row_pivots = function() { + view_config.prototype.get_row_pivots = function () { let vector = __MODULE__.make_string_vector(); return fill_vector(vector, this.row_pivots); }; - view_config.prototype.get_column_pivots = function() { + view_config.prototype.get_column_pivots = function () { let vector = __MODULE__.make_string_vector(); return fill_vector(vector, this.column_pivots); }; - view_config.prototype.get_columns = function() { + view_config.prototype.get_columns = function () { let vector = __MODULE__.make_string_vector(); return fill_vector(vector, this.columns); }; - view_config.prototype.get_filter = function() { + view_config.prototype.get_filter = function () { let vector = __MODULE__.make_2d_val_vector(); for (let filter of this.filter) { let filter_vector = __MODULE__.make_val_vector(); @@ -1051,7 +1220,7 @@ export default function(Module) { return vector; }; - view_config.prototype.get_sort = function() { + view_config.prototype.get_sort = function () { let vector = __MODULE__.make_2d_string_vector(); for (let sort of this.sort) { let sort_vector = __MODULE__.make_string_vector(); @@ -1061,7 +1230,7 @@ export default function(Module) { return vector; }; - view_config.prototype.get_expressions = function() { + view_config.prototype.get_expressions = function () { let vector = __MODULE__.make_2d_val_vector(); for (let expression of this.expressions) { let inner = __MODULE__.make_val_vector(); @@ -1107,23 +1276,23 @@ export default function(Module) { bindall(this); } - table.prototype.get_id = function() { + table.prototype.get_id = function () { return this._Table.get_id(); }; - table.prototype.get_pool = function() { + table.prototype.get_pool = function () { return this._Table.get_pool(); }; - table.prototype.make_port = function() { + table.prototype.make_port = function () { return this._Table.make_port(); }; - table.prototype.remove_port = function() { + table.prototype.remove_port = function () { this._Table.remove_port(); }; - table.prototype._update_callback = function(port_id) { + table.prototype._update_callback = function (port_id) { let cache = {}; for (let e in this.update_callbacks) { this.update_callbacks[e].callback(port_id, cache); @@ -1134,7 +1303,7 @@ export default function(Module) { * Returns the user-specified index column for this * {@link module:perspective~table} or null if an index is not set. */ - table.prototype.get_index = function() { + table.prototype.get_index = function () { return this.index; }; @@ -1142,7 +1311,7 @@ export default function(Module) { * Returns the user-specified limit column for this * {@link module:perspective~table} or null if an limit is not set. */ - table.prototype.get_limit = function() { + table.prototype.get_limit = function () { return this.limit; }; @@ -1150,7 +1319,7 @@ export default function(Module) { * Remove all rows in this {@link module:perspective~table} while preserving * the schema and construction options. */ - table.prototype.clear = function() { + table.prototype.clear = function () { _remove_process(this.get_id()); this._Table.reset_gnode(this.gnode_id); }; @@ -1158,7 +1327,7 @@ export default function(Module) { /** * Replace all rows in this {@link module:perspective~table} the input data. */ - table.prototype.replace = function(data) { + table.prototype.replace = function (data) { _remove_process(this.get_id()); this._Table.reset_gnode(this.gnode_id); this.update(data); @@ -1171,7 +1340,7 @@ export default function(Module) { * processing updates when they are garbage collected - you must call this * method to reclaim these. */ - table.prototype.delete = function() { + table.prototype.delete = function () { if (this.views.length > 0) { throw `Cannot delete Table as it still has ${this.views.length} registered View(s).`; } @@ -1193,7 +1362,7 @@ export default function(Module) { * @param {function} callback A callback function with no parameters * that will be invoked on `delete()`. */ - table.prototype.on_delete = function(callback) { + table.prototype.on_delete = function (callback) { this._delete_callbacks.push(callback); }; @@ -1203,10 +1372,13 @@ export default function(Module) { * * @param {function} callback A delete callback function to be removed */ - table.prototype.remove_delete = function(callback) { + table.prototype.remove_delete = function (callback) { const initial_length = this._delete_callbacks.length; - filterInPlace(this._delete_callbacks, cb => cb !== callback); - console.assert(initial_length > this._delete_callbacks.length, `"callback" does not match a registered delete callbacks`); + filterInPlace(this._delete_callbacks, (cb) => cb !== callback); + console.assert( + initial_length > this._delete_callbacks.length, + `"callback" does not match a registered delete callbacks` + ); }; /** @@ -1219,7 +1391,7 @@ export default function(Module) { * * @returns {Promise} The number of accumulated rows. */ - table.prototype.size = function() { + table.prototype.size = function () { _call_process(this._Table.get_id()); return this._Table.size(); }; @@ -1234,7 +1406,7 @@ export default function(Module) { * @returns {Promise} A Promise of this * {@link module:perspective~table}'s schema. */ - table.prototype.schema = function(override = true) { + table.prototype.schema = function (override = true) { let schema = this._Table.get_schema(); let columns = schema.columns(); let types = schema.types(); @@ -1276,7 +1448,9 @@ export default function(Module) { for (let expression_string of expressions) { if (expression_string.includes('""')) { - console.error(`Skipping expression '${expression_string}', as it cannot reference an empty column!`); + console.error( + `Skipping expression '${expression_string}', as it cannot reference an empty column!` + ); continue; } @@ -1293,44 +1467,61 @@ export default function(Module) { // on the first line of the expression. let expression_alias; - let parsed_expression_string = expression_string.replace(/\/\/(.+?)$/m, (_, alias) => { - expression_alias = alias.trim(); - return ""; - }); + let parsed_expression_string = expression_string.replace( + /\/\/(.+?)$/m, + (_, alias) => { + expression_alias = alias.trim(); + return ""; + } + ); // If an alias does not exist, the alias is the expression itself. if (!expression_alias || expression_alias.length == 0) { expression_alias = expression_string; } - parsed_expression_string = parsed_expression_string.replace(/\"(.*?[^\\])\"/g, (_, cname) => { - // If the column name contains escaped double quotes, replace - // them and assume that they escape one double quote. If there - // are multiple double quotes being escaped, i.e. \""...well? - cname = cname.replace(/\\"/g, '"'); + parsed_expression_string = parsed_expression_string.replace( + /\"(.*?[^\\])\"/g, + (_, cname) => { + // If the column name contains escaped double quotes, replace + // them and assume that they escape one double quote. If there + // are multiple double quotes being escaped, i.e. \""...well? + cname = cname.replace(/\\"/g, '"'); + + if (column_name_map[cname] === undefined) { + let column_id = `COLUMN${running_cidx}`; + column_name_map[cname] = column_id; + column_id_map[column_id] = cname; + } - if (column_name_map[cname] === undefined) { - let column_id = `COLUMN${running_cidx}`; - column_name_map[cname] = column_id; - column_id_map[column_id] = cname; + running_cidx++; + return column_name_map[cname]; } - - running_cidx++; - return column_name_map[cname]; - }); + ); // Replace single quote string literals and wrap them in a call to // intern() which makes sure they don't leak - parsed_expression_string = parsed_expression_string.replace(/'(.*?[^\\])'/g, match => `intern(${match})`); + parsed_expression_string = parsed_expression_string.replace( + /'(.*?[^\\])'/g, + (match) => `intern(${match})` + ); // Replace intern() for bucket, as it takes a string literal // parameter and does not work if that param is interned. TODO: // this is clumsy and we should have a better way of handling it. - parsed_expression_string = parsed_expression_string.replace(/bucket\(.*?, (intern\(\'([smhDWMY])\'\))\)/g, (match, full, value) => { - return `${match.substr(0, match.indexOf(full))}'${value}')`; - }); + parsed_expression_string = parsed_expression_string.replace( + /bucket\(.*?, (intern\(\'([smhDWMY])\'\))\)/g, + (match, full, value) => { + return `${match.substr(0, match.indexOf(full))}'${value}')`; + } + ); - const validated = [expression_alias, expression_string, parsed_expression_string, column_id_map]; + const validated = [ + expression_alias, + expression_string, + parsed_expression_string, + column_id_map, + ]; // Check if this expression is already in the array, if so then // we need to replace the expression so the last expression tagged @@ -1340,7 +1531,8 @@ export default function(Module) { validated_expressions[idx] = validated; } else { validated_expressions.push(validated); - expression_idx_map[expression_alias] = validated_expressions.length - 1; + expression_idx_map[expression_alias] = + validated_expressions.length - 1; } } @@ -1370,11 +1562,14 @@ export default function(Module) { * // {"invalid": "unknown token!", "1 + 'string'": "TypeError"} * console.log(results.errors); */ - table.prototype.validate_expressions = function(expressions, override = true) { + table.prototype.validate_expressions = function ( + expressions, + override = true + ) { const validated = { expression_schema: {}, expression_alias: {}, - errors: {} + errors: {}, }; if (!expressions || expressions.length === 0) return validated; @@ -1393,7 +1588,10 @@ export default function(Module) { validated.expression_alias[expression[0]] = expression[1]; } - const validation_results = __MODULE__.validate_expressions(this._Table, vector); + const validation_results = __MODULE__.validate_expressions( + this._Table, + vector + ); const expression_schema = validation_results.get_expression_schema(); const expression_errors = validation_results.get_expression_errors(); @@ -1436,10 +1634,13 @@ export default function(Module) { * @async * @param {Array} [filter] a filter configuration to test. */ - table.prototype.is_valid_filter = function(filter) { + table.prototype.is_valid_filter = function (filter) { // isNull and isNotNull filter operators are always valid and apply to // all schema types - if (filter[1] === perspective.FILTER_OPERATORS.isNull || filter[1] === perspective.FILTER_OPERATORS.isNotNull) { + if ( + filter[1] === perspective.FILTER_OPERATORS.isNull || + filter[1] === perspective.FILTER_OPERATORS.isNotNull + ) { return true; } @@ -1450,7 +1651,10 @@ export default function(Module) { const schema = this.schema(); const exists = schema[filter[0]]; - if (exists && (schema[filter[0]] === "date" || schema[filter[0]] === "datetime")) { + if ( + exists && + (schema[filter[0]] === "date" || schema[filter[0]] === "datetime") + ) { return __MODULE__.is_valid_datetime(filter[2]); } @@ -1500,23 +1704,29 @@ export default function(Module) { * {@link module:perspective~view} object for the supplied configuration, * bound to this table. */ - table.prototype.view = function(_config = {}) { + table.prototype.view = function (_config = {}) { _call_process(this._Table.get_id()); let config = {}; for (const key of Object.keys(_config)) { if (defaults.CONFIG_ALIASES[key]) { if (!config[defaults.CONFIG_ALIASES[key]]) { if (!WARNED_KEYS.has(key)) { - console.warn(`Deprecated: "${key}" config parameter, please use "${defaults.CONFIG_ALIASES[key]}" instead`); + console.warn( + `Deprecated: "${key}" config parameter, please use "${defaults.CONFIG_ALIASES[key]}" instead` + ); WARNED_KEYS.add(key); } config[defaults.CONFIG_ALIASES[key]] = _config[key]; } else { - throw new Error(`Duplicate configuration parameter "${key}"`); + throw new Error( + `Duplicate configuration parameter "${key}"` + ); } } else if (key === "aggregate") { if (!WARNED_KEYS.has("aggregate")) { - console.warn(`Deprecated: "aggregate" config parameter has been replaced by "aggregates" and "columns"`); + console.warn( + `Deprecated: "aggregate" config parameter has been replaced by "aggregates" and "columns"` + ); WARNED_KEYS.add("aggregate"); } // backwards compatibility: deconstruct `aggregate` into @@ -1564,7 +1774,9 @@ export default function(Module) { for (let filter of config.filter) { // TODO: this does not work for expressions const dtype = table_schema[filter[0]]; - const is_compare = filter[1] !== perspective.FILTER_OPERATORS.isNull && filter[1] !== perspective.FILTER_OPERATORS.isNotNull; + const is_compare = + filter[1] !== perspective.FILTER_OPERATORS.isNull && + filter[1] !== perspective.FILTER_OPERATORS.isNotNull; if (is_compare && (dtype === "date" || dtype === "datetime")) { // new Date() accepts strings and new Date() objects, so no // need to type check here. @@ -1602,7 +1814,9 @@ export default function(Module) { let start = performance.now(); setTimeout(function poll() { let now = performance.now(); - console.log(`${((1000 * _msgs) / (now - start)).toFixed(2)} msgs/sec`); + console.log( + `${((1000 * _msgs) / (now - start)).toFixed(2)} msgs/sec` + ); _msgs = 0; start = now; setTimeout(poll, 5000); @@ -1627,7 +1841,7 @@ export default function(Module) { * * @see {@link module:perspective~table} */ - table.prototype.update = function(data, options) { + table.prototype.update = function (data, options) { options = options || {}; options.port_id = options.port_id || 0; @@ -1652,7 +1866,9 @@ export default function(Module) { pdata = data; } else { accessor.init(data); - accessor.names = cols.concat(accessor.names.filter(x => x === "__INDEX__")); + accessor.names = cols.concat( + accessor.names.filter((x) => x === "__INDEX__") + ); accessor.types = extract_vector(types).slice(0, cols.length); if (meter) { @@ -1672,7 +1888,9 @@ export default function(Module) { const explicit_index = !!this.index; if (explicit_index) { // find the type of the index column - accessor.types.push(accessor.types[accessor.names.indexOf(this.index)]); + accessor.types.push( + accessor.types[accessor.names.indexOf(this.index)] + ); } else { // default index is an integer accessor.types.push(__MODULE__.t_dtype.DTYPE_INT32); @@ -1684,7 +1902,17 @@ export default function(Module) { const op = __MODULE__.t_op.OP_INSERT; // update the Table in C++, but don't keep the returned C++ Table // reference as it is identical - make_table(pdata, this._Table, this.index, this.limit, op, true, is_arrow, is_csv, options.port_id); + make_table( + pdata, + this._Table, + this.index, + this.limit, + op, + true, + is_arrow, + is_csv, + options.port_id + ); this.initialized = true; } catch (e) { console.error(`Update failed: ${e}`); @@ -1701,9 +1929,11 @@ export default function(Module) { * * @see {@link module:perspective~table} */ - table.prototype.remove = function(data, options) { + table.prototype.remove = function (data, options) { if (!this.index) { - console.error("Cannot call `remove()` on a Table without a user-specified index."); + console.error( + "Cannot call `remove()` on a Table without a user-specified index." + ); return; } @@ -1715,7 +1945,7 @@ export default function(Module) { let types = schema.types(); let is_arrow = false; - data = data.map(idx => ({[this.index]: idx})); + data = data.map((idx) => ({[this.index]: idx})); if (data instanceof ArrayBuffer) { pdata = new Uint8Array(data); @@ -1731,7 +1961,17 @@ export default function(Module) { const op = __MODULE__.t_op.OP_DELETE; // update the Table in C++, but don't keep the returned Table // reference as it is identical - make_table(pdata, this._Table, this.index, this.limit, op, false, is_arrow, false, options.port_id); + make_table( + pdata, + this._Table, + this.index, + this.limit, + op, + false, + is_arrow, + false, + options.port_id + ); this.initialized = true; } catch (e) { console.error(`Remove failed`, e); @@ -1747,7 +1987,7 @@ export default function(Module) { * @returns {Promise>} An array of column names for this * table. */ - table.prototype.columns = function() { + table.prototype.columns = function () { let schema = this._Table.get_schema(); let cols = schema.columns(); let names = []; @@ -1762,7 +2002,7 @@ export default function(Module) { return names; }; - table.prototype.execute = function(f) { + table.prototype.execute = function (f) { f(this); }; @@ -1777,7 +2017,7 @@ export default function(Module) { Server, - worker: function() { + worker: function () { return this; }, @@ -1820,7 +2060,7 @@ export default function(Module) { * {@link module:perspective~table} object, or be rejected if an error * happens during Table construction. */ - table: function(data, options) { + table: function (data, options) { options = options || {}; // Always store index and limit as user-provided values or `null`. @@ -1832,7 +2072,10 @@ export default function(Module) { let overridden_types = {}; let is_csv = false; - if (data instanceof ArrayBuffer || (typeof Buffer !== "undefined" && data instanceof Buffer)) { + if ( + data instanceof ArrayBuffer || + (typeof Buffer !== "undefined" && data instanceof Buffer) + ) { data_accessor = new Uint8Array(data); is_arrow = true; } else if (typeof data === "string") { @@ -1861,11 +2104,26 @@ export default function(Module) { // and limit, so `make_table` will convert null to default // values of "" for index and 4294967295 for limit. Tables // must be created on port 0. - _Table = make_table(data_accessor, undefined, options.index, options.limit, op, false, is_arrow, is_csv, 0); + _Table = make_table( + data_accessor, + undefined, + options.index, + options.limit, + op, + false, + is_arrow, + is_csv, + 0 + ); // Pass through user-provided values or `null` to the // Javascript Table constructor. - return new table(_Table, options.index, options.limit, overridden_types); + return new table( + _Table, + options.index, + options.limit, + overridden_types + ); } catch (e) { if (_Table) { _Table.delete(); @@ -1873,7 +2131,7 @@ export default function(Module) { console.error(`Table initialization failed: ${e}`); throw e; } - } + }, }; for (let prop of Object.keys(defaults)) { @@ -1902,7 +2160,11 @@ export default function(Module) { */ constructor(perspective) { super(perspective); - self.addEventListener("message", e => this.process(e.data), false); + self.addEventListener( + "message", + (e) => this.process(e.data), + false + ); } /** @@ -1930,8 +2192,8 @@ export default function(Module) { } else { __MODULE__({ wasmBinary: msg.buffer, - wasmJSMethod: "native-wasm" - }).then(mod => { + wasmJSMethod: "native-wasm", + }).then((mod) => { __MODULE__ = mod; super.init(msg); }); diff --git a/packages/perspective/src/js/perspective.node.js b/packages/perspective/src/js/perspective.node.js index 43fef14b8c..d12f3f7526 100644 --- a/packages/perspective/src/js/perspective.node.js +++ b/packages/perspective/src/js/perspective.node.js @@ -20,19 +20,22 @@ const WebSocket = require("ws"); const process = require("process"); const path = require("path"); -const load_perspective = require("./@finos/perspective-cpp/dist/cjs/perspective.cpp.js").default; +const load_perspective = + require("./@finos/perspective-cpp/dist/cjs/perspective.cpp.js").default; // eslint-disable-next-line no-undef const LOCAL_PATH = path.join(process.cwd(), "node_modules"); -const buffer = fs.readFileSync(require.resolve("./@finos/perspective-cpp/dist/cjs/perspective.cpp.wasm")).buffer; +const buffer = fs.readFileSync( + require.resolve("./@finos/perspective-cpp/dist/cjs/perspective.cpp.wasm") +).buffer; const SYNC_SERVER = new (class extends Server { init(msg) { load_perspective({ wasmBinary: buffer, - wasmJSMethod: "native-wasm" - }).then(core => { + wasmJSMethod: "native-wasm", + }).then((core) => { this.perspective = perspective(core); super.init(msg); }); @@ -61,7 +64,7 @@ const DEFAULT_ASSETS = [ "@finos/perspective-viewer-datagrid/dist/umd", "@finos/perspective-viewer-d3fc/dist/umd", "@finos/perspective-workspace/dist/umd", - "@finos/perspective-jupyterlab/dist/umd" + "@finos/perspective-jupyterlab/dist/umd", ]; const CONTENT_TYPES = { @@ -69,12 +72,12 @@ const CONTENT_TYPES = { ".css": "text/css", ".json": "application/json", ".arrow": "arraybuffer", - ".wasm": "application/wasm" + ".wasm": "application/wasm", }; function read_promise(filePath) { return new Promise((resolve, reject) => { - fs.readFile(filePath, function(error, content) { + fs.readFile(filePath, function (error, content) { if (error && error.code !== "ENOENT") { reject(error); } else { @@ -88,7 +91,7 @@ function read_promise(filePath) { * Host a Perspective server that hosts data, code files, etc. */ function perspective_assets(assets, host_psp) { - return async function(request, response) { + return async function (request, response) { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Request-Method", "*"); response.setHeader("Access-Control-Allow-Methods", "OPTIONS,GET"); @@ -114,7 +117,10 @@ function perspective_assets(assets, host_psp) { if (typeof content !== "undefined") { console.log(`200 ${url}`); response.writeHead(200, {"Content-Type": contentType}); - response.end(content, extname === ".arrow" ? undefined : "utf-8"); + response.end( + content, + extname === ".arrow" ? undefined : "utf-8" + ); return; } } @@ -122,13 +128,22 @@ function perspective_assets(assets, host_psp) { for (let rootDir of DEFAULT_ASSETS) { try { let paths = require.resolve.paths(rootDir + url); - paths = [...paths, ...assets.map(x => path.join(x, "node_modules")), LOCAL_PATH]; + paths = [ + ...paths, + ...assets.map((x) => path.join(x, "node_modules")), + LOCAL_PATH, + ]; let filePath = require.resolve(rootDir + url, {paths}); let content = await read_promise(filePath); if (typeof content !== "undefined") { console.log(`200 ${url}`); - response.writeHead(200, {"Content-Type": contentType}); - response.end(content, extname === ".arrow" ? undefined : "utf-8"); + response.writeHead(200, { + "Content-Type": contentType, + }); + response.end( + content, + extname === ".arrow" ? undefined : "utf-8" + ); return; } } catch (e) {} @@ -162,14 +177,19 @@ class WebSocketServer extends WebSocketManager { this._server = http.createServer(perspective_assets(assets, host_psp)); // Serve Worker API through WebSockets - this._wss = new WebSocket.Server({noServer: true, perMessageDeflate: true}); + this._wss = new WebSocket.Server({ + noServer: true, + perMessageDeflate: true, + }); // When the server starts, define how to handle messages - this._wss.on("connection", ws => this.add_connection(ws)); + this._wss.on("connection", (ws) => this.add_connection(ws)); this._server.on("upgrade", (request, socket, head) => { console.log("200 *** websocket upgrade ***"); - this._wss.handleUpgrade(request, socket, head, sock => this._wss.emit("connection", sock, request)); + this._wss.handleUpgrade(request, socket, head, (sock) => + this._wss.emit("connection", sock, request) + ); }); this._server.listen(port, () => { @@ -190,7 +210,7 @@ class WebSocketServer extends WebSocketManager { * a Perspective server and Perspective client to communicate. * @param {*} url */ -const websocket = url => { +const websocket = (url) => { return new WebSocketClient(new WebSocket(url)); }; diff --git a/packages/perspective/src/js/perspective.parallel.js b/packages/perspective/src/js/perspective.parallel.js index a05188fdc0..461c333399 100644 --- a/packages/perspective/src/js/perspective.parallel.js +++ b/packages/perspective/src/js/perspective.parallel.js @@ -69,9 +69,14 @@ class WebWorkerClient extends Client { let _worker; const msg = {cmd: "init", config: get_config()}; if (typeof WebAssembly === "undefined") { - throw new Error("WebAssembly not supported. Support for ASM.JS has been removed as of 0.3.1."); + throw new Error( + "WebAssembly not supported. Support for ASM.JS has been removed as of 0.3.1." + ); } else { - [_worker, msg.buffer] = await Promise.all([override.worker(), override.wasm()]); + [_worker, msg.buffer] = await Promise.all([ + override.worker(), + override.wasm(), + ]); } for (var key in this._worker) { _worker[key] = this._worker[key]; @@ -88,7 +93,11 @@ class WebWorkerClient extends Client { * @param {*} msg */ send(msg) { - if (this._worker.transferable && msg.args && msg.args[0] instanceof ArrayBuffer) { + if ( + this._worker.transferable && + msg.args && + msg.args[0] instanceof ArrayBuffer + ) { this._worker.postMessage(msg, [msg.args[0]]); } else { this._worker.postMessage(msg); @@ -118,20 +127,22 @@ class WebWorkerClient extends Client { * */ -const WORKER_SINGLETON = (function() { +const WORKER_SINGLETON = (function () { let __WORKER__, __CONFIG__; return { - getInstance: function(config) { + getInstance: function (config) { if (__WORKER__ === undefined) { __WORKER__ = new WebWorkerClient(config); } const config_str = JSON.stringify(config); if (__CONFIG__ && config_str !== __CONFIG__) { - throw new Error(`Confiuration object for shared_worker() has changed - this is probably a bug in your application.`); + throw new Error( + `Confiuration object for shared_worker() has changed - this is probably a bug in your application.` + ); } __CONFIG__ = config_str; return __WORKER__; - } + }, }; })(); @@ -144,7 +155,7 @@ if (document.currentScript && document.currentScript.hasAttribute("preload")) { } const mod = { - override: x => override.set(x), + override: (x) => override.set(x), /** * Create a new WebWorkerClient instance. s @@ -166,7 +177,7 @@ const mod = { shared_worker(config) { return WORKER_SINGLETON.getInstance(config); - } + }, }; for (let prop of Object.keys(defaults)) { diff --git a/packages/perspective/src/js/perspective.worker.js b/packages/perspective/src/js/perspective.worker.js index 2f945bb4d9..0b527860cd 100644 --- a/packages/perspective/src/js/perspective.worker.js +++ b/packages/perspective/src/js/perspective.worker.js @@ -16,8 +16,8 @@ if (global.document !== undefined && typeof WebAssembly !== "undefined") { _perspective_instance = global.perspective = perspective( load_perspective({ wasmJSMethod: "native-wasm", - printErr: x => console.error(x), - print: x => console.log(x) + printErr: (x) => console.error(x), + print: (x) => console.log(x), }) ); } else { diff --git a/packages/perspective/src/js/utils.js b/packages/perspective/src/js/utils.js index 500576efd6..582bfea333 100644 --- a/packages/perspective/src/js/utils.js +++ b/packages/perspective/src/js/utils.js @@ -26,7 +26,9 @@ export function get_column_type(val) { } else if (val === 13) { return "date"; } else { - console.warn(`Unknown type for value ${val} with JS type ${typeof val}`); + console.warn( + `Unknown type for value ${val} with JS type ${typeof val}` + ); } } @@ -86,7 +88,13 @@ export function detectChrome() { if (isIOSChrome) { return true; - } else if (isChromium !== null && typeof isChromium !== "undefined" && vendorName === "Google Inc." && isOpera === false && isIEedge === false) { + } else if ( + isChromium !== null && + typeof isChromium !== "undefined" && + vendorName === "Google Inc." && + isOpera === false && + isIEedge === false + ) { return true; } else { return false; @@ -102,7 +110,7 @@ export function detect_iphone() { * String.includes() polyfill */ if (!String.prototype.includes) { - String.prototype.includes = function(search, start) { + String.prototype.includes = function (search, start) { if (typeof start !== "number") { start = 0; } @@ -119,7 +127,7 @@ if (!String.prototype.includes) { // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes if (!Array.prototype.includes) { Object.defineProperty(Array.prototype, "includes", { - value: function(searchElement, fromIndex) { + value: function (searchElement, fromIndex) { if (this == null) { throw new TypeError('"this" is null or not defined'); } @@ -144,7 +152,13 @@ if (!Array.prototype.includes) { var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); function sameValueZero(x, y) { - return x === y || (typeof x === "number" && typeof y === "number" && isNaN(x) && isNaN(y)); + return ( + x === y || + (typeof x === "number" && + typeof y === "number" && + isNaN(x) && + isNaN(y)) + ); } // 7. Repeat, while k < len @@ -161,6 +175,6 @@ if (!Array.prototype.includes) { // 8. Return false return false; - } + }, }); } diff --git a/packages/perspective/src/js/view_formatters.js b/packages/perspective/src/js/view_formatters.js index 10254d42a1..895f6d4584 100644 --- a/packages/perspective/src/js/view_formatters.js +++ b/packages/perspective/src/js/view_formatters.js @@ -14,19 +14,21 @@ const jsonFormatter = { setColumnValue: (data, row, colName, value) => (row[colName] = value), addColumnValue: (data, row, colName, value) => row[colName].unshift(value), addRow: (data, row) => data.push(row), - formatData: data => data, - slice: (data, start) => data.slice(start) + formatData: (data) => data, + slice: (data, start) => data.slice(start), }; const csvFormatter = Object.assign({}, jsonFormatter, { - addColumnValue: (data, row, colName, value) => row[colName.split("|").join(",")].unshift(value), - setColumnValue: (data, row, colName, value) => (row[colName.split("|").join(",")] = value), - formatData: function(data, {delimiter = ","} = {}) { + addColumnValue: (data, row, colName, value) => + row[colName.split("|").join(",")].unshift(value), + setColumnValue: (data, row, colName, value) => + (row[colName.split("|").join(",")] = value), + formatData: function (data, {delimiter = ","} = {}) { if (data.length === 0) { return ""; } - const format = function(x) { + const format = function (x) { if (x === null) { return ""; } @@ -36,7 +38,9 @@ const csvFormatter = Object.assign({}, jsonFormatter, { // CSV escapes with double double quotes, for real. // Section 2.7 of the fake // [CSV spec](https://tools.ietf.org/html/rfc4180) - return x.indexOf(delimiter) > -1 ? `"${x.replace(/\"/g, '""')}"` : x.toString(); + return x.indexOf(delimiter) > -1 + ? `"${x.replace(/\"/g, '""')}"` + : x.toString(); case "number": return x; case "boolean": @@ -47,11 +51,15 @@ const csvFormatter = Object.assign({}, jsonFormatter, { const columns = Object.keys(data[0]); let csv = columns.map(format).join(delimiter); for (let x = 0; x < data.length; x++) { - csv += "\r\n" + columns.map(column => format(data[x][column])).join(delimiter); + csv += + "\r\n" + + columns + .map((column) => format(data[x][column])) + .join(delimiter); } return csv; - } + }, }); const jsonTableFormatter = { @@ -70,18 +78,18 @@ const jsonTableFormatter = { data[colName].push([]); }, addRow: () => {}, - formatData: data => data, + formatData: (data) => data, slice: (data, start) => { let new_data = {}; for (let x in data) { new_data[x] = data[x].slice(start); } return new_data; - } + }, }; export default { jsonFormatter, jsonTableFormatter, - csvFormatter + csvFormatter, }; diff --git a/packages/perspective/src/js/websocket/client.js b/packages/perspective/src/js/websocket/client.js index 88a963d3c4..d74657cef9 100644 --- a/packages/perspective/src/js/websocket/client.js +++ b/packages/perspective/src/js/websocket/client.js @@ -22,7 +22,7 @@ export class WebSocketClient extends Client { this._ws.onopen = () => { this.send({ id: -1, - cmd: "init" + cmd: "init", }); }; @@ -33,7 +33,7 @@ export class WebSocketClient extends Client { setTimeout(ping, PING_TIMEOUT); - this._ws.onmessage = msg => { + this._ws.onmessage = (msg) => { if (msg.data === "pong") { return; } @@ -44,7 +44,10 @@ export class WebSocketClient extends Client { // chunk. let binary_msg = msg.data; - this._full_binary.set(new Uint8Array(binary_msg), this._total_chunk_length); + this._full_binary.set( + new Uint8Array(binary_msg), + this._total_chunk_length + ); this._total_chunk_length += binary_msg.byteLength; // Use the total length of the binary from the pre-message @@ -62,8 +65,8 @@ export class WebSocketClient extends Client { let result = { data: { id: this._pending_binary, - data: binary_msg - } + data: binary_msg, + }, }; // make sure on_update callbacks are called with a `port_id` @@ -71,7 +74,7 @@ export class WebSocketClient extends Client { if (this._pending_port_id !== undefined) { const new_data_with_port_id = { port_id: this._pending_port_id, - delta: binary_msg + delta: binary_msg, }; result.data.data = new_data_with_port_id; } @@ -107,10 +110,12 @@ export class WebSocketClient extends Client { // Create an empty ArrayBuffer to hold the binary, as it // will be sent in n >= 1 chunks. - this._full_binary = new Uint8Array(this._pending_binary_length); + this._full_binary = new Uint8Array( + this._pending_binary_length + ); } else { this._handle({ - data: msg + data: msg, }); } } @@ -129,7 +134,12 @@ export class WebSocketClient extends Client { * receiver. */ send(msg) { - if (msg.args && msg.args.length > 0 && msg.args[0] instanceof ArrayBuffer && msg.args[0].byteLength !== undefined) { + if ( + msg.args && + msg.args.length > 0 && + msg.args[0] instanceof ArrayBuffer && + msg.args[0].byteLength !== undefined + ) { const pre_msg = msg; msg.binary_length = msg.args[0].byteLength; this._ws.send(JSON.stringify(pre_msg)); @@ -140,7 +150,7 @@ export class WebSocketClient extends Client { } terminate() { - return new Promise(resolve => { + return new Promise((resolve) => { this._ws.onclose = resolve; this._ws.close(); }); diff --git a/packages/perspective/src/js/websocket/manager.js b/packages/perspective/src/js/websocket/manager.js index 46e6d05f9a..b5a65292ef 100644 --- a/packages/perspective/src/js/websocket/manager.js +++ b/packages/perspective/src/js/websocket/manager.js @@ -53,7 +53,7 @@ export class WebSocketManager extends Server { ws.id = CLIENT_ID_GEN++; // Parse incoming messages - ws.on("message", msg => { + ws.on("message", (msg) => { ws.isAlive = true; if (msg === "ping") { @@ -94,7 +94,7 @@ export class WebSocketManager extends Server { msg.id = compoundId; this.requests[msg.id] = { ws, - msg + msg, }; this.process(msg, ws.id); } catch (e) { @@ -132,7 +132,13 @@ export class WebSocketManager extends Server { const binary_msg = transferable[0]; msg.binary_length = binary_msg.byteLength; req.ws.send(JSON.stringify(msg)); - this._post_chunked(req, binary_msg, 0, this.chunk_size, binary_msg.byteLength); + this._post_chunked( + req, + binary_msg, + 0, + this.chunk_size, + binary_msg.byteLength + ); } else { req.ws.send(JSON.stringify(msg)); } diff --git a/packages/perspective/test/config/test_node.config.js b/packages/perspective/test/config/test_node.config.js index 88f9a12d11..36442fdad2 100644 --- a/packages/perspective/test/config/test_node.config.js +++ b/packages/perspective/test/config/test_node.config.js @@ -7,11 +7,11 @@ module.exports = Object.assign({}, common({no_minify: true}), { externals: [/^([a-z0-9]|\@(?!apache\-arrow)).*?(?!wasm)$/g], node: { __dirname: false, - __filename: false + __filename: false, }, output: { filename: "perspective.spec.js", path: path.resolve(__dirname, "../../build"), - libraryTarget: "umd" - } + libraryTarget: "umd", + }, }); diff --git a/packages/perspective/test/config/timezone/jest.config.js b/packages/perspective/test/config/timezone/jest.config.js index fbc4ee2d61..d0267791b8 100644 --- a/packages/perspective/test/config/timezone/jest.config.js +++ b/packages/perspective/test/config/timezone/jest.config.js @@ -9,5 +9,5 @@ module.exports = { verbose: true, - globalSetup: "/test/config/timezone/set_timezone.js" + globalSetup: "/test/config/timezone/set_timezone.js", }; diff --git a/packages/perspective/test/html/test.html b/packages/perspective/test/html/test.html index 11e151d287..40007b6234 100644 --- a/packages/perspective/test/html/test.html +++ b/packages/perspective/test/html/test.html @@ -1,15 +1,15 @@ - - - - - - - - + + + + + + + + diff --git a/packages/perspective/test/js/clear.js b/packages/perspective/test/js/clear.js index c36bf85d86..4f0718914c 100644 --- a/packages/perspective/test/js/clear.js +++ b/packages/perspective/test/js/clear.js @@ -7,9 +7,9 @@ * */ -module.exports = perspective => { - describe("Clear", function() { - it("removes the rows from the table", async function() { +module.exports = (perspective) => { + describe("Clear", function () { + it("removes the rows from the table", async function () { const table = await perspective.table([{x: 1}]); const view = await table.view(); let json = await view.to_json(); @@ -21,12 +21,12 @@ module.exports = perspective => { table.delete(); }); - it("to_columns output is empty", async function() { + it("to_columns output is empty", async function () { const table = await perspective.table([{x: 1}]); const view = await table.view(); let result = await view.to_columns(); expect(result).toEqual({ - x: [1] + x: [1], }); table.clear(); result = await view.to_columns(); @@ -36,18 +36,18 @@ module.exports = perspective => { }); }); - describe("Replace", function() { - it("replaces the rows in the table with the input data", async function() { + describe("Replace", function () { + it("replaces the rows in the table with the input data", async function () { const table = await perspective.table([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); const view = await table.view(); let json = await view.to_json(); expect(json).toHaveLength(2); expect(json).toEqual([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); table.replace([{x: 5, y: 6}]); json = await view.to_json(); @@ -57,15 +57,15 @@ module.exports = perspective => { table.delete(); }); - it("replaces the rows in the table with the input data and fires an on_update", async function(done) { + it("replaces the rows in the table with the input data and fires an on_update", async function (done) { const table = await perspective.table([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); const view = await table.view(); - const callback = async function(updated) { + const callback = async function (updated) { expect(updated.port_id).toEqual(0); const json = await view.to_json(); expect(json).toHaveLength(1); @@ -81,21 +81,21 @@ module.exports = perspective => { expect(json).toHaveLength(2); expect(json).toEqual([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); table.replace([{x: 5, y: 6}]); }); - it("replaces the rows in the table with the input data and fires an on_update with the correct delta", async function(done) { + it("replaces the rows in the table with the input data and fires an on_update with the correct delta", async function (done) { const table = await perspective.table([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); const view = await table.view(); - const callback = async function(updated) { + const callback = async function (updated) { expect(updated.port_id).toEqual(0); const table2 = await perspective.table(updated.delta); const view2 = await table2.view(); @@ -120,16 +120,16 @@ module.exports = perspective => { expect(json).toHaveLength(2); expect(json).toEqual([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); table.replace([{x: 5, y: 6}]); }); - it("replace the rows in the table atomically", async function() { + it("replace the rows in the table atomically", async function () { const table = await perspective.table([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); const view = await table.view(); setTimeout(() => table.replace([{x: 5, y: 6}])); @@ -137,7 +137,7 @@ module.exports = perspective => { expect(json).toHaveLength(2); expect(json).toEqual([ {x: 1, y: 2}, - {x: 3, y: 4} + {x: 3, y: 4}, ]); await new Promise(setTimeout); json = await view.to_json(); @@ -147,17 +147,22 @@ module.exports = perspective => { table.delete(); }); - it("Preserves sort order with 2-sided pivot", async function() { + it("Preserves sort order with 2-sided pivot", async function () { const input = [ {x: 1, y: 7, z: "a"}, {x: 1, y: 6, z: "b"}, {x: 2, y: 5, z: "a"}, {x: 2, y: 4, z: "b"}, {x: 3, y: 3, z: "a"}, - {x: 3, y: 2, z: "b"} + {x: 3, y: 2, z: "b"}, ]; const table = await perspective.table(input); - const view = await table.view({row_pivots: ["z"], column_pivots: ["x"], sort: [["y", "asc"]], columns: ["y"]}); + const view = await table.view({ + row_pivots: ["z"], + column_pivots: ["x"], + sort: [["y", "asc"]], + columns: ["y"], + }); setTimeout(() => table.replace(input)); let json = await view.to_json(); await new Promise(setTimeout); diff --git a/packages/perspective/test/js/constructors.js b/packages/perspective/test/js/constructors.js index 598b72e909..da2660ae75 100644 --- a/packages/perspective/test/js/constructors.js +++ b/packages/perspective/test/js/constructors.js @@ -14,33 +14,33 @@ const data = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; const col_data = { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }; const meta = { x: "integer", y: "string", - z: "boolean" + z: "boolean", }; const data_3 = [ {w: 1.5, x: 1, y: "a", z: true}, {w: 2.5, x: 2, y: "b", z: false}, {w: 3.5, x: 3, y: "c", z: true}, - {w: 4.5, x: 4, y: "d", z: false} + {w: 4.5, x: 4, y: "d", z: false}, ]; const data_7 = { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }; const int_in_string = [{a: "1"}, {a: "2"}, {a: "12345"}]; @@ -51,7 +51,7 @@ const meta_3 = { w: "float", x: "integer", y: "string", - z: "boolean" + z: "boolean", }; const arrow_result = [ @@ -67,7 +67,7 @@ const arrow_result = [ dict: "a", "datetime(ms)": +new Date("2018-01-25"), "datetime(us)": +new Date("2018-01-25"), - "datetime(ns)": +new Date("2018-01-25") + "datetime(ns)": +new Date("2018-01-25"), }, { f32: 2.5, @@ -81,7 +81,7 @@ const arrow_result = [ dict: "b", "datetime(ms)": +new Date("2018-01-26"), "datetime(us)": +new Date("2018-01-26"), - "datetime(ns)": +new Date("2018-01-26") + "datetime(ns)": +new Date("2018-01-26"), }, { f32: 3.5, @@ -95,7 +95,7 @@ const arrow_result = [ dict: "c", "datetime(ms)": +new Date("2018-01-27"), "datetime(us)": +new Date("2018-01-27"), - "datetime(ns)": +new Date("2018-01-27") + "datetime(ns)": +new Date("2018-01-27"), }, { f32: 4.5, @@ -109,7 +109,7 @@ const arrow_result = [ dict: "", "datetime(ms)": +new Date("2018-01-28"), "datetime(us)": +new Date("2018-01-28"), - "datetime(ns)": +new Date("2018-01-28") + "datetime(ns)": +new Date("2018-01-28"), }, { f32: null, @@ -123,8 +123,8 @@ const arrow_result = [ dict: null, "datetime(ms)": null, "datetime(us)": null, - "datetime(ns)": null - } + "datetime(ns)": null, + }, ]; let arrow_date_data = { @@ -159,7 +159,7 @@ let arrow_date_data = { "2019-01-28", "2019-01-29", "2019-01-30", - "2019-01-31" + "2019-01-31", ], "feb-2020": [ "2020-02-01", @@ -192,7 +192,7 @@ let arrow_date_data = { "2020-02-28", "2020-02-29", null, - null + null, ], "mar-2019": [ "2019-03-01", @@ -225,7 +225,7 @@ let arrow_date_data = { "2019-03-28", "2019-03-29", "2019-03-30", - "2019-03-31" + "2019-03-31", ], "apr-2020": [ "2020-04-01", @@ -258,13 +258,15 @@ let arrow_date_data = { "2020-04-28", "2020-04-29", "2020-04-30", - null - ] + null, + ], }; // transform arrow strings into timestamps for (const k in arrow_date_data) { - arrow_date_data[k] = arrow_date_data[k].map(d => (d ? new Date(d).getTime() : null)); + arrow_date_data[k] = arrow_date_data[k].map((d) => + d ? new Date(d).getTime() : null + ); } const dt = () => { @@ -288,35 +290,91 @@ const int_float_data = [ {int: 1, float: 2.25}, {int: 2, float: 3.5}, {int: 3, float: 4.75}, - {int: 4, float: 5.25} + {int: 4, float: 5.25}, ]; const int_float_string_data = [ {int: 1, float: 2.25, string: "a"}, {int: 2, float: 3.5, string: "b"}, {int: 3, float: 4.75, string: "c"}, - {int: 4, float: 5.25, string: "d"} + {int: 4, float: 5.25, string: "d"}, ]; // out of order to make sure we can read out of perspective in insertion // order, not pkey order. const all_types_data = [ - {int: 4, float: 5.25, string: "d", date: new Date(2020, 3, 15), datetime: new Date(2020, 0, 15, 23, 30), boolean: true}, - {int: 3, float: 4.75, string: "c", date: new Date(2020, 2, 15), datetime: new Date(2020, 0, 15, 18, 30), boolean: false}, - {int: 2, float: 3.5, string: "b", date: new Date(2020, 1, 15), datetime: new Date(2020, 0, 15, 12, 30), boolean: true}, - {int: 1, float: 2.25, string: "a", date: new Date(2020, 0, 15), datetime: new Date(2020, 0, 15, 6, 30), boolean: false}, + { + int: 4, + float: 5.25, + string: "d", + date: new Date(2020, 3, 15), + datetime: new Date(2020, 0, 15, 23, 30), + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: new Date(2020, 2, 15), + datetime: new Date(2020, 0, 15, 18, 30), + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: new Date(2020, 1, 15), + datetime: new Date(2020, 0, 15, 12, 30), + boolean: true, + }, + { + int: 1, + float: 2.25, + string: "a", + date: new Date(2020, 0, 15), + datetime: new Date(2020, 0, 15, 6, 30), + boolean: false, + }, // values above should be replaced with these values below, due to // indexing - {int: 4, float: 5.25, string: "d", date: new Date(2020, 3, 15), datetime: new Date(2020, 0, 15, 23, 30), boolean: true}, - {int: 3, float: 4.75, string: "c", date: new Date(2020, 2, 15), datetime: new Date(2020, 0, 15, 18, 30), boolean: false}, - {int: 2, float: 3.5, string: "b", date: new Date(2020, 1, 15), datetime: new Date(2020, 0, 15, 12, 30), boolean: true}, - {int: 1, float: 2.25, string: "a", date: new Date(2020, 0, 15), datetime: new Date(2020, 0, 15, 6, 30), boolean: false} + { + int: 4, + float: 5.25, + string: "d", + date: new Date(2020, 3, 15), + datetime: new Date(2020, 0, 15, 23, 30), + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: new Date(2020, 2, 15), + datetime: new Date(2020, 0, 15, 18, 30), + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: new Date(2020, 1, 15), + datetime: new Date(2020, 0, 15, 12, 30), + boolean: true, + }, + { + int: 1, + float: 2.25, + string: "a", + date: new Date(2020, 0, 15), + datetime: new Date(2020, 0, 15, 6, 30), + boolean: false, + }, ]; const datetime_data = [ {datetime: new Date(), int: 1}, {datetime: new Date(), int: 1}, {datetime: new Date(), int: 2}, - {datetime: new Date(), int: 2} + {datetime: new Date(), int: 2}, ]; // utility for checking typed arrays @@ -333,20 +391,20 @@ function validate_typed_array(typed_array, column_data) { return is_valid; } -module.exports = perspective => { - describe("Execute", function() { - it("serialized functions in a worker", async function() { +module.exports = (perspective) => { + describe("Execute", function () { + it("serialized functions in a worker", async function () { var table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); - table.execute(t => { + table.execute((t) => { t.update([ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); }); let view = await table.view({}); @@ -355,21 +413,21 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); view.delete(); table.delete(); }); }); - describe("Destructors", function() { - it("calls delete() on table with no views", async function() { + describe("Destructors", function () { + it("calls delete() on table with no views", async function () { let table = await perspective.table(data); await table.delete(); expect(true).toEqual(true); }); - it("calls delete on a view, then a table", async function() { + it("calls delete on a view, then a table", async function () { var table = await perspective.table(data); var view = await table.view(); await view.delete(); @@ -377,7 +435,7 @@ module.exports = perspective => { expect(true).toEqual(true); }); - it("calls delete on multiple views, then a table", async function() { + it("calls delete on multiple views, then a table", async function () { var table = await perspective.table(data); var view1 = await table.view(); var view2 = await table.view(); @@ -388,17 +446,21 @@ module.exports = perspective => { }); }); - describe("Schema", function() { - it("0-sided view", async function() { + describe("Schema", function () { + it("0-sided view", async function () { const table = await perspective.table(int_float_string_data); const view = await table.view(); const schema = await view.schema(); - expect(schema).toEqual({int: "integer", float: "float", string: "string"}); + expect(schema).toEqual({ + int: "integer", + float: "float", + string: "string", + }); view.delete(); table.delete(); }); - it("0-sided view with columns selection", async function() { + it("0-sided view with columns selection", async function () { const table = await perspective.table(int_float_string_data); const view = await table.view({columns: ["float", "string"]}); const schema = await view.schema(); @@ -408,20 +470,20 @@ module.exports = perspective => { }); }); - describe("Typed Arrays", function() { - it("Respects start/end rows", async function() { + describe("Typed Arrays", function () { + it("Respects start/end rows", async function () { var table = await perspective.table(int_float_data); var view = await table.view(); const result = await view.col_to_js_typed_array("int", { start_row: 1, - end_row: 2 + end_row: 2, }); expect(result[0].byteLength).toEqual(4); view.delete(); table.delete(); }); - it("Int, 0-sided view", async function() { + it("Int, 0-sided view", async function () { var table = await perspective.table(int_float_data); var view = await table.view(); const result = await view.col_to_js_typed_array("int"); @@ -430,7 +492,7 @@ module.exports = perspective => { table.delete(); }); - it("Float, 0-sided view", async function() { + it("Float, 0-sided view", async function () { var table = await perspective.table(int_float_data); var view = await table.view(); const result = await view.col_to_js_typed_array("float"); @@ -439,7 +501,7 @@ module.exports = perspective => { table.delete(); }); - it("Datetime, 0-sided view", async function() { + it("Datetime, 0-sided view", async function () { var table = await perspective.table(datetime_data); var view = await table.view(); var schema = await view.schema(); @@ -450,11 +512,11 @@ module.exports = perspective => { table.delete(); }); - it("Int, 1-sided view", async function() { + it("Int, 1-sided view", async function () { var table = await perspective.table(int_float_data); var view = await table.view({ row_pivots: ["int"], - columns: ["int", "float"] + columns: ["int", "float"], }); const result = await view.col_to_js_typed_array("int"); // should include aggregate row @@ -463,11 +525,11 @@ module.exports = perspective => { table.delete(); }); - it("Float, 1-sided view", async function() { + it("Float, 1-sided view", async function () { var table = await perspective.table(int_float_data); var view = await table.view({ row_pivots: ["int"], - columns: ["int", "float"] + columns: ["int", "float"], }); const result = await view.col_to_js_typed_array("float"); expect(result[0].byteLength).toEqual(40); @@ -475,12 +537,12 @@ module.exports = perspective => { table.delete(); }); - it("Datetime, 1-sided view", async function() { + it("Datetime, 1-sided view", async function () { var table = await perspective.table(datetime_data); var view = await table.view({ row_pivots: ["int"], columns: ["datetime"], - aggregates: {datetime: "high"} + aggregates: {datetime: "high"}, }); const result = await view.col_to_js_typed_array("datetime"); expect(result[0].byteLength).toEqual(24); @@ -488,12 +550,12 @@ module.exports = perspective => { table.delete(); }); - it("Int, 2-sided view with row pivot", async function() { + it("Int, 2-sided view with row pivot", async function () { var table = await perspective.table(int_float_data); var view = await table.view({ column_pivots: ["float"], row_pivots: ["int"], - columns: ["int", "float"] + columns: ["int", "float"], }); const result = await view.col_to_js_typed_array("3.5|int"); expect(result[0].byteLength).toEqual(20); @@ -501,12 +563,12 @@ module.exports = perspective => { table.delete(); }); - it("Float, 2-sided view with row pivot", async function() { + it("Float, 2-sided view with row pivot", async function () { var table = await perspective.table(int_float_data); var view = await table.view({ column_pivots: ["float"], row_pivots: ["int"], - columns: ["int", "float"] + columns: ["int", "float"], }); const result = await view.col_to_js_typed_array("3.5|float"); expect(result[0].byteLength).toEqual(40); @@ -514,7 +576,7 @@ module.exports = perspective => { table.delete(); }); - it("Int, 2-sided view, no row pivot", async function() { + it("Int, 2-sided view, no row pivot", async function () { var table = await perspective.table(int_float_data); var view = await table.view({column_pivots: ["float"]}); const result = await view.col_to_js_typed_array("3.5|int"); @@ -524,7 +586,7 @@ module.exports = perspective => { table.delete(); }); - it("Float, 2-sided view, no row pivot", async function() { + it("Float, 2-sided view, no row pivot", async function () { var table = await perspective.table(int_float_data); var view = await table.view({column_pivots: ["float"]}); const result = await view.col_to_js_typed_array("3.5|float"); @@ -533,7 +595,7 @@ module.exports = perspective => { table.delete(); }); - it("Symmetric output with to_columns, 0-sided", async function() { + it("Symmetric output with to_columns, 0-sided", async function () { let table = await perspective.table(int_float_data); let view = await table.view(); let cols = await view.to_columns(); @@ -543,18 +605,20 @@ module.exports = perspective => { let column = cols[col]; if (ta !== undefined && column !== undefined) { expect(ta[0].length).toEqual(cols[col].length); - expect(validate_typed_array(ta[0], cols[col])).toEqual(true); + expect(validate_typed_array(ta[0], cols[col])).toEqual( + true + ); } } view.delete(); table.delete(); }); - it("Symmetric output with to_columns, 1-sided", async function() { + it("Symmetric output with to_columns, 1-sided", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], - columns: ["int", "float"] + columns: ["int", "float"], }); let cols = await view.to_columns(); @@ -563,7 +627,9 @@ module.exports = perspective => { let column = cols[col]; if (ta !== undefined && column !== undefined) { expect(ta[0].length).toEqual(cols[col].length); - expect(validate_typed_array(ta[0], cols[col])).toEqual(true); + expect(validate_typed_array(ta[0], cols[col])).toEqual( + true + ); } } view.delete(); @@ -571,8 +637,8 @@ module.exports = perspective => { }); }); - describe("Other `View`s", function() { - it("Construct a table from another view", async function() { + describe("Other `View`s", function () { + it("Construct a table from another view", async function () { const table = await perspective.table(int_float_string_data); const view = await table.view(); const table2 = await perspective.table(view); @@ -586,54 +652,62 @@ module.exports = perspective => { }); }); - describe("Errors", function() { - it("Table constructor should throw an exception and reject promise", async function() { + describe("Errors", function () { + it("Table constructor should throw an exception and reject promise", async function () { expect.assertions(1); - perspective.table([1, 2, 3]).catch(error => { - expect(error.message).toEqual("Abort(): Cannot determine data types without column names!\n"); + perspective.table([1, 2, 3]).catch((error) => { + expect(error.message).toEqual( + "Abort(): Cannot determine data types without column names!\n" + ); }); }); - it("View constructor should throw an exception and reject promise", async function() { + it("View constructor should throw an exception and reject promise", async function () { expect.assertions(1); const table = await perspective.table(int_float_string_data); table .view({ - row_pivots: ["abcd"] + row_pivots: ["abcd"], }) - .catch(error => { - expect(error.message).toEqual("Abort(): Invalid column 'abcd' found in View row_pivots.\n"); + .catch((error) => { + expect(error.message).toEqual( + "Abort(): Invalid column 'abcd' found in View row_pivots.\n" + ); table.delete(); }); }); - it("Table constructor should throw an exception on await", async function() { + it("Table constructor should throw an exception on await", async function () { expect.assertions(1); try { await perspective.table([1, 2, 3]); } catch (error) { - expect(error.message).toEqual("Abort(): Cannot determine data types without column names!\n"); + expect(error.message).toEqual( + "Abort(): Cannot determine data types without column names!\n" + ); } }); - it("View constructor should throw an exception on await", async function() { + it("View constructor should throw an exception on await", async function () { expect.assertions(1); const table = await perspective.table(int_float_string_data); try { await table.view({ - row_pivots: ["abcd"] + row_pivots: ["abcd"], }); } catch (error) { - expect(error.message).toEqual("Abort(): Invalid column 'abcd' found in View row_pivots.\n"); + expect(error.message).toEqual( + "Abort(): Invalid column 'abcd' found in View row_pivots.\n" + ); table.delete(); } }); }); - describe("Formatters", function() { - it("Serializes a simple view to CSV", async function() { + describe("Formatters", function () { + it("Serializes a simple view to CSV", async function () { var table = await perspective.table(data); var view = await table.view({}); var answer = `x,y,z\r\n1,a,true\r\n2,b,false\r\n3,c,true\r\n4,d,false`; @@ -643,11 +717,11 @@ module.exports = perspective => { table.delete(); }); - it("Serializes 1 sided view to CSV", async function() { + it("Serializes 1 sided view to CSV", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], - columns: ["x"] + columns: ["x"], }); var answer = `__ROW_PATH__,x\r\n,10\r\nfalse,6\r\ntrue,4`; let result = await view.to_csv(); @@ -656,12 +730,12 @@ module.exports = perspective => { table.delete(); }); - it("Serializes a 2 sided view to CSV", async function() { + it("Serializes a 2 sided view to CSV", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], column_pivots: ["y"], - columns: ["x"] + columns: ["x"], }); var answer = `__ROW_PATH__,\"a,x\",\"b,x\",\"c,x\",\"d,x\"\r\n,1,2,3,4\r\nfalse,,2,,4\r\ntrue,1,,3,`; let result = await view.to_csv(); @@ -670,7 +744,7 @@ module.exports = perspective => { table.delete(); }); - it("Serializes a simple view to column-oriented JSON", async function() { + it("Serializes a simple view to column-oriented JSON", async function () { var table = await perspective.table(data_3); var view = await table.view({}); let result = await view.to_columns(); @@ -680,8 +754,8 @@ module.exports = perspective => { }); }); - describe("CSV parsing", function() { - it("Does not lose leading 0's when a CSV column is declared as a string", async function() { + describe("CSV parsing", function () { + it("Does not lose leading 0's when a CSV column is declared as a string", async function () { let table = await perspective.table({x: "string", y: "integer"}); table.update("x,y\n000123,000123"); let view = await table.view(); @@ -691,21 +765,23 @@ module.exports = perspective => { table.delete(); }); - it("Handles strings with quotation characters and commas", async function() { + it("Handles strings with quotation characters and commas", async function () { let table = await perspective.table({x: "string", y: "integer"}); table.update([ {x: "Test, hello!", y: 1}, {x: 'Test2"', y: 2}, - {x: 'Test3, Hello!"', y: 3} + {x: 'Test3, Hello!"', y: 3}, ]); let view = await table.view(); let result = await view.to_csv(); - expect(result).toEqual(`x,y\r\n"Test, hello!",1\r\nTest2",2\r\n"Test3, Hello!""",3`); + expect(result).toEqual( + `x,y\r\n"Test, hello!",1\r\nTest2",2\r\n"Test3, Hello!""",3` + ); view.delete(); table.delete(); }); - it("Transitively loads a CSV created from `to_csv()` on a table with a datetime column", async function() { + it("Transitively loads a CSV created from `to_csv()` on a table with a datetime column", async function () { // Assert that the CSV parser can handle POSIX timestamps. let table = await perspective.table(arrow_date_data); let view = await table.view(); @@ -723,8 +799,8 @@ module.exports = perspective => { }); }); - describe("Constructors", function() { - it("JSON constructor", async function() { + describe("Constructors", function () { + it("JSON constructor", async function () { var table = await perspective.table(data); var view = await table.view(); let result = await view.to_json(); @@ -733,7 +809,7 @@ module.exports = perspective => { table.delete(); }); - it("JSON column oriented constructor", async function() { + it("JSON column oriented constructor", async function () { var table = await perspective.table(col_data); var view = await table.view(); let result = await view.to_json(); @@ -742,7 +818,7 @@ module.exports = perspective => { table.delete(); }); - it("Arrow constructor", async function() { + it("Arrow constructor", async function () { var table = await perspective.table(arrows.test_null_arrow.slice()); var view = await table.view(); let result = await view.to_json(); @@ -751,7 +827,7 @@ module.exports = perspective => { table.delete(); }); - it("Arrow (chunked format) constructor", async function() { + it("Arrow (chunked format) constructor", async function () { var table = await perspective.table(arrows.chunked_arrow.slice()); var view = await table.view(); let result = await view.to_json(); @@ -760,7 +836,7 @@ module.exports = perspective => { table.delete(); }); - it("Arrow date32 constructor", async function() { + it("Arrow date32 constructor", async function () { const table = await perspective.table(arrows.date32_arrow.slice()); const view = await table.view(); const result = await view.to_columns(); @@ -769,7 +845,7 @@ module.exports = perspective => { table.delete(); }); - it("Arrow date64 constructor", async function() { + it("Arrow date64 constructor", async function () { const table = await perspective.table(arrows.date64_arrow.slice()); const view = await table.view(); const result = await view.to_columns(); @@ -778,66 +854,68 @@ module.exports = perspective => { table.delete(); }); - it("Arrow dictionary constructor", async function() { + it("Arrow dictionary constructor", async function () { const table = await perspective.table(arrows.dict_arrow.slice()); const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ a: ["abc", "def", "def", null, "abc"], - b: ["klm", "hij", null, "hij", "klm"] + b: ["klm", "hij", null, "hij", "klm"], }); view.delete(); table.delete(); }); - it("CSV constructor", async function() { + it("CSV constructor", async function () { var table = await perspective.table(csv); var view = await table.view(); let result = await view.to_json(); - expect(result).toEqual(papaparse.parse(csv, {header: true, dynamicTyping: true}).data); + expect(result).toEqual( + papaparse.parse(csv, {header: true, dynamicTyping: true}).data + ); view.delete(); table.delete(); }); - it("CSV constructor with null columns", async function() { + it("CSV constructor with null columns", async function () { const table = await perspective.table("x,y\n1,"); const view = await table.view(); expect(await table.schema()).toEqual({ x: "integer", - y: "string" + y: "string", }); const result = await view.to_columns(); expect(result).toEqual({ x: [1], - y: [null] + y: [null], }); await view.delete(); await table.delete(); }); - it("CSV constructor indexed, with null columns, and update", async function() { + it("CSV constructor indexed, with null columns, and update", async function () { const table = await perspective.table("x,y\n1,", {index: "x"}); const view = await table.view(); expect(await table.schema()).toEqual({ x: "integer", - y: "string" + y: "string", }); const result = await view.to_columns(); expect(result).toEqual({ x: [1], - y: [null] + y: [null], }); table.update("x,y\n1,abc\n2,123"); const result2 = await view.to_columns(); expect(result2).toEqual({ x: [1, 2], - y: ["abc", "123"] + y: ["abc", "123"], }); await view.delete(); await table.delete(); }); - it("Meta constructor", async function() { + it("Meta constructor", async function () { var table = await perspective.table(meta); var view = await table.view(); let result = await view.to_json(); @@ -846,7 +924,7 @@ module.exports = perspective => { table.delete(); }); - it("Handles floats", async function() { + it("Handles floats", async function () { var table = await perspective.table(data_3); var view = await table.view(); let result = await view.to_json(); @@ -855,7 +933,7 @@ module.exports = perspective => { table.delete(); }); - it("Infers ints wrapped in strings", async function() { + it("Infers ints wrapped in strings", async function () { var table = await perspective.table(int_in_string); var view = await table.view(); let result = await view.schema(); @@ -864,7 +942,7 @@ module.exports = perspective => { table.delete(); }); - it("Infers floats wrapped in strings", async function() { + it("Infers floats wrapped in strings", async function () { var table = await perspective.table(float_in_string); var view = await table.view(); let result = await view.schema(); @@ -873,12 +951,12 @@ module.exports = perspective => { table.delete(); }); - it("Infers correct type for empty string columns", async function() { + it("Infers correct type for empty string columns", async function () { var table = await perspective.table([ {x: "", y: 1}, {x: "", y: 2}, {x: "", y: 3}, - {x: "", y: 4} + {x: "", y: 4}, ]); var view = await table.view(); let result = await view.schema(); @@ -887,12 +965,12 @@ module.exports = perspective => { table.delete(); }); - it("Returns the correct number of rows for column-only views", async function() { + it("Returns the correct number of rows for column-only views", async function () { var table = await perspective.table(data); var view = await table.view(); var num_rows = await view.num_rows(); var view2 = await table.view({ - column_pivots: ["x"] + column_pivots: ["x"], }); var num_rows_col_only = await view2.num_rows(); expect(num_rows_col_only).toEqual(num_rows); @@ -901,23 +979,23 @@ module.exports = perspective => { table.delete(); }); - it.skip("Handles inconsistent rows with same width", async function() { + it.skip("Handles inconsistent rows with same width", async function () { const int_to_float = [ {x: 1, y: 2}, - {y: 2, z: 3} + {y: 2, z: 3}, ]; var table = await perspective.table(int_to_float); var view = await table.view(); var json = await view.to_json(); expect(json).toEqual([ {x: 1, y: 2, z: null}, - {x: null, y: 2, z: 3} + {x: null, y: 2, z: 3}, ]); view.delete(); table.delete(); }); - it.skip("Handles inconsistent rows", async function() { + it.skip("Handles inconsistent rows", async function () { const int_to_float = [{x: 1}, {y: 2, z: 3}]; var table = await perspective.table(int_to_float); var schema = await table.schema(); @@ -926,15 +1004,15 @@ module.exports = perspective => { var json = await view.to_json(); expect(json).toEqual([ {x: 1, y: null, z: null}, - {x: null, y: 2, z: 3} + {x: null, y: 2, z: 3}, ]); view.delete(); table.delete(); }); - it("Upgrades integer columns to strings", async function() { + it("Upgrades integer columns to strings", async function () { const int_to_float = { - a: [1, 2, 3, "x", "y"] + a: [1, 2, 3, "x", "y"], }; var table = await perspective.table(int_to_float); @@ -942,14 +1020,20 @@ module.exports = perspective => { expect(schema_1["a"]).toEqual("string"); var view = await table.view(); var json = await view.to_json(); - expect(json).toEqual([{a: "1"}, {a: "2"}, {a: "3"}, {a: "x"}, {a: "y"}]); + expect(json).toEqual([ + {a: "1"}, + {a: "2"}, + {a: "3"}, + {a: "x"}, + {a: "y"}, + ]); view.delete(); table.delete(); }); - it("Upgrades integer columns with values beyond max/min_int to float", async function() { + it("Upgrades integer columns with values beyond max/min_int to float", async function () { const int_to_float = { - a: [1, 2, 3, 2147483667, 5] + a: [1, 2, 3, 2147483667, 5], }; var table = await perspective.table(int_to_float); @@ -965,13 +1049,13 @@ module.exports = perspective => { // This currently won't work, and I'm unclear we want it to - upgrading // ths column in place is easy, but once the gnode and potentially // contexts have been created, this becomes much more difficult. - it.skip("Upgrades integer columns with values beyond max/min_int to float", async function() { + it.skip("Upgrades integer columns with values beyond max/min_int to float", async function () { const schema = { - a: "integer" + a: "integer", }; const int_to_float = { - a: [1, 2, 3, 2147483667, 5] + a: [1, 2, 3, 2147483667, 5], }; var table = await perspective.table(schema); @@ -985,7 +1069,7 @@ module.exports = perspective => { table.delete(); }); - it("Does not infer float column as integers", async function() { + it("Does not infer float column as integers", async function () { const int_to_float = []; for (let x = 0; x < 200; x++) { int_to_float.push({a: 1}); @@ -1003,42 +1087,42 @@ module.exports = perspective => { table.delete(); }); - it("has correct size", async function() { + it("has correct size", async function () { var table = await perspective.table(data); let result = await table.size(); expect(result).toEqual(4); table.delete(); }); - it("has a schema", async function() { + it("has a schema", async function () { var table = await perspective.table(data); let result = await table.schema(); expect(result).toEqual(meta); table.delete(); }); - it("has columns", async function() { + it("has columns", async function () { var table = await perspective.table(data); let result = await table.columns(); expect(result).toEqual(["x", "y", "z"]); table.delete(); }); - it("Handles floats schemas", async function() { + it("Handles floats schemas", async function () { var table = await perspective.table(data_3); let result = await table.schema(); expect(result).toEqual(meta_3); table.delete(); }); - it("Generates correct date schemas", async function() { + it("Generates correct date schemas", async function () { var table = await perspective.table(data_4); let result = await table.schema(); expect(result).toEqual(meta_4); table.delete(); }); - it("Handles date updates when constructed from a schema", async function() { + it("Handles date updates when constructed from a schema", async function () { var table = await perspective.table(meta_4); table.update(data_4); let view = await table.view(); @@ -1048,7 +1132,7 @@ module.exports = perspective => { table.delete(); }); - it("Handles datetime values", async function() { + it("Handles datetime values", async function () { var table = await perspective.table(data_4); let view = await table.view(); let result = await view.to_json(); @@ -1057,34 +1141,46 @@ module.exports = perspective => { table.delete(); }); - it("Handles datetime strings", async function() { + it("Handles datetime strings", async function () { var table = await perspective.table(data_5); let view = await table.view(); let result = await view.to_json(); - expect(result).toEqual([{v: +moment(data_5[0]["v"], "MM-DD-YYYY")}]); + expect(result).toEqual([ + {v: +moment(data_5[0]["v"], "MM-DD-YYYY")}, + ]); view.delete(); table.delete(); }); - it.skip("Handles datetime strings in US locale string", async function() { + it.skip("Handles datetime strings in US locale string", async function () { // FIXME: 1/1/2020, 12:30:45 PM = 1/1/2020, 7:30:45 AM UTC, but // because C++ strptime in Emscripten parses the time as 12:30:45AM, // the output is 12/31/2019 19:30:45 PM UTC. This is clearly wrong. const data = { - x: ["1/1/2020, 12:30:45 PM", "03/15/2020, 11:30:45 AM", "06/30/2020, 01:30:45 AM", "12/31/2020, 11:59:59 PM"] + x: [ + "1/1/2020, 12:30:45 PM", + "03/15/2020, 11:30:45 AM", + "06/30/2020, 01:30:45 AM", + "12/31/2020, 11:59:59 PM", + ], }; const table = await perspective.table(data); const schema = await table.schema(); expect(schema).toEqual({ - x: "datetime" + x: "datetime", }); const view = await table.view(); const result = await view.to_columns(); const expected = { - x: ["1/1/2020, 12:30:45 PM", "03/15/2020, 11:30:45 AM", "06/30/2020, 01:30:45 AM", "12/31/2020, 11:59:59 PM"].map(v => new Date(v).getTime()) + x: [ + "1/1/2020, 12:30:45 PM", + "03/15/2020, 11:30:45 AM", + "06/30/2020, 01:30:45 AM", + "12/31/2020, 11:59:59 PM", + ].map((v) => new Date(v).getTime()), }; expect(result).toEqual(expected); @@ -1092,22 +1188,32 @@ module.exports = perspective => { table.delete(); }); - it("Handles datetime strings in US locale string", async function() { + it("Handles datetime strings in US locale string", async function () { const data = { - x: ["1/1/2020, 05:30:45 AM", "03/15/2020, 11:30:45 AM", "06/30/2020, 01:30:45 AM", "12/31/2020, 11:59:59 PM"] + x: [ + "1/1/2020, 05:30:45 AM", + "03/15/2020, 11:30:45 AM", + "06/30/2020, 01:30:45 AM", + "12/31/2020, 11:59:59 PM", + ], }; const table = await perspective.table(data); const schema = await table.schema(); expect(schema).toEqual({ - x: "datetime" + x: "datetime", }); const view = await table.view(); const result = await view.to_columns(); const expected = { - x: ["1/1/2020, 05:30:45 AM", "03/15/2020, 11:30:45 AM", "06/30/2020, 01:30:45 AM", "12/31/2020, 11:59:59 PM"].map(v => new Date(v).getTime()) + x: [ + "1/1/2020, 05:30:45 AM", + "03/15/2020, 11:30:45 AM", + "06/30/2020, 01:30:45 AM", + "12/31/2020, 11:59:59 PM", + ].map((v) => new Date(v).getTime()), }; expect(result).toEqual(expected); @@ -1115,17 +1221,25 @@ module.exports = perspective => { table.delete(); }); - it("Handles datetime values with mixed formats", async function() { + it("Handles datetime values with mixed formats", async function () { var table = await perspective.table({datetime: "datetime"}); - table.update([{datetime: new Date(1549257586108)}, {datetime: "2019-01-30"}, {datetime: 11}]); + table.update([ + {datetime: new Date(1549257586108)}, + {datetime: "2019-01-30"}, + {datetime: 11}, + ]); let view = await table.view(); let result = await view.to_json(); - expect(result).toEqual([{datetime: 1549257586108}, {datetime: 1548806400000}, {datetime: 11}]); + expect(result).toEqual([ + {datetime: 1549257586108}, + {datetime: 1548806400000}, + {datetime: 11}, + ]); view.delete(); table.delete(); }); - it("Handles date values", async function() { + it("Handles date values", async function () { var table = await perspective.table({v: "date"}); table.update(data_4); let view = await table.view(); @@ -1140,7 +1254,7 @@ module.exports = perspective => { table.delete(); }); - it("Handles utf16 column names", async function() { + it("Handles utf16 column names", async function () { var table = await perspective.table({š: [1, 2, 3]}); let view = await table.view({}); let result = await view.schema(); @@ -1149,7 +1263,7 @@ module.exports = perspective => { table.delete(); }); - it("Handles utf16", async function() { + it("Handles utf16", async function () { var table = await perspective.table(data_6); let view = await table.view({}); let result = await view.to_json(); @@ -1158,52 +1272,71 @@ module.exports = perspective => { table.delete(); }); - describe("Datetime constructors", function() { - it("Correctly parses an ISO-8601 formatted string", async function() { - let table = await perspective.table({d: ["2011-10-05T14:48:00"]}); + describe("Datetime constructors", function () { + it("Correctly parses an ISO-8601 formatted string", async function () { + let table = await perspective.table({ + d: ["2011-10-05T14:48:00"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it("Correctly parses an ISO-8601 formatted string 2", async function() { - let table = await perspective.table({d: ["2011-10-05T14:48:00.000"]}); + it("Correctly parses an ISO-8601 formatted string 2", async function () { + let table = await perspective.table({ + d: ["2011-10-05T14:48:00.000"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it("Correctly parses an ISO-8601 formatted string 3", async function() { - let table = await perspective.table({d: ["2011-10-05T14:48:00Z"]}); + it("Correctly parses an ISO-8601 formatted string 3", async function () { + let table = await perspective.table({ + d: ["2011-10-05T14:48:00Z"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it("Correctly parses an ISO-8601 formatted string 3", async function() { - let table = await perspective.table({d: ["2011-10-05T14:48:00.000Z"]}); + it("Correctly parses an ISO-8601 formatted string 3", async function () { + let table = await perspective.table({ + d: ["2011-10-05T14:48:00.000Z"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it("Correctly parses an ISO-8601 formatted string with timezone", async function() { - let table = await perspective.table({d: ["2008-09-15T15:53:00+05:00"]}); + it("Correctly parses an ISO-8601 formatted string with timezone", async function () { + let table = await perspective.table({ + d: ["2008-09-15T15:53:00+05:00"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it.skip("Correctly parses an RFC 2822 formatted string", async function() { - let table = await perspective.table({d: ["Wed, 05 Oct 2011 22:26:12 -0400"]}); + it.skip("Correctly parses an RFC 2822 formatted string", async function () { + let table = await perspective.table({ + d: ["Wed, 05 Oct 2011 22:26:12 -0400"], + }); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); // Not all formats covered by JS parser, test intended for C++ parser - it.skip("Correctly parses all m-d-y formatted strings", async function() { - let datestrings = ["08-15-2009", "08/15/2009", "08-15-2009", "02 28 2009", "08/15/10", "31 08 2009"]; + it.skip("Correctly parses all m-d-y formatted strings", async function () { + let datestrings = [ + "08-15-2009", + "08/15/2009", + "08-15-2009", + "02 28 2009", + "08/15/10", + "31 08 2009", + ]; for (let str of datestrings) { let table = await perspective.table({d: [str]}); let view = await table.view({}); @@ -1213,21 +1346,21 @@ module.exports = perspective => { }); // Only implemented in the C++ date parser - skip - it.skip("Correctly parses a 'dd mm yyyy' formatted string", async function() { + it.skip("Correctly parses a 'dd mm yyyy' formatted string", async function () { let table = await perspective.table({d: ["15 08 08"]}); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("datetime"); }); - it("Does not (for now) parse a date string in non-US formatting", async function() { + it("Does not (for now) parse a date string in non-US formatting", async function () { let table = await perspective.table({d: ["2018/07/30"]}); let view = await table.view({}); let result = await view.schema(); expect(result["d"]).toEqual("string"); }); - it("Does not mistakenly parse a date-like string", async function() { + it("Does not mistakenly parse a date-like string", async function () { let table = await perspective.table({d: ["Jan 14, 14"]}); let view = await table.view({}); let result = await view.schema(); @@ -1235,16 +1368,30 @@ module.exports = perspective => { }); }); - it("allocates a large tables", async function() { + it("allocates a large tables", async function () { function makeid() { var text = ""; - var possible = Array.from(Array(26).keys()).map(x => String.fromCharCode(x + 65)); - for (var i = 0; i < 15; i++) text += possible[Math.floor(Math.random() * possible.length)]; + var possible = Array.from(Array(26).keys()).map((x) => + String.fromCharCode(x + 65) + ); + for (var i = 0; i < 15; i++) + text += + possible[Math.floor(Math.random() * possible.length)]; return text; } let data = []; for (let i = 0; i < 35000; i++) { - data.push([{a: makeid(), b: makeid(), c: makeid(), d: makeid(), w: i + 0.5, x: i, y: makeid()}]); + data.push([ + { + a: makeid(), + b: makeid(), + c: makeid(), + d: makeid(), + w: i + 0.5, + x: i, + y: makeid(), + }, + ]); } let table = await perspective.table(data); let view = await table.view(); @@ -1254,86 +1401,236 @@ module.exports = perspective => { table.delete(); }, 3000); - describe("get_index/get_limit", function() { - it("get_index() on unindexed table", async function() { + describe("get_index/get_limit", function () { + it("get_index() on unindexed table", async function () { const table = await perspective.table(data); expect(await table.get_index()).toBeNull(); }); - it("get_index() on an indexed table", async function() { + it("get_index() on an indexed table", async function () { const table = await perspective.table(data, {index: "x"}); expect(await table.get_index()).toEqual("x"); }); - it("get_limit() on table without limit", async function() { + it("get_limit() on table without limit", async function () { const table = await perspective.table(data); expect(await table.get_limit()).toBeNull(); }); - it("get_limit() on table with limit", async function() { + it("get_limit() on table with limit", async function () { const table = await perspective.table(data, {limit: 2}); expect(await table.get_limit()).toEqual(2); }); }); - describe("Indexed table constructors", function() { - it("Should index on an integer column", async function() { - const table = await perspective.table(all_types_data, {index: "int"}); + describe("Indexed table constructors", function () { + it("Should index on an integer column", async function () { + const table = await perspective.table(all_types_data, { + index: "int", + }); const view = await table.view(); expect(await table.size()).toEqual(4); expect(await view.to_json()).toEqual([ - {int: 1, float: 2.25, string: "a", date: 1579046400000, datetime: 1579069800000, boolean: false}, - {int: 2, float: 3.5, string: "b", date: 1581724800000, datetime: 1579091400000, boolean: true}, - {int: 3, float: 4.75, string: "c", date: 1584230400000, datetime: 1579113000000, boolean: false}, - {int: 4, float: 5.25, string: "d", date: 1586908800000, datetime: 1579131000000, boolean: true} + { + int: 1, + float: 2.25, + string: "a", + date: 1579046400000, + datetime: 1579069800000, + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: 1581724800000, + datetime: 1579091400000, + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: 1584230400000, + datetime: 1579113000000, + boolean: false, + }, + { + int: 4, + float: 5.25, + string: "d", + date: 1586908800000, + datetime: 1579131000000, + boolean: true, + }, ]); }); - it("Should index on a float column", async function() { - const table = await perspective.table(all_types_data, {index: "float"}); + it("Should index on a float column", async function () { + const table = await perspective.table(all_types_data, { + index: "float", + }); const view = await table.view(); expect(await table.size()).toEqual(4); expect(await view.to_json()).toEqual([ - {int: 1, float: 2.25, string: "a", date: 1579046400000, datetime: 1579069800000, boolean: false}, - {int: 2, float: 3.5, string: "b", date: 1581724800000, datetime: 1579091400000, boolean: true}, - {int: 3, float: 4.75, string: "c", date: 1584230400000, datetime: 1579113000000, boolean: false}, - {int: 4, float: 5.25, string: "d", date: 1586908800000, datetime: 1579131000000, boolean: true} + { + int: 1, + float: 2.25, + string: "a", + date: 1579046400000, + datetime: 1579069800000, + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: 1581724800000, + datetime: 1579091400000, + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: 1584230400000, + datetime: 1579113000000, + boolean: false, + }, + { + int: 4, + float: 5.25, + string: "d", + date: 1586908800000, + datetime: 1579131000000, + boolean: true, + }, ]); }); - it("Should index on a string column", async function() { - const table = await perspective.table(all_types_data, {index: "string"}); + it("Should index on a string column", async function () { + const table = await perspective.table(all_types_data, { + index: "string", + }); const view = await table.view(); expect(await table.size()).toEqual(4); expect(await view.to_json()).toEqual([ - {int: 1, float: 2.25, string: "a", date: 1579046400000, datetime: 1579069800000, boolean: false}, - {int: 2, float: 3.5, string: "b", date: 1581724800000, datetime: 1579091400000, boolean: true}, - {int: 3, float: 4.75, string: "c", date: 1584230400000, datetime: 1579113000000, boolean: false}, - {int: 4, float: 5.25, string: "d", date: 1586908800000, datetime: 1579131000000, boolean: true} + { + int: 1, + float: 2.25, + string: "a", + date: 1579046400000, + datetime: 1579069800000, + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: 1581724800000, + datetime: 1579091400000, + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: 1584230400000, + datetime: 1579113000000, + boolean: false, + }, + { + int: 4, + float: 5.25, + string: "d", + date: 1586908800000, + datetime: 1579131000000, + boolean: true, + }, ]); }); - it("Should index on a date column", async function() { - const table = await perspective.table(all_types_data, {index: "date"}); + it("Should index on a date column", async function () { + const table = await perspective.table(all_types_data, { + index: "date", + }); const view = await table.view(); expect(await table.size()).toEqual(4); expect(await view.to_json()).toEqual([ - {int: 1, float: 2.25, string: "a", date: 1579046400000, datetime: 1579069800000, boolean: false}, - {int: 2, float: 3.5, string: "b", date: 1581724800000, datetime: 1579091400000, boolean: true}, - {int: 3, float: 4.75, string: "c", date: 1584230400000, datetime: 1579113000000, boolean: false}, - {int: 4, float: 5.25, string: "d", date: 1586908800000, datetime: 1579131000000, boolean: true} + { + int: 1, + float: 2.25, + string: "a", + date: 1579046400000, + datetime: 1579069800000, + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: 1581724800000, + datetime: 1579091400000, + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: 1584230400000, + datetime: 1579113000000, + boolean: false, + }, + { + int: 4, + float: 5.25, + string: "d", + date: 1586908800000, + datetime: 1579131000000, + boolean: true, + }, ]); }); - it("Should index on a datetime column", async function() { - const table = await perspective.table(all_types_data, {index: "datetime"}); + it("Should index on a datetime column", async function () { + const table = await perspective.table(all_types_data, { + index: "datetime", + }); const view = await table.view(); expect(await table.size()).toEqual(4); expect(await view.to_json()).toEqual([ - {int: 1, float: 2.25, string: "a", date: 1579046400000, datetime: 1579069800000, boolean: false}, - {int: 2, float: 3.5, string: "b", date: 1581724800000, datetime: 1579091400000, boolean: true}, - {int: 3, float: 4.75, string: "c", date: 1584230400000, datetime: 1579113000000, boolean: false}, - {int: 4, float: 5.25, string: "d", date: 1586908800000, datetime: 1579131000000, boolean: true} + { + int: 1, + float: 2.25, + string: "a", + date: 1579046400000, + datetime: 1579069800000, + boolean: false, + }, + { + int: 2, + float: 3.5, + string: "b", + date: 1581724800000, + datetime: 1579091400000, + boolean: true, + }, + { + int: 3, + float: 4.75, + string: "c", + date: 1584230400000, + datetime: 1579113000000, + boolean: false, + }, + { + int: 4, + float: 5.25, + string: "d", + date: 1586908800000, + datetime: 1579131000000, + boolean: true, + }, ]); }); }); diff --git a/packages/perspective/test/js/delete.js b/packages/perspective/test/js/delete.js index b932aac96e..00e508c8ae 100644 --- a/packages/perspective/test/js/delete.js +++ b/packages/perspective/test/js/delete.js @@ -7,9 +7,9 @@ * */ -module.exports = perspective => { - describe("Delete", function() { - it("calls all delete callbacks registered on table", async function() { +module.exports = (perspective) => { + describe("Delete", function () { + it("calls all delete callbacks registered on table", async function () { const table = await perspective.table([{x: 1}]); const cb1 = jest.fn(); @@ -24,7 +24,7 @@ module.exports = perspective => { expect(cb2).toHaveBeenCalledTimes(1); }); - it("remove_delete unregisters table delete callbacks", async function() { + it("remove_delete unregisters table delete callbacks", async function () { const table = await perspective.table([{x: 1}]); const cb1 = jest.fn(); @@ -40,7 +40,7 @@ module.exports = perspective => { expect(cb2).toHaveBeenCalledTimes(1); }); - it("calls all delete callbacks registered on view", async function() { + it("calls all delete callbacks registered on view", async function () { const table = await perspective.table([{x: 1}]); const view = await table.view(); @@ -58,7 +58,7 @@ module.exports = perspective => { await table.delete(); }); - it("remove_delete unregisters view delete callbacks", async function() { + it("remove_delete unregisters view delete callbacks", async function () { const table = await perspective.table([{x: 1}]); const view = await table.view(); @@ -77,7 +77,7 @@ module.exports = perspective => { await table.delete(); }); - it("properly removes a failed delete callback on a table", async function(done) { + it("properly removes a failed delete callback on a table", async function (done) { const table = await perspective.table([{x: 1}]); // when a callback throws, it should delete that callback @@ -89,7 +89,7 @@ module.exports = perspective => { done(); }); - it("properly removes a failed delete callback on a view", async function(done) { + it("properly removes a failed delete callback on a view", async function (done) { const table = await perspective.table([{x: 1}]); const view = await table.view(); diff --git a/packages/perspective/test/js/delta.js b/packages/perspective/test/js/delta.js index 869671b46e..bf3d9a4b89 100644 --- a/packages/perspective/test/js/delta.js +++ b/packages/perspective/test/js/delta.js @@ -13,27 +13,32 @@ let data = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; let partial_change_y = [ {x: 1, y: "string1"}, - {x: 2, y: "string2"} + {x: 2, y: "string2"}, ]; let partial_change_z = [ {x: 1, z: false}, - {x: 2, z: true} + {x: 2, z: true}, ]; let partial_change_y_z = [ {x: 1, y: "string1", z: false}, - {x: 2, y: "string2", z: true} + {x: 2, y: "string2", z: true}, ]; let partial_change_nonseq = [ {x: 1, y: "string1", z: false}, - {x: 4, y: "string2", z: true} + {x: 4, y: "string2", z: true}, ]; -async function match_delta(perspective, delta, expected, formatter = "to_json") { +async function match_delta( + perspective, + delta, + expected, + formatter = "to_json" +) { const table = await perspective.table(delta); const view = await table.view(); const result = await view[formatter](); @@ -42,17 +47,17 @@ async function match_delta(perspective, delta, expected, formatter = "to_json") await table.delete(); } -module.exports = perspective => { - describe("Row delta", function() { - describe("0-sided row delta", function() { - it("returns changed rows", async function(done) { +module.exports = (perspective) => { + describe("Row delta", function () { + describe("0-sided row delta", function () { + it("returns changed rows", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "string1", z: true}, - {x: 2, y: "string2", z: false} + {x: 2, y: "string2", z: false}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -64,12 +69,15 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows, hidden sort", async function(done) { + it("returns changed rows, hidden sort", async function (done) { let table = await perspective.table(data, {index: "x"}); - let view = await table.view({columns: ["x"], sort: [["y", "desc"]]}); + let view = await table.view({ + columns: ["x"], + sort: [["y", "desc"]], + }); console.log(await view.to_json()); view.on_update( - async function(updated) { + async function (updated) { const expected = [{x: 2}, {x: 1}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -81,22 +89,22 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows from schema", async function(done) { + it("returns changed rows from schema", async function (done) { let table = await perspective.table( { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "d", z: false}, {x: 2, y: "b", z: false}, - {x: 3, y: "c", z: true} + {x: 3, y: "c", z: true}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -109,18 +117,18 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 1, y: "d", z: false} + {x: 1, y: "d", z: false}, ]); }); - it("returns added rows", async function(done) { + it("returns added rows", async function (done) { let table = await perspective.table(data); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "string1", z: null}, - {x: 2, y: "string2", z: null} + {x: 2, y: "string2", z: null}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -132,15 +140,15 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns added rows from schema", async function(done) { + it("returns added rows from schema", async function (done) { let table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, data); view.delete(); table.delete(); @@ -151,14 +159,14 @@ module.exports = perspective => { table.update(data); }); - it("returns deleted columns", async function(done) { + it("returns deleted columns", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: null, z: true}, - {x: 4, y: null, z: false} + {x: 4, y: null, z: false}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -169,20 +177,20 @@ module.exports = perspective => { ); table.update([ {x: 1, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns changed rows in sorted context", async function(done) { + it("returns changed rows in sorted context", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 2, y: "string2", z: false}, - {x: 1, y: "string1", z: true} + {x: 1, y: "string1", z: true}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -194,25 +202,25 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows in sorted context from schema", async function(done) { + it("returns changed rows in sorted context from schema", async function (done) { let table = await perspective.table( { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); let view = await table.view({ - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 4, y: "a", z: true}, {x: 3, y: "c", z: true}, {x: 2, y: "b", z: false}, - {x: 1, y: "d", z: false} + {x: 1, y: "d", z: false}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -226,22 +234,24 @@ module.exports = perspective => { {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, {x: 1, y: "d", z: false}, - {x: 4, y: "a", z: true} + {x: 4, y: "a", z: true}, ]); }); - it("returns added rows in filtered context from schema", async function(done) { + it("returns added rows in filtered context from schema", async function (done) { let table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); let view = await table.view({ - filter: [["x", ">", 3]] + filter: [["x", ">", 3]], }); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, [{x: 4, y: "d", z: false}]); + async function (updated) { + await match_delta(perspective, updated.delta, [ + {x: 4, y: "d", z: false}, + ]); view.delete(); table.delete(); done(); @@ -251,11 +261,11 @@ module.exports = perspective => { table.update(data); }); - it("returns changed rows in non-sequential update", async function(done) { + it("returns changed rows in non-sequential update", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { const expected = partial_change_nonseq; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -268,18 +278,18 @@ module.exports = perspective => { }); }); - describe("0-sided row delta, randomized column order", function() { - it("returns changed rows", async function(done) { + describe("0-sided row delta, randomized column order", function () { + it("returns changed rows", async function (done) { let table = await perspective.table(data, {index: "x"}); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "string1", z: true}, - {x: 2, y: "string2", z: false} + {x: 2, y: "string2", z: false}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -291,25 +301,25 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows from schema", async function(done) { + it("returns changed rows from schema", async function (done) { let table = await perspective.table( { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "d", z: false}, {x: 2, y: "b", z: false}, - {x: 3, y: "c", z: true} + {x: 3, y: "c", z: true}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -322,21 +332,21 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 1, y: "d", z: false} + {x: 1, y: "d", z: false}, ]); }); - it("returns added rows", async function(done) { + it("returns added rows", async function (done) { let table = await perspective.table(data); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "string1", z: null}, - {x: 2, y: "string2", z: null} + {x: 2, y: "string2", z: null}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -348,14 +358,14 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns added rows, hidden sort", async function(done) { + it("returns added rows, hidden sort", async function (done) { let table = await perspective.table(data); let view = await table.view({ columns: ["x"], - sort: [["y", "desc"]] + sort: [["y", "desc"]], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{x: 2}, {x: 1}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -367,18 +377,18 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns added rows from schema", async function(done) { + it("returns added rows from schema", async function (done) { let table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, data); view.delete(); table.delete(); @@ -389,17 +399,17 @@ module.exports = perspective => { table.update(data); }); - it("returns deleted columns", async function(done) { + it("returns deleted columns", async function (done) { let table = await perspective.table(data, {index: "x"}); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: null, z: true}, - {x: 4, y: null, z: false} + {x: 4, y: null, z: false}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -410,18 +420,18 @@ module.exports = perspective => { ); table.update([ {x: 1, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns changed rows in non-sequential update", async function(done) { + it("returns changed rows in non-sequential update", async function (done) { let table = await perspective.table(data, {index: "x"}); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); view.on_update( - async function(updated) { + async function (updated) { const expected = partial_change_nonseq; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -434,14 +444,14 @@ module.exports = perspective => { }); }); - describe("0-sided row delta, column order subset", function() { - it("returns changed rows", async function(done) { + describe("0-sided row delta, column order subset", function () { + it("returns changed rows", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{y: "string1"}, {y: "string2"}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -453,20 +463,20 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows from schema", async function(done) { + it("returns changed rows from schema", async function (done) { let table = await perspective.table( { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); let view = await table.view({ - columns: ["z"] + columns: ["z"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{z: false}, {z: false}, {z: true}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -479,17 +489,17 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 1, y: "d", z: false} + {x: 1, y: "d", z: false}, ]); }); - it("returns added rows", async function(done) { + it("returns added rows", async function (done) { let table = await perspective.table(data); let view = await table.view({ - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{y: "string1"}, {y: "string2"}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -501,18 +511,23 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns added rows from schema", async function(done) { + it("returns added rows from schema", async function (done) { let table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); let view = await table.view({ - columns: ["z"] + columns: ["z"], }); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, [{z: true}, {z: false}, {z: true}, {z: false}]); + async function (updated) { + await match_delta(perspective, updated.delta, [ + {z: true}, + {z: false}, + {z: true}, + {z: false}, + ]); view.delete(); table.delete(); done(); @@ -522,13 +537,13 @@ module.exports = perspective => { table.update(data); }); - it("returns deleted rows", async function(done) { + it("returns deleted rows", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{y: null}, {y: null}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -539,18 +554,21 @@ module.exports = perspective => { ); table.update([ {x: 1, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns changed rows in non-sequential update", async function(done) { + it("returns changed rows in non-sequential update", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, [{y: "string1"}, {y: "string2"}]); + async function (updated) { + await match_delta(perspective, updated.delta, [ + {y: "string1"}, + {y: "string2"}, + ]); view.delete(); table.delete(); done(); @@ -561,18 +579,18 @@ module.exports = perspective => { }); }); - describe("1-sided row delta", function() { - it("returns changed rows", async function(done) { + describe("1-sided row delta", function () { + it("returns changed rows", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: 1, z: 1}, - {x: 2, y: 1, z: 1} + {x: 2, y: 1, z: 1}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -584,7 +602,7 @@ module.exports = perspective => { table.update(partial_change_y); }); - it.skip("returns changed rows, unique", async function(done) { + it.skip("returns changed rows, unique", async function (done) { // FIXME: the delta doesn't seem to trigger if the // cell is invalidated, only if the actual values are // different. This feels off behavior-wise. @@ -592,29 +610,29 @@ module.exports = perspective => { { x: [1, 2, 3, 4], y: ["a", "a", "a", "a"], - z: [100, 200, 100, 200] + z: [100, 200, 100, 200], }, {index: "x"} ); const view = await table.view({ row_pivots: ["z"], - aggregates: {y: "unique"} + aggregates: {y: "unique"}, }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], [100], [200]], x: [10, 4, 6], y: ["a", "a", "a"], - z: [600, 200, 400] + z: [600, 200, 400], }); view.on_update( - async function(updated) { + async function (updated) { console.log(await view.to_columns()); const expected = [ {x: 10, y: null, z: 600}, // total - {x: 6, y: null, z: 400} + {x: 6, y: null, z: 400}, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -627,19 +645,19 @@ module.exports = perspective => { table.update({ x: [4], y: ["a"], - z: [200] + z: [200], }); }); - it("returns changed rows, column range", async function(done) { + it("returns changed rows, column range", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{x: 1}, {x: 2}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -651,14 +669,14 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns nothing when updated data is not in pivot", async function(done) { + it("returns nothing when updated data is not in pivot", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, []); view.delete(); table.delete(); @@ -669,18 +687,18 @@ module.exports = perspective => { table.update(partial_change_z); }); - it("returns added rows", async function(done) { + it("returns added rows", async function (done) { let table = await perspective.table(data); let view = await table.view({ row_pivots: ["y"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 13, y: 6, z: 3}, {x: 1, y: 1, z: 1}, - {x: 2, y: 1, z: 1} + {x: 2, y: 1, z: 1}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -692,15 +710,15 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns added rows, column range", async function(done) { + it("returns added rows, column range", async function (done) { let table = await perspective.table(data); let view = await table.view({ row_pivots: ["y"], columns: ["z"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{z: 3}, {z: 1}, {z: 1}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -712,14 +730,14 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns deleted columns", async function(done) { + it("returns deleted columns", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { // underlying data changes, but only total aggregate row is affected const expected = [{x: 10, y: 3, z: 2}]; await match_delta(perspective, updated.delta, expected); @@ -731,22 +749,22 @@ module.exports = perspective => { ); table.update([ {x: 1, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns changed rows in non-sequential update", async function(done) { + it("returns changed rows in non-sequential update", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { // aggregates are sorted, in this case by string comparator - "string1" and "string2" are at the end const expected = [ {x: 1, y: 1, z: 1}, - {x: 4, y: 1, z: 1} + {x: 4, y: 1, z: 1}, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -758,19 +776,19 @@ module.exports = perspective => { table.update(partial_change_nonseq); }); - it("Returns appended rows, hidden sort", async function(done) { + it("Returns appended rows, hidden sort", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ row_pivots: ["y"], sort: [["y", "desc"]], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{x: 13}, {x: 2}, {x: 1}]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -784,17 +802,17 @@ module.exports = perspective => { }); }); - describe("2-sided row delta", function() { - it("returns changed rows when updated data in row pivot", async function(done) { + describe("2-sided row delta", function () { + it("returns changed rows when updated data in row pivot", async function (done) { let table = await perspective.table(data, {index: "y"}); let view = await table.view({ row_pivots: ["y"], - column_pivots: ["x"] + column_pivots: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = json.slice(0, 3); @@ -808,17 +826,17 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows when updated data in row pivot, column range", async function(done) { + it("returns changed rows when updated data in row pivot, column range", async function (done) { let table = await perspective.table(data, {index: "y"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = json.slice(0, 3); @@ -832,21 +850,21 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows when updated data in row pivot, hidden sort", async function(done) { + it("returns changed rows when updated data in row pivot, hidden sort", async function (done) { let table = await perspective.table(data, {index: "y"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], sort: [["y", "desc"]], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = await view.to_json(); await match_delta( perspective, updated.delta, - expected.slice(0, 3).map(x => { + expected.slice(0, 3).map((x) => { delete x.__ROW_PATH__; return x; }) @@ -860,16 +878,16 @@ module.exports = perspective => { table.update(partial_change_y); }); - it.skip("returns changed rows when updated data in row pivot multi, hidden sort", async function(done) { + it.skip("returns changed rows when updated data in row pivot multi, hidden sort", async function (done) { let table = await perspective.table(data, {index: "y"}); let view = await table.view({ row_pivots: ["y", "x", "z"], column_pivots: ["x"], sort: [["y", "desc"]], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = { "1|x": [14, 0, 0], "1|y": ["-", null, null], @@ -878,9 +896,14 @@ module.exports = perspective => { "3|x": [2, null, 1], "3|y": ["b", null, "HELLO"], "4|x": [6, null, 1], - "4|y": ["-", null, "HELLO"] + "4|y": ["-", null, "HELLO"], }; - await match_delta(perspective, updated.delta, expected, "to_columns"); + await match_delta( + perspective, + updated.delta, + expected, + "to_columns" + ); view.delete(); table.delete(); done(); @@ -890,16 +913,16 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows when updated data in column pivot", async function(done) { + it("returns changed rows when updated data in column pivot", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = json.slice(0, 3); @@ -913,17 +936,17 @@ module.exports = perspective => { table.update(partial_change_z); }); - it("returns changed rows when updated data in column pivot, column range", async function(done) { + it("returns changed rows when updated data in column pivot, column range", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["z"], - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = json.slice(0, 3); @@ -937,16 +960,16 @@ module.exports = perspective => { table.update(partial_change_z); }); - it("returns changed rows when updated data in row and column pivot", async function(done) { + it("returns changed rows when updated data in row and column pivot", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = [json[0], json[3], json[4]]; @@ -960,17 +983,17 @@ module.exports = perspective => { table.update(partial_change_y_z); }); - it("returns changed rows when updated data in row and column pivot, column range", async function(done) { + it("returns changed rows when updated data in row and column pivot, column range", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["z"], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = [json[0], json[3], json[4]]; @@ -984,15 +1007,15 @@ module.exports = perspective => { table.update(partial_change_y_z); }); - it("returns nothing when updated data is not in pivot", async function(done) { + it("returns nothing when updated data is not in pivot", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, []); view.delete(); table.delete(); @@ -1003,16 +1026,16 @@ module.exports = perspective => { table.update(partial_change_z); }); - it("returns added rows", async function(done) { + it("returns added rows", async function (done) { let table = await perspective.table(data); let view = await table.view({ row_pivots: ["y"], - column_pivots: ["x"] + column_pivots: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = json.slice(0, 3); @@ -1026,20 +1049,20 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns deleted columns", async function(done) { + it("returns deleted columns", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], aggregates: {y: "unique"}, - columns: ["x", "y", "z"] + columns: ["x", "y", "z"], }); view.on_update( - async function(updated) { + async function (updated) { // underlying data changes, but only total aggregate row is affected const expected = await view.to_json(); expected.splice(3, 1); - expected.map(d => { + expected.map((d) => { delete d["__ROW_PATH__"]; }); await match_delta(perspective, updated.delta, expected); @@ -1052,24 +1075,24 @@ module.exports = perspective => { table.update([ {x: 1, y: null}, {x: 2, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns deleted columns, column range", async function(done) { + it("returns deleted columns, column range", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], aggregates: {y: "unique"}, - columns: ["y"] + columns: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { // underlying data changes, but only total aggregate row is affected const expected = await view.to_json(); expected.splice(3, 1); - expected.map(d => { + expected.map((d) => { delete d["__ROW_PATH__"]; }); await match_delta(perspective, updated.delta, expected); @@ -1082,22 +1105,22 @@ module.exports = perspective => { table.update([ {x: 1, y: null}, {x: 2, y: null}, - {x: 4, y: null} + {x: 4, y: null}, ]); }); - it("returns changed rows in non-sequential update", async function(done) { + it("returns changed rows in non-sequential update", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ row_pivots: ["y"], column_pivots: ["x"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); view.on_update( - async function(updated) { + async function (updated) { // aggregates are sorted, in this case by string comparator - "string1" and "string2" are at the end const json = await view.to_json(); - json.map(d => { + json.map((d) => { delete d["__ROW_PATH__"]; }); const expected = [json[3], json[4]]; @@ -1111,18 +1134,31 @@ module.exports = perspective => { table.update(partial_change_nonseq); }); - it("returns changed rows in column-only pivots", async function(done) { + it("returns changed rows in column-only pivots", async function (done) { let table = await perspective.table(data, {index: "x"}); let view = await table.view({ - column_pivots: ["x"] + column_pivots: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const json = await view.to_json(); const expected = [ - {"1|x": 1, "1|y": "string1", "1|z": false, "2|x": 2, "2|y": "b", "2|z": false, "3|x": 3, "3|y": "c", "3|z": true, "4|x": 4, "4|y": "string2", "4|z": true}, + { + "1|x": 1, + "1|y": "string1", + "1|z": false, + "2|x": 2, + "2|y": "b", + "2|z": false, + "3|x": 3, + "3|y": "c", + "3|z": true, + "4|x": 4, + "4|y": "string2", + "4|z": true, + }, json[0], - json[3] + json[3], ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -1134,15 +1170,15 @@ module.exports = perspective => { table.update(partial_change_nonseq); }); - it("returns changed rows, col only", async function(done) { + it("returns changed rows, col only", async function (done) { let table = await perspective.table(data, { - index: "x" + index: "x", }); let view = await table.view({ - column_pivots: ["y"] + column_pivots: ["y"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ { "c|x": 3, @@ -1156,7 +1192,7 @@ module.exports = perspective => { "string1|z": true, "string2|x": 2, "string2|y": "string2", - "string2|z": false + "string2|z": false, }, { "c|x": null, @@ -1170,7 +1206,7 @@ module.exports = perspective => { "string1|z": true, "string2|x": null, "string2|y": null, - "string2|z": null + "string2|z": null, }, { "c|x": null, @@ -1184,8 +1220,8 @@ module.exports = perspective => { "string1|z": null, "string2|x": 2, "string2|y": "string2", - "string2|z": false - } + "string2|z": false, + }, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -1193,26 +1229,26 @@ module.exports = perspective => { done(); }, { - mode: "row" + mode: "row", } ); table.update(partial_change_y); }); - it("returns changed rows, col only agg", async function(done) { + it("returns changed rows, col only agg", async function (done) { const table = await perspective.table(data, { - index: "x" + index: "x", }); const view = await table.view({ column_pivots: ["x"], sort: [["y", "desc"]], columns: ["y"], - aggregates: {y: "last"} + aggregates: {y: "last"}, }); view.on_update( - async function(updated) { + async function (updated) { // TODO: deltas return a total row for column only // which they probably shouldn't. const expected = [ @@ -1220,20 +1256,20 @@ module.exports = perspective => { "1|y": "string1", "2|y": "string2", "3|y": "c", - "4|y": "d" + "4|y": "d", }, { "1|y": null, "2|y": "string2", "3|y": null, - "4|y": null + "4|y": null, }, { "1|y": "string1", "2|y": null, "3|y": null, - "4|y": null - } + "4|y": null, + }, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -1246,35 +1282,35 @@ module.exports = perspective => { table.update(partial_change_y); }); - it("returns changed rows, col only col range", async function(done) { + it("returns changed rows, col only col range", async function (done) { let table = await perspective.table(data, { - index: "x" + index: "x", }); let view = await table.view({ column_pivots: ["y"], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ { "c|x": 3, "d|x": 4, "string1|x": 1, - "string2|x": 2 + "string2|x": 2, }, { "c|x": null, "d|x": null, "string1|x": 1, - "string2|x": null + "string2|x": null, }, { "c|x": null, "d|x": null, "string1|x": null, - "string2|x": 2 - } + "string2|x": 2, + }, ]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -1282,43 +1318,43 @@ module.exports = perspective => { done(); }, { - mode: "row" + mode: "row", } ); table.update(partial_change_y); }); - it("returns changed rows, col only sorted", async function(done) { + it("returns changed rows, col only sorted", async function (done) { const table = await perspective.table(data, { - index: "x" + index: "x", }); const view = await table.view({ column_pivots: ["y"], columns: ["x"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); console.log(await view.to_json()); view.on_update( - async function(updated) { + async function (updated) { const expected = [ { "c|x": 3, "d|x": 4, "string1|x": 1, - "string2|x": 2 + "string2|x": 2, }, { "c|x": null, "d|x": null, "string1|x": null, - "string2|x": 2 + "string2|x": 2, }, { "c|x": null, "d|x": null, "string1|x": 1, - "string2|x": null - } + "string2|x": null, + }, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -1326,27 +1362,27 @@ module.exports = perspective => { done(); }, { - mode: "row" + mode: "row", } ); table.update(partial_change_y); }); - it("returns changed rows, col only sorted change not in pivot", async function(done) { + it("returns changed rows, col only sorted change not in pivot", async function (done) { let table = await perspective.table( {x: [1], y: [100]}, { - index: "x" + index: "x", } ); let view = await table.view({ column_pivots: ["y"], columns: ["x"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); console.log(await view.to_json()); view.on_update( - async function(updated) { + async function (updated) { const expected = [{"100|x": 3}]; await match_delta(perspective, updated.delta, expected); view.delete(); @@ -1354,40 +1390,40 @@ module.exports = perspective => { done(); }, { - mode: "row" + mode: "row", } ); table.update([{x: 3, y: 100}]); }); - it("returns changed rows, col only hidden sort", async function(done) { + it("returns changed rows, col only hidden sort", async function (done) { const table = await perspective.table(data, {index: "x"}); const view = await table.view({ column_pivots: ["y"], columns: ["x"], - sort: [["y", "desc"]] + sort: [["y", "desc"]], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ { "c|x": 3, "d|x": 4, "string1|x": 1, - "string2|x": 2 + "string2|x": 2, }, { "c|x": null, "d|x": null, "string1|x": null, - "string2|x": 2 + "string2|x": 2, }, { "c|x": null, "d|x": null, "string1|x": 1, - "string2|x": null - } + "string2|x": null, + }, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); diff --git a/packages/perspective/test/js/expressions.js b/packages/perspective/test/js/expressions.js index d51dd03b57..6134c27907 100644 --- a/packages/perspective/test/js/expressions.js +++ b/packages/perspective/test/js/expressions.js @@ -17,7 +17,7 @@ const invariant = require("./expressions/invariant"); const multiple_views = require("./expressions/multiple_views"); const conversions = require("./expressions/conversions"); -module.exports = perspective => { +module.exports = (perspective) => { functionality(perspective); numeric(perspective); string(perspective); diff --git a/packages/perspective/test/js/expressions/common.js b/packages/perspective/test/js/expressions/common.js index eb6c6c6cf9..3db3f4d3c2 100644 --- a/packages/perspective/test/js/expressions/common.js +++ b/packages/perspective/test/js/expressions/common.js @@ -12,50 +12,82 @@ exports.data = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; exports.int_float_data = [ {w: 1.5, x: 1, y: "a", z: true}, {w: 2.5, x: 2, y: "b", z: false}, {w: 3.5, x: 3, y: "c", z: true}, - {w: 4.5, x: 4, y: "d", z: false} + {w: 4.5, x: 4, y: "d", z: false}, ]; exports.int_float_subtract_data = [ {u: 2.5, v: 2, w: 1.5, x: 1, y: "a", z: true}, {u: 3.5, v: 3, w: 2.5, x: 2, y: "b", z: false}, {u: 4.5, v: 4, w: 3.5, x: 3, y: "c", z: true}, - {u: 5.5, v: 5, w: 4.5, x: 4, y: "d", z: false} + {u: 5.5, v: 5, w: 4.5, x: 4, y: "d", z: false}, ]; exports.comparison_data = [ {u: 0, v: 1.5, w: 1.5, x: 1, x2: 1, y: "a", z: true}, {u: 1, v: 2.55, w: 2.5, x: 2, x2: 10, y: "b", z: false}, {u: 0, v: 3.5, w: 3.5, x: 3, x2: 3, y: "c", z: true}, - {u: 1, v: 4.55, w: 4.5, x: 4, x2: 10, y: "d", z: false} + {u: 1, v: 4.55, w: 4.5, x: 4, x2: 10, y: "d", z: false}, ]; -exports.cols = ["i8", "ui8", "i16", "ui16", "i32", "ui32", "i64", "ui64", "f32", "f64"]; +exports.cols = [ + "i8", + "ui8", + "i16", + "ui16", + "i32", + "ui32", + "i64", + "ui64", + "f32", + "f64", +]; exports.arrow = arrows.numbers_arrow; exports.all_types_arrow = arrows.all_types_arrow; exports.all_types_multi_arrow = arrows.all_types_multi_arrow; -exports.days_of_week = ["1 Sunday", "2 Monday", "3 Tuesday", "4 Wednesday", "5 Thursday", "6 Friday", "7 Saturday"]; -exports.months_of_year = ["01 January", "02 February", "03 March", "04 April", "05 May", "06 June", "07 July", "08 August", "09 September", "10 October", "11 November", "12 December"]; +exports.days_of_week = [ + "1 Sunday", + "2 Monday", + "3 Tuesday", + "4 Wednesday", + "5 Thursday", + "6 Friday", + "7 Saturday", +]; +exports.months_of_year = [ + "01 January", + "02 February", + "03 March", + "04 April", + "05 May", + "06 June", + "07 July", + "08 August", + "09 September", + "10 October", + "11 November", + "12 December", +]; -exports.second_bucket = function(val) { +exports.second_bucket = function (val) { return new Date(Math.floor(new Date(val).getTime() / 1000) * 1000); }; -exports.minute_bucket = function(val) { +exports.minute_bucket = function (val) { let date = new Date(val); date.setSeconds(0); date.setMilliseconds(0); return date; }; -exports.hour_bucket = function(val) { +exports.hour_bucket = function (val) { let date = new Date(val); date.setMinutes(0); date.setSeconds(0); @@ -63,7 +95,7 @@ exports.hour_bucket = function(val) { return date; }; -exports.day_bucket = function(val) { +exports.day_bucket = function (val) { let date = new Date(val); date.setHours(0); date.setMinutes(0); @@ -72,7 +104,7 @@ exports.day_bucket = function(val) { return date; }; -exports.week_bucket = function(val) { +exports.week_bucket = function (val) { let date = new Date(val); let day = date.getDay(); let diff = date.getDate() - day + (day == 0 ? -6 : 1); @@ -83,7 +115,7 @@ exports.week_bucket = function(val) { return date; }; -exports.month_bucket = function(val) { +exports.month_bucket = function (val) { let date = new Date(val); date.setHours(0); date.setMinutes(0); @@ -92,7 +124,7 @@ exports.month_bucket = function(val) { return date; }; -exports.year_bucket = function(val) { +exports.year_bucket = function (val) { let date = new Date(val); date.setHours(0); date.setMinutes(0); diff --git a/packages/perspective/test/js/expressions/conversions.js b/packages/perspective/test/js/expressions/conversions.js index 6f6abbd003..1ca9de3bf6 100644 --- a/packages/perspective/test/js/expressions/conversions.js +++ b/packages/perspective/test/js/expressions/conversions.js @@ -11,7 +11,7 @@ * Tests the correctness of each datetime computation function in various * environments and parameters - different types, nulls, undefined, etc. */ -module.exports = perspective => { +module.exports = (perspective) => { describe("string()", () => { it("Should create string from all column types", async () => { const table = await perspective.table({ @@ -20,7 +20,7 @@ module.exports = perspective => { c: "integer", d: "float", e: "string", - f: "boolean" + f: "boolean", }); const view = await table.view({ @@ -31,17 +31,20 @@ module.exports = perspective => { '// computed4\n string("d")', '// computed5\n string("e")', '// computed6\n string("f")', - "// computed7\n string(1234.5678)" - ] + "// computed7\n string(1234.5678)", + ], }); table.update({ a: [new Date(2020, 4, 30), new Date(2021, 6, 13)], - b: [new Date(2015, 10, 29, 23, 59, 59), new Date(2016, 10, 29, 23, 59, 59)], + b: [ + new Date(2015, 10, 29, 23, 59, 59), + new Date(2016, 10, 29, 23, 59, 59), + ], c: [12345678, 1293879852], d: [1.2792013981, 19.218975981], e: ["abcdefghijklmnop", "def"], - f: [false, true] + f: [false, true], }); expect(await view.expression_schema()).toEqual({ @@ -51,13 +54,16 @@ module.exports = perspective => { computed4: "string", computed5: "string", computed6: "string", - computed7: "string" + computed7: "string", }); const result = await view.to_columns(); expect(result["computed"]).toEqual(["2020-05-30", "2021-07-13"]); - expect(result["computed2"]).toEqual(["2015-11-29 23:59:59.000", "2016-11-29 23:59:59.000"]); + expect(result["computed2"]).toEqual([ + "2015-11-29 23:59:59.000", + "2016-11-29 23:59:59.000", + ]); expect(result["computed3"]).toEqual(["12345678", "1293879852"]); expect(result["computed4"]).toEqual(["1.2792", "19.219"]); expect(result["computed5"]).toEqual(["abcdefghijklmnop", "def"]); @@ -70,7 +76,7 @@ module.exports = perspective => { it("Should create string from scalars", async () => { const table = await perspective.table({ - x: [1] + x: [1], }); const view = await table.view({ @@ -78,15 +84,19 @@ module.exports = perspective => { aggregates: { computed: "last", computed2: "last", - computed3: "last" + computed3: "last", }, - expressions: ["// computed\n string('abcdefg')", "// computed2\n string(1234)", "// computed3\n string(1234.5678)"] + expressions: [ + "// computed\n string('abcdefg')", + "// computed2\n string(1234)", + "// computed3\n string(1234.5678)", + ], }); expect(await view.expression_schema()).toEqual({ computed: "string", computed2: "string", - computed3: "string" + computed3: "string", }); const result = await view.to_columns(); @@ -118,8 +128,8 @@ module.exports = perspective => { `//computed11\ninteger('1234abcd')`, `//computed12\ninteger('abcdefg1234')`, `//computed13\ninteger('2147483648')`, - `//computed14\ninteger('-2147483649')` - ] + `//computed14\ninteger('-2147483649')`, + ], }); const result = await view.to_columns(); @@ -150,15 +160,22 @@ module.exports = perspective => { const table = await perspective.table({x: "integer"}); const view = await table.view({ - expressions: [`//computed\ninteger("x")`] + expressions: [`//computed\ninteger("x")`], }); table.update({ - x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937] + x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([100, -17238, 0, -1, null, 12836215]); + expect(result["computed"]).toEqual([ + 100, + -17238, + 0, + -1, + null, + 12836215, + ]); await view.delete(); await table.delete(); @@ -168,15 +185,29 @@ module.exports = perspective => { const table = await perspective.table({x: "float"}); const view = await table.view({ - expressions: [`//computed\ninteger("x")`] + expressions: [`//computed\ninteger("x")`], }); table.update({ - x: [100.9999999, -17238.8123, 0.890798, -1.1295, null, 12836215.128937] + x: [ + 100.9999999, + -17238.8123, + 0.890798, + -1.1295, + null, + 12836215.128937, + ], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([100, -17238, 0, -1, null, 12836215]); + expect(result["computed"]).toEqual([ + 100, + -17238, + 0, + -1, + null, + 12836215, + ]); await view.delete(); await table.delete(); @@ -186,13 +217,13 @@ module.exports = perspective => { const table = await perspective.table({x: "date"}); const view = await table.view({ - expressions: [`//computed\ninteger("x")`] + expressions: [`//computed\ninteger("x")`], }); const value = new Date(2020, 5, 30); table.update({ - x: [value] + x: [value], }); const result = await view.to_columns(); @@ -219,26 +250,39 @@ module.exports = perspective => { const table = await perspective.table({x: "datetime"}); const view = await table.view({ - expressions: [`//computed\ninteger("x")`] + expressions: [`//computed\ninteger("x")`], }); // first will not overflow, second will table.update({ - x: [new Date(1970, 0, 1, 1), new Date(2020, 0, 1, 1)] + x: [new Date(1970, 0, 1, 1), new Date(2020, 0, 1, 1)], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([new Date(1970, 0, 1, 1).getTime(), null]); + expect(result["computed"]).toEqual([ + new Date(1970, 0, 1, 1).getTime(), + null, + ]); await view.delete(); await table.delete(); }); it("Should create integers from string columns", async () => { - const table = await perspective.table({x: ["1", "2", "3", "abc", "4.5", "0.101928317581729083", "-123456"]}); + const table = await perspective.table({ + x: [ + "1", + "2", + "3", + "abc", + "4.5", + "0.101928317581729083", + "-123456", + ], + }); const view = await table.view({ - expressions: [`//computed\ninteger("x")`] + expressions: [`//computed\ninteger("x")`], }); const result = await view.to_columns(); @@ -271,8 +315,8 @@ module.exports = perspective => { `//computed15\n float('inf')`, `//computed16\n float('-inf')`, `//computed17\n float(inf)`, - `//computed18\n float(-inf)` - ] + `//computed18\n float(-inf)`, + ], }); expect(await view.expression_schema()).toEqual({ @@ -293,7 +337,7 @@ module.exports = perspective => { computed15: "float", computed16: "float", computed17: "float", - computed18: "float" + computed18: "float", }); const result = await view.to_columns(); @@ -324,15 +368,22 @@ module.exports = perspective => { const table = await perspective.table({x: "integer"}); const view = await table.view({ - expressions: [`//computed\nfloat("x")`] + expressions: [`//computed\nfloat("x")`], }); table.update({ - x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937] + x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([100, -17238, 0, -1, null, 12836215]); + expect(result["computed"]).toEqual([ + 100, + -17238, + 0, + -1, + null, + 12836215, + ]); await view.delete(); await table.delete(); @@ -342,15 +393,29 @@ module.exports = perspective => { const table = await perspective.table({x: "float"}); const view = await table.view({ - expressions: [`//computed\n float("x")`] + expressions: [`//computed\n float("x")`], }); table.update({ - x: [100.9999999, -17238.8123, 0.890798, -1.1295, null, 12836215.128937] + x: [ + 100.9999999, + -17238.8123, + 0.890798, + -1.1295, + null, + 12836215.128937, + ], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([100.9999999, -17238.8123, 0.890798, -1.1295, null, 12836215.128937]); + expect(result["computed"]).toEqual([ + 100.9999999, + -17238.8123, + 0.890798, + -1.1295, + null, + 12836215.128937, + ]); await view.delete(); await table.delete(); @@ -360,13 +425,13 @@ module.exports = perspective => { const table = await perspective.table({x: "date"}); const view = await table.view({ - expressions: [`//computed\nfloat("x")`] + expressions: [`//computed\nfloat("x")`], }); const value = new Date(2020, 5, 30); table.update({ - x: [value] + x: [value], }); const result = await view.to_columns(); @@ -393,15 +458,18 @@ module.exports = perspective => { const table = await perspective.table({x: "datetime"}); const view = await table.view({ - expressions: [`//computed\nfloat("x")`] + expressions: [`//computed\nfloat("x")`], }); table.update({ - x: [new Date(2020, 0, 1, 1), new Date(2020, 0, 1, 1, 50)] + x: [new Date(2020, 0, 1, 1), new Date(2020, 0, 1, 1, 50)], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([new Date(2020, 0, 1, 1).getTime(), new Date(2020, 0, 1, 1, 50).getTime()]); + expect(result["computed"]).toEqual([ + new Date(2020, 0, 1, 1).getTime(), + new Date(2020, 0, 1, 1, 50).getTime(), + ]); await view.delete(); await table.delete(); @@ -409,15 +477,31 @@ module.exports = perspective => { it("Should create floats from string columns", async () => { const table = await perspective.table({ - x: ["1.1238757869112321", "2.0000001", "abcdefg1234.12878591", "12354.1827389abc", "4.555555555", "0.101928317581729083", "-123456.21831729054781"] + x: [ + "1.1238757869112321", + "2.0000001", + "abcdefg1234.12878591", + "12354.1827389abc", + "4.555555555", + "0.101928317581729083", + "-123456.21831729054781", + ], }); const view = await table.view({ - expressions: [`//computed\n float("x")`] + expressions: [`//computed\n float("x")`], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([1.1238757869112321, 2.0000001, null, null, 4.555555555, 0.101928317581729083, -123456.21831729054781]); + expect(result["computed"]).toEqual([ + 1.1238757869112321, + 2.0000001, + null, + null, + 4.555555555, + 0.101928317581729083, + -123456.21831729054781, + ]); await view.delete(); await table.delete(); @@ -427,16 +511,24 @@ module.exports = perspective => { describe("date()", () => { it("Should create a date from scalars", async () => { const table = await perspective.table({ - x: [1, 2, 3, 4] + x: [1, 2, 3, 4], }); const view = await table.view({ - expressions: [`//computed \n date(2020, 7, 15)`, `//computed2 \n date(1970, 10, 29)`, `//computed3\n date(2020, 1, "x")`] + expressions: [ + `//computed \n date(2020, 7, 15)`, + `//computed2 \n date(1970, 10, 29)`, + `//computed3\n date(2020, 1, "x")`, + ], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual(Array(4).fill(new Date(2020, 6, 15).getTime())); - expect(result["computed2"]).toEqual(Array(4).fill(new Date(1970, 9, 29).getTime())); + expect(result["computed"]).toEqual( + Array(4).fill(new Date(2020, 6, 15).getTime()) + ); + expect(result["computed2"]).toEqual( + Array(4).fill(new Date(1970, 9, 29).getTime()) + ); expect(result["computed3"]).toEqual( Array(4) .fill(true) @@ -450,21 +542,27 @@ module.exports = perspective => { const table = await perspective.table({ y: "integer", m: "integer", - d: "integer" + d: "integer", }); const view = await table.view({ - expressions: [`//computed \n date("y", "m", "d")`] + expressions: [`//computed \n date("y", "m", "d")`], }); table.update({ y: [0, 2020, 1776, 2018, 2020, 2020], m: [1, 2, 5, 2, 12, null], - d: [1, 29, 31, 29, 31, 1] + d: [1, 29, 31, 29, 31, 1], }); const result = await view.to_columns(); - const expected = [new Date(1900, 0, 1), new Date(2020, 1, 29), new Date(1776, 4, 31), new Date(2018, 1, 29), new Date(2020, 11, 31)].map(x => x.getTime()); + const expected = [ + new Date(1900, 0, 1), + new Date(2020, 1, 29), + new Date(1776, 4, 31), + new Date(2018, 1, 29), + new Date(2020, 11, 31), + ].map((x) => x.getTime()); expected.push(null); expect(result["computed"]).toEqual(expected); await view.delete(); @@ -475,21 +573,27 @@ module.exports = perspective => { const table = await perspective.table({ y: "float", m: "float", - d: "float" + d: "float", }); const view = await table.view({ - expressions: [`//computed \n date("y", "m", "d")`] + expressions: [`//computed \n date("y", "m", "d")`], }); table.update({ y: [0, 2020, 1776, 2018, 2020, 2020], m: [1, 2, 5, 2, 12, null], - d: [1, 29, 31, 29, 31, 1] + d: [1, 29, 31, 29, 31, 1], }); const result = await view.to_columns(); - const expected = [new Date(1900, 0, 1), new Date(2020, 1, 29), new Date(1776, 4, 31), new Date(2018, 1, 29), new Date(2020, 11, 31)].map(x => x.getTime()); + const expected = [ + new Date(1900, 0, 1), + new Date(2020, 1, 29), + new Date(1776, 4, 31), + new Date(2018, 1, 29), + new Date(2020, 11, 31), + ].map((x) => x.getTime()); expected.push(null); expect(result["computed"]).toEqual(expected); await view.delete(); @@ -500,30 +604,44 @@ module.exports = perspective => { const table = await perspective.table({ y: [-100, 0, 2000, 3000], m: [12, 0, 12, 11], - d: [1, 10, 32, 10] + d: [1, 10, 32, 10], }); const view = await table.view({ - expressions: [`//computed \n date("y", "m", "d")`] + expressions: [`//computed \n date("y", "m", "d")`], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([null, null, null, new Date(3000, 10, 10).getTime()]); + expect(result["computed"]).toEqual([ + null, + null, + null, + new Date(3000, 10, 10).getTime(), + ]); await view.delete(); await table.delete(); }); it("Should create a date from variables inside expression", async () => { const table = await perspective.table({ - x: [20200101, 20090531, 19801220, 20200229] + x: [20200101, 20090531, 19801220, 20200229], }); const view = await table.view({ - expressions: [`//computed \n var year := floor("x" / 10000); var month := floor("x" % 10000 / 100); var day := floor("x" % 100); date(year, month, day)`] + expressions: [ + `//computed \n var year := floor("x" / 10000); var month := floor("x" % 10000 / 100); var day := floor("x" % 100); date(year, month, day)`, + ], }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([new Date(2020, 0, 1), new Date(2009, 4, 31), new Date(1980, 11, 20), new Date(2020, 1, 29)].map(x => x.getTime())); + expect(result["computed"]).toEqual( + [ + new Date(2020, 0, 1), + new Date(2009, 4, 31), + new Date(1980, 11, 20), + new Date(2020, 1, 29), + ].map((x) => x.getTime()) + ); await view.delete(); await table.delete(); }); @@ -532,26 +650,32 @@ module.exports = perspective => { const table = await perspective.table({ y: [-100, 0, 2000, 3000], m: [12, 0, 12, 11], - d: [1, 10, 32, 10] + d: [1, 10, 32, 10], }); - const validated = await table.validate_expressions([`//computed \n date()`, `//computed2 \n date('abc', 'def', '123')`, `//computed3\ndate("y", "m", "d")`]); + const validated = await table.validate_expressions([ + `//computed \n date()`, + `//computed2 \n date('abc', 'def', '123')`, + `//computed3\ndate("y", "m", "d")`, + ]); expect(validated.expression_schema).toEqual({ - computed3: "date" + computed3: "date", }); expect(validated.errors).toEqual({ computed: { column: 8, - error_message: "Parser Error - Zero parameter call to generic function: date not allowed", - line: 1 + error_message: + "Parser Error - Zero parameter call to generic function: date not allowed", + line: 1, }, computed2: { column: 0, - error_message: "Type Error - inputs do not resolve to a valid expression.", - line: 0 - } + error_message: + "Type Error - inputs do not resolve to a valid expression.", + line: 0, + }, }); await table.delete(); @@ -561,19 +685,22 @@ module.exports = perspective => { describe("datetime()", () => { it("Should create a datetime from scalars", async () => { const table = await perspective.table({ - x: [1, 2, 3, 4] + x: [1, 2, 3, 4], }); const a = new Date(2005, 6, 31, 11, 59, 32).getTime(); const b = new Date(2005, 6, 31, 11, 59, 32).getTime(); const view = await table.view({ - expressions: [`//computed \n datetime(${a})`, `//computed2 \n datetime(${b})`] + expressions: [ + `//computed \n datetime(${a})`, + `//computed2 \n datetime(${b})`, + ], }); expect(await view.expression_schema()).toEqual({ computed: "datetime", - computed2: "datetime" + computed2: "datetime", }); const result = await view.to_columns(); @@ -585,37 +712,44 @@ module.exports = perspective => { it("Should create a datetime from a float()", async () => { const table = await perspective.table({ - x: [new Date(2005, 6, 31, 11, 59, 32)] + x: [new Date(2005, 6, 31, 11, 59, 32)], }); const view = await table.view({ - expressions: [`//computed \n datetime(float("x"))`] + expressions: [`//computed \n datetime(float("x"))`], }); expect(await view.expression_schema()).toEqual({ - computed: "datetime" + computed: "datetime", }); const result = await view.to_columns(); - expect(result["computed"]).toEqual([new Date(2005, 6, 31, 11, 59, 32).getTime()]); + expect(result["computed"]).toEqual([ + new Date(2005, 6, 31, 11, 59, 32).getTime(), + ]); await view.delete(); await table.delete(); }); it("Should not create a datetime from int columns as int32 is too small", async () => { const table = await perspective.table({ - x: "integer" + x: "integer", }); const view = await table.view({ - expressions: [`//computed \n datetime("x")`] + expressions: [`//computed \n datetime("x")`], }); - const data = [new Date(2020, 1, 29, 5, 1, 2), new Date(1776, 4, 31, 13, 23, 18), new Date(2018, 1, 29, 19, 39, 43), new Date(2020, 11, 31, 23, 59, 59)].map(x => x.getTime()); + const data = [ + new Date(2020, 1, 29, 5, 1, 2), + new Date(1776, 4, 31, 13, 23, 18), + new Date(2018, 1, 29, 19, 39, 43), + new Date(2020, 11, 31, 23, 59, 59), + ].map((x) => x.getTime()); data.push(null); table.update({ - x: data + x: data, }); let result = await view.to_columns(); @@ -627,18 +761,23 @@ module.exports = perspective => { it("Should create a datetime from float columns", async () => { const table = await perspective.table({ - x: "float" + x: "float", }); const view = await table.view({ - expressions: [`//computed \n datetime("x")`] + expressions: [`//computed \n datetime("x")`], }); - const data = [new Date(2020, 1, 29, 5, 1, 2), new Date(1776, 4, 31, 13, 23, 18), new Date(2018, 1, 29, 19, 39, 43), new Date(2020, 11, 31, 23, 59, 59)].map(x => x.getTime()); + const data = [ + new Date(2020, 1, 29, 5, 1, 2), + new Date(1776, 4, 31, 13, 23, 18), + new Date(2018, 1, 29, 19, 39, 43), + new Date(2020, 11, 31, 23, 59, 59), + ].map((x) => x.getTime()); data.push(null); table.update({ - x: data + x: data, }); const result = await view.to_columns(); @@ -649,31 +788,44 @@ module.exports = perspective => { it("Should create a datetime from numeric scalars < 0", async () => { const table = await perspective.table({ - x: [1] + x: [1], }); const view = await table.view({ - expressions: [`//computed1 \n datetime(-1)`, `//computed2 \n datetime(0)`, `//computed3 \n datetime(${new Date(2002, 11, 12, 13, 14, 15).getTime()})`] + expressions: [ + `//computed1 \n datetime(-1)`, + `//computed2 \n datetime(0)`, + `//computed3 \n datetime(${new Date( + 2002, + 11, + 12, + 13, + 14, + 15 + ).getTime()})`, + ], }); expect(await view.expression_schema()).toEqual({ computed1: "datetime", computed2: "datetime", - computed3: "datetime" + computed3: "datetime", }); const result = await view.to_columns(); expect(result["computed1"]).toEqual([-1]); expect(result["computed2"]).toEqual([0]); - expect(result["computed3"]).toEqual([new Date(2002, 11, 12, 13, 14, 15).getTime()]); + expect(result["computed3"]).toEqual([ + new Date(2002, 11, 12, 13, 14, 15).getTime(), + ]); await view.delete(); await table.delete(); }); it("Should validate inputs", async () => { const table = await perspective.table({ - x: [1] + x: [1], }); const validated = await table.validate_expressions([ @@ -681,7 +833,7 @@ module.exports = perspective => { `//computed2 \n datetime('abcd')`, `//computed3 \n datetime(today())`, `//computed4 \n datetime(now())`, - `//computed5 \n datetime(123456, 7)` + `//computed5 \n datetime(123456, 7)`, ]); expect(validated.expression_schema).toEqual({}); @@ -689,29 +841,34 @@ module.exports = perspective => { expect(validated.errors).toEqual({ computed1: { column: 12, - error_message: "Parser Error - Zero parameter call to generic function: datetime not allowed", - line: 1 + error_message: + "Parser Error - Zero parameter call to generic function: datetime not allowed", + line: 1, }, computed2: { - error_message: "Type Error - inputs do not resolve to a valid expression.", + error_message: + "Type Error - inputs do not resolve to a valid expression.", column: 0, - line: 0 + line: 0, }, computed3: { - error_message: "Type Error - inputs do not resolve to a valid expression.", + error_message: + "Type Error - inputs do not resolve to a valid expression.", column: 0, - line: 0 + line: 0, }, computed4: { - error_message: "Type Error - inputs do not resolve to a valid expression.", + error_message: + "Type Error - inputs do not resolve to a valid expression.", column: 0, - line: 0 + line: 0, }, computed5: { - error_message: "Parser Error - Failed parameter type check for function 'datetime', Expected 'T' call set: 'TT'", + error_message: + "Parser Error - Failed parameter type check for function 'datetime', Expected 'T' call set: 'TT'", column: 21, - line: 1 - } + line: 1, + }, }); await table.delete(); diff --git a/packages/perspective/test/js/expressions/datetime.js b/packages/perspective/test/js/expressions/datetime.js index 925de871cd..d345993ef3 100644 --- a/packages/perspective/test/js/expressions/datetime.js +++ b/packages/perspective/test/js/expressions/datetime.js @@ -13,90 +13,144 @@ const common = require("./common.js"); * Tests the correctness of each datetime computation function in various * environments and parameters - different types, nulls, undefined, etc. */ -module.exports = perspective => { - describe("Date comparisons", function() { - it("equality", async function() { +module.exports = (perspective) => { + describe("Date comparisons", function () { + it("equality", async function () { const table = await perspective.table({ a: "date", - b: "date" + b: "date", }); const view = await table.view({ - expressions: ['"a" == "b"', '"a" != "b"', '"a" == "b" ? 100 : 0'] + expressions: [ + '"a" == "b"', + '"a" != "b"', + '"a" == "b" ? 100 : 0', + ], }); table.update({ - a: [new Date(2020, 0, 26), new Date(2020, 0, 27), new Date(2020, 0, 28), new Date(2020, 0, 29), new Date(2020, 0, 30)], - b: [new Date(2020, 0, 26), new Date(2020, 0, 27), new Date(2020, 0, 28), new Date(2020, 0, 29), new Date(2020, 1, 30)] + a: [ + new Date(2020, 0, 26), + new Date(2020, 0, 27), + new Date(2020, 0, 28), + new Date(2020, 0, 29), + new Date(2020, 0, 30), + ], + b: [ + new Date(2020, 0, 26), + new Date(2020, 0, 27), + new Date(2020, 0, 28), + new Date(2020, 0, 29), + new Date(2020, 1, 30), + ], }); let result = await view.to_columns(); expect(result['"a" == "b"']).toEqual([1, 1, 1, 1, 0]); expect(result['"a" != "b"']).toEqual([0, 0, 0, 0, 1]); - expect(result['"a" == "b" ? 100 : 0']).toEqual([100, 100, 100, 100, 0]); + expect(result['"a" == "b" ? 100 : 0']).toEqual([ + 100, 100, 100, 100, 0, + ]); view.delete(); table.delete(); }); - it("greater", async function() { + it("greater", async function () { const table = await perspective.table({ a: "date", - b: "date" + b: "date", }); const view = await table.view({ - expressions: ['"a" > "b"', '"a" >= "b"', '"a" >= "b" ? 100 : 0'] + expressions: [ + '"a" > "b"', + '"a" >= "b"', + '"a" >= "b" ? 100 : 0', + ], }); table.update({ - a: [new Date(2020, 0, 26), new Date(2020, 0, 27), new Date(2020, 0, 29), new Date(2020, 0, 30), new Date(2020, 1, 30)], - b: [new Date(2020, 1, 26), new Date(2020, 0, 28), new Date(2020, 0, 28), new Date(2020, 0, 29), new Date(2020, 1, 30)] + a: [ + new Date(2020, 0, 26), + new Date(2020, 0, 27), + new Date(2020, 0, 29), + new Date(2020, 0, 30), + new Date(2020, 1, 30), + ], + b: [ + new Date(2020, 1, 26), + new Date(2020, 0, 28), + new Date(2020, 0, 28), + new Date(2020, 0, 29), + new Date(2020, 1, 30), + ], }); let result = await view.to_columns(); expect(result['"a" > "b"']).toEqual([0, 0, 1, 1, 0]); expect(result['"a" >= "b"']).toEqual([0, 0, 1, 1, 1]); - expect(result['"a" >= "b" ? 100 : 0']).toEqual([0, 0, 100, 100, 100]); + expect(result['"a" >= "b" ? 100 : 0']).toEqual([ + 0, 0, 100, 100, 100, + ]); view.delete(); table.delete(); }); - it("less than", async function() { + it("less than", async function () { const table = await perspective.table({ a: "date", - b: "date" + b: "date", }); const view = await table.view({ - expressions: ['"a" < "b"', '"a" <= "b"', '"a" <= "b" ? 100 : 0'] + expressions: [ + '"a" < "b"', + '"a" <= "b"', + '"a" <= "b" ? 100 : 0', + ], }); table.update({ - a: [new Date(2020, 0, 26), new Date(2020, 0, 27), new Date(2020, 0, 29), new Date(2020, 0, 30), new Date(2020, 1, 30)], - b: [new Date(2020, 1, 26), new Date(2020, 0, 28), new Date(2020, 0, 28), new Date(2020, 0, 29), new Date(2020, 1, 30)] + a: [ + new Date(2020, 0, 26), + new Date(2020, 0, 27), + new Date(2020, 0, 29), + new Date(2020, 0, 30), + new Date(2020, 1, 30), + ], + b: [ + new Date(2020, 1, 26), + new Date(2020, 0, 28), + new Date(2020, 0, 28), + new Date(2020, 0, 29), + new Date(2020, 1, 30), + ], }); let result = await view.to_columns(); expect(result['"a" < "b"']).toEqual([1, 1, 0, 0, 0]); expect(result['"a" <= "b"']).toEqual([1, 1, 0, 0, 1]); - expect(result['"a" <= "b" ? 100 : 0']).toEqual([100, 100, 0, 0, 100]); + expect(result['"a" <= "b" ? 100 : 0']).toEqual([ + 100, 100, 0, 0, 100, + ]); view.delete(); table.delete(); }); }); - describe("Date functions", function() { - it("today()", async function() { + describe("Date functions", function () { + it("today()", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['today() == "a"'] + expressions: ['today() == "a"'], }); table.update({ - a: [new Date(), new Date(), new Date()] + a: [new Date(), new Date(), new Date()], }); let result = await view.to_columns(); @@ -105,17 +159,17 @@ module.exports = perspective => { table.delete(); }); - it("Hour of day, date", async function() { + it("Hour of day, date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['hour_of_day("a")'] + expressions: ['hour_of_day("a")'], }); table.update({ - a: [new Date(), new Date(), new Date()] + a: [new Date(), new Date(), new Date()], }); let result = await view.to_columns(); @@ -124,17 +178,17 @@ module.exports = perspective => { table.delete(); }); - it("Hour of day, date with null", async function() { + it("Hour of day, date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['hour_of_day("a")'] + expressions: ['hour_of_day("a")'], }); table.update({ - a: [new Date(), null, undefined, new Date()] + a: [new Date(), null, undefined, new Date()], }); let result = await view.to_columns(); @@ -143,93 +197,143 @@ module.exports = perspective => { table.delete(); }); - it("Day of week, date", async function() { + it("Day of week, date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['day_of_week("a")'] + expressions: ['day_of_week("a")'], }); table.update({ - a: [new Date(2020, 0, 26), new Date(2020, 0, 27), new Date(2020, 0, 28), new Date(2020, 0, 29), new Date(2020, 0, 30)] + a: [ + new Date(2020, 0, 26), + new Date(2020, 0, 27), + new Date(2020, 0, 28), + new Date(2020, 0, 29), + new Date(2020, 0, 30), + ], }); let result = await view.to_columns(); - expect(result['day_of_week("a")']).toEqual(result.a.map(x => common.days_of_week[new Date(x).getDay()])); + expect(result['day_of_week("a")']).toEqual( + result.a.map((x) => common.days_of_week[new Date(x).getDay()]) + ); view.delete(); table.delete(); }); - it("Day of week, date with null", async function() { + it("Day of week, date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['day_of_week("a")'] + expressions: ['day_of_week("a")'], }); table.update({ - a: [new Date(2020, 0, 26), null, undefined, new Date(2020, 0, 29), new Date(2020, 0, 30)] + a: [ + new Date(2020, 0, 26), + null, + undefined, + new Date(2020, 0, 29), + new Date(2020, 0, 30), + ], }); let result = await view.to_columns(); - expect(result['day_of_week("a")']).toEqual(result.a.map(x => (x ? common.days_of_week[new Date(x).getDay()] : null))); + expect(result['day_of_week("a")']).toEqual( + result.a.map((x) => + x ? common.days_of_week[new Date(x).getDay()] : null + ) + ); view.delete(); table.delete(); }); - it("Month of year, date", async function() { + it("Month of year, date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['month_of_year("a")'] + expressions: ['month_of_year("a")'], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result['month_of_year("a")']).toEqual(result.a.map(x => common.months_of_year[new Date(x).getMonth()])); + expect(result['month_of_year("a")']).toEqual( + result.a.map( + (x) => common.months_of_year[new Date(x).getMonth()] + ) + ); view.delete(); table.delete(); }); - it("Month of year, date with null", async function() { + it("Month of year, date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ['month_of_year("a")'] + expressions: ['month_of_year("a")'], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result['month_of_year("a")']).toEqual(result.a.map(x => (x ? common.months_of_year[new Date(x).getMonth()] : null))); + expect(result['month_of_year("a")']).toEqual( + result.a.map((x) => + x ? common.months_of_year[new Date(x).getMonth()] : null + ) + ); view.delete(); table.delete(); }); - it("Bucket (s), date", async function() { + it("Bucket (s), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 's')"] + expressions: ["bucket(\"a\", 's')"], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); @@ -238,35 +342,53 @@ module.exports = perspective => { table.delete(); }); - it("Bucket (s), date with nulls", async function() { + it("Bucket (s), date with nulls", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 's')"] + expressions: ["bucket(\"a\", 's')"], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 's')"]).toEqual(result.a.map(x => (x ? x : null))); + expect(result["bucket(\"a\", 's')"]).toEqual( + result.a.map((x) => (x ? x : null)) + ); view.delete(); table.delete(); }); - it("Bucket (m), date", async function() { + it("Bucket (m), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'm')"] + expressions: ["bucket(\"a\", 'm')"], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); @@ -275,36 +397,54 @@ module.exports = perspective => { table.delete(); }); - it("Bucket (m), date with nulls", async function() { + it("Bucket (m), date with nulls", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'm')"] + expressions: ["bucket(\"a\", 'm')"], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'm')"]).toEqual(result.a.map(x => (x ? x : null))); + expect(result["bucket(\"a\", 'm')"]).toEqual( + result.a.map((x) => (x ? x : null)) + ); view.delete(); table.delete(); }); - it("Bucket (h), date", async function() { + it("Bucket (h), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'h')"] + expressions: ["bucket(\"a\", 'h')"], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); @@ -313,36 +453,54 @@ module.exports = perspective => { table.delete(); }); - it("Bucket (h), date with nulls", async function() { + it("Bucket (h), date with nulls", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'h')"] + expressions: ["bucket(\"a\", 'h')"], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'h')"]).toEqual(result.a.map(x => (x ? x : null))); + expect(result["bucket(\"a\", 'h')"]).toEqual( + result.a.map((x) => (x ? x : null)) + ); view.delete(); table.delete(); }); - it("Bucket (D), date", async function() { + it("Bucket (D), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'D')"] + expressions: ["bucket(\"a\", 'D')"], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); @@ -351,588 +509,834 @@ module.exports = perspective => { table.delete(); }); - it("Bucket (D), date with null", async function() { + it("Bucket (D), date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'D')"] + expressions: ["bucket(\"a\", 'D')"], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'D')"]).toEqual(result.a.map(x => (x ? x : null))); + expect(result["bucket(\"a\", 'D')"]).toEqual( + result.a.map((x) => (x ? x : null)) + ); view.delete(); table.delete(); }); - it("Bucket (W), date", async function() { + it("Bucket (W), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'W')"] + expressions: ["bucket(\"a\", 'W')"], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2020, 0, 17), new Date(2020, 0, 18), new Date(2020, 0, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2020, 0, 17), + new Date(2020, 0, 18), + new Date(2020, 0, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'W')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.week_bucket(x))); + expect( + result["bucket(\"a\", 'W')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => common.week_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (W), date with null", async function() { + it("Bucket (W), date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'W')"] + expressions: ["bucket(\"a\", 'W')"], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2020, 0, 17), new Date(2020, 0, 18), new Date(2020, 0, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2020, 0, 17), + new Date(2020, 0, 18), + new Date(2020, 0, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'W')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.week_bucket(x) : null))); + expect( + result["bucket(\"a\", 'W')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => (x ? common.week_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (W), date shouldn't ever overflow at beginning of year", async function() { + it("Bucket (W), date shouldn't ever overflow at beginning of year", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'W')"] + expressions: ["bucket(\"a\", 'W')"], }); table.update({ - a: [new Date(2015, 0, 3, 15), new Date(2015, 0, 4)] + a: [new Date(2015, 0, 3, 15), new Date(2015, 0, 4)], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'W')"].map(x => new Date(x))).toEqual(result.a.map(x => common.week_bucket(x))); + expect( + result["bucket(\"a\", 'W')"].map((x) => new Date(x)) + ).toEqual(result.a.map((x) => common.week_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (M), date", async function() { + it("Bucket (M), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'M')"] + expressions: ["bucket(\"a\", 'M')"], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2020, 1, 17), new Date(2020, 2, 18), new Date(2020, 2, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2020, 1, 17), + new Date(2020, 2, 18), + new Date(2020, 2, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'M')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.month_bucket(x))); + expect( + result["bucket(\"a\", 'M')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => common.month_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (M), date with null", async function() { + it("Bucket (M), date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'M')"] + expressions: ["bucket(\"a\", 'M')"], }); table.update({ - a: [new Date(2020, 0, 12), null, undefined, new Date(2020, 2, 18), new Date(2020, 2, 29)] + a: [ + new Date(2020, 0, 12), + null, + undefined, + new Date(2020, 2, 18), + new Date(2020, 2, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'M')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.month_bucket(x) : null))); + expect( + result["bucket(\"a\", 'M')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => (x ? common.month_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (Y), date", async function() { + it("Bucket (Y), date", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'Y')"] + expressions: ["bucket(\"a\", 'Y')"], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2021, 1, 17), new Date(2019, 2, 18), new Date(2019, 2, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2021, 1, 17), + new Date(2019, 2, 18), + new Date(2019, 2, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'Y')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.year_bucket(x))); + expect( + result["bucket(\"a\", 'Y')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => common.year_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (Y), date with null", async function() { + it("Bucket (Y), date with null", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: ["bucket(\"a\", 'Y')"] + expressions: ["bucket(\"a\", 'Y')"], }); table.update({ - a: [new Date(2020, 0, 12), null, undefined, new Date(2019, 2, 18), new Date(2019, 2, 29)] + a: [ + new Date(2020, 0, 12), + null, + undefined, + new Date(2019, 2, 18), + new Date(2019, 2, 29), + ], }); let result = await view.to_columns(); - expect(result["bucket(\"a\", 'Y')"].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.year_bucket(x) : null))); + expect( + result["bucket(\"a\", 'Y')"].map((x) => + x ? new Date(x) : null + ) + ).toEqual(result.a.map((x) => (x ? common.year_bucket(x) : null))); view.delete(); table.delete(); }); }); - describe("Datetime, Arity 1 computed", function() { - it("Hour of day, datetime", async function() { + describe("Datetime, Arity 1 computed", function () { + it("Hour of day, datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`hour_of_day("a")`] + expressions: [`hour_of_day("a")`], }); table.update({ - a: [new Date(), new Date(), new Date()] + a: [new Date(), new Date(), new Date()], }); let result = await view.to_columns(); - expect(result[`hour_of_day("a")`]).toEqual(result.a.map(x => new Date(x).getUTCHours())); + expect(result[`hour_of_day("a")`]).toEqual( + result.a.map((x) => new Date(x).getUTCHours()) + ); view.delete(); table.delete(); }); - it("Hour of day, datetime with null", async function() { + it("Hour of day, datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`hour_of_day("a")`] + expressions: [`hour_of_day("a")`], }); table.update({ - a: [new Date(), null, undefined, new Date()] + a: [new Date(), null, undefined, new Date()], }); let result = await view.to_columns(); - expect(result[`hour_of_day("a")`]).toEqual(result.a.map(x => (x ? new Date(x).getUTCHours() : null))); + expect(result[`hour_of_day("a")`]).toEqual( + result.a.map((x) => (x ? new Date(x).getUTCHours() : null)) + ); view.delete(); table.delete(); }); - it("Day of week, datetime", async function() { + it("Day of week, datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`day_of_week("a")`] + expressions: [`day_of_week("a")`], }); table.update({ - a: [new Date(2020, 0, 26, 1), new Date(2020, 0, 27, 2), new Date(2020, 0, 28, 3), new Date(2020, 0, 29, 4), new Date(2020, 0, 30, 5)] + a: [ + new Date(2020, 0, 26, 1), + new Date(2020, 0, 27, 2), + new Date(2020, 0, 28, 3), + new Date(2020, 0, 29, 4), + new Date(2020, 0, 30, 5), + ], }); let result = await view.to_columns(); - expect(result[`day_of_week("a")`]).toEqual(result.a.map(x => common.days_of_week[new Date(x).getUTCDay()])); + expect(result[`day_of_week("a")`]).toEqual( + result.a.map( + (x) => common.days_of_week[new Date(x).getUTCDay()] + ) + ); view.delete(); table.delete(); }); - it("Day of week, datetime with null", async function() { + it("Day of week, datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`day_of_week("a")`] + expressions: [`day_of_week("a")`], }); table.update({ - a: [new Date(2020, 0, 26, 1), null, undefined, new Date(2020, 0, 29, 4), new Date(2020, 0, 30, 5)] + a: [ + new Date(2020, 0, 26, 1), + null, + undefined, + new Date(2020, 0, 29, 4), + new Date(2020, 0, 30, 5), + ], }); let result = await view.to_columns(); - expect(result[`day_of_week("a")`]).toEqual(result.a.map(x => (x ? common.days_of_week[new Date(x).getUTCDay()] : null))); + expect(result[`day_of_week("a")`]).toEqual( + result.a.map((x) => + x ? common.days_of_week[new Date(x).getUTCDay()] : null + ) + ); view.delete(); table.delete(); }); - it("Month of year, datetime", async function() { + it("Month of year, datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`month_of_year("a")`] + expressions: [`month_of_year("a")`], }); table.update({ - a: [new Date(2020, 0, 15), new Date(2020, 1, 27), new Date(2020, 2, 28), new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + new Date(2020, 1, 27), + new Date(2020, 2, 28), + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result[`month_of_year("a")`]).toEqual(result.a.map(x => common.months_of_year[new Date(x).getUTCMonth()])); + expect(result[`month_of_year("a")`]).toEqual( + result.a.map( + (x) => common.months_of_year[new Date(x).getUTCMonth()] + ) + ); view.delete(); table.delete(); }); - it("Month of year, datetime", async function() { + it("Month of year, datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`month_of_year("a")`] + expressions: [`month_of_year("a")`], }); table.update({ - a: [new Date(2020, 0, 15), null, undefined, new Date(2020, 3, 29), new Date(2020, 4, 30), new Date(2020, 5, 31), new Date(2020, 6, 1)] + a: [ + new Date(2020, 0, 15), + null, + undefined, + new Date(2020, 3, 29), + new Date(2020, 4, 30), + new Date(2020, 5, 31), + new Date(2020, 6, 1), + ], }); let result = await view.to_columns(); - expect(result[`month_of_year("a")`]).toEqual(result.a.map(x => (x ? common.months_of_year[new Date(x).getUTCMonth()] : null))); + expect(result[`month_of_year("a")`]).toEqual( + result.a.map((x) => + x ? common.months_of_year[new Date(x).getUTCMonth()] : null + ) + ); view.delete(); table.delete(); }); - it("Bucket (s), datetime", async function() { + it("Bucket (s), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 's')`] + expressions: [`bucket("a", 's')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), new Date(2020, 1, 27, 1, 30, 30), new Date(2020, 2, 28, 1, 30, 45), new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + new Date(2020, 1, 27, 1, 30, 30), + new Date(2020, 2, 28, 1, 30, 45), + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 's')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.second_bucket(x))); + expect( + result[`bucket("a", 's')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.second_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (s), datetime with null", async function() { + it("Bucket (s), datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 's')`] + expressions: [`bucket("a", 's')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), null, undefined, new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + null, + undefined, + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 's')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.second_bucket(x) : null))); + expect( + result[`bucket("a", 's')`].map((x) => (x ? new Date(x) : null)) + ).toEqual( + result.a.map((x) => (x ? common.second_bucket(x) : null)) + ); view.delete(); table.delete(); }); - it("Bucket (m), datetime", async function() { + it("Bucket (m), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'm')`] + expressions: [`bucket("a", 'm')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), new Date(2020, 1, 27, 1, 30, 30), new Date(2020, 2, 28, 1, 30, 45), new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + new Date(2020, 1, 27, 1, 30, 30), + new Date(2020, 2, 28, 1, 30, 45), + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'm')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.minute_bucket(x))); + expect( + result[`bucket("a", 'm')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.minute_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (m), datetime with null", async function() { + it("Bucket (m), datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'm')`] + expressions: [`bucket("a", 'm')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), null, undefined, new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + null, + undefined, + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'm')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.minute_bucket(x) : null))); + expect( + result[`bucket("a", 'm')`].map((x) => (x ? new Date(x) : null)) + ).toEqual( + result.a.map((x) => (x ? common.minute_bucket(x) : null)) + ); view.delete(); table.delete(); }); - it("Bucket (h), datetime", async function() { + it("Bucket (h), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'h')`] + expressions: [`bucket("a", 'h')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), new Date(2020, 1, 27, 1, 30, 30), new Date(2020, 2, 28, 1, 30, 45), new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + new Date(2020, 1, 27, 1, 30, 30), + new Date(2020, 2, 28, 1, 30, 45), + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'h')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.hour_bucket(x))); + expect( + result[`bucket("a", 'h')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.hour_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (h), datetime with null", async function() { + it("Bucket (h), datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'h')`] + expressions: [`bucket("a", 'h')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), null, undefined, new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + null, + undefined, + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'h')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.hour_bucket(x) : null))); + expect( + result[`bucket("a", 'h')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.hour_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (D), datetime", async function() { + it("Bucket (D), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'D')`] + expressions: [`bucket("a", 'D')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), new Date(2020, 1, 27, 1, 30, 30), new Date(2020, 2, 28, 1, 30, 45), new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + new Date(2020, 1, 27, 1, 30, 30), + new Date(2020, 2, 28, 1, 30, 45), + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'D')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.day_bucket(x))); + expect( + result[`bucket("a", 'D')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.day_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (D), datetime with null", async function() { + it("Bucket (D), datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'D')`] + expressions: [`bucket("a", 'D')`], }); table.update({ - a: [new Date(2020, 0, 15, 1, 30, 15), null, undefined, new Date(2020, 3, 29, 1, 30, 0), new Date(2020, 4, 30, 1, 30, 15)] + a: [ + new Date(2020, 0, 15, 1, 30, 15), + null, + undefined, + new Date(2020, 3, 29, 1, 30, 0), + new Date(2020, 4, 30, 1, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'D')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.day_bucket(x) : null))); + expect( + result[`bucket("a", 'D')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.day_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (D), datetime at UTC edge", async function() { + it("Bucket (D), datetime at UTC edge", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'D')`] + expressions: [`bucket("a", 'D')`], }); table.update({ - a: [new Date(2020, 0, 15, 23, 30, 15), null, undefined, new Date(2020, 3, 29, 23, 30, 0), new Date(2020, 4, 30, 23, 30, 15)] + a: [ + new Date(2020, 0, 15, 23, 30, 15), + null, + undefined, + new Date(2020, 3, 29, 23, 30, 0), + new Date(2020, 4, 30, 23, 30, 15), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'D')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.day_bucket(x) : null))); + expect( + result[`bucket("a", 'D')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.day_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (W), datetime", async function() { + it("Bucket (W), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'W')`] + expressions: [`bucket("a", 'W')`], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2020, 0, 17), new Date(2020, 0, 18), new Date(2020, 0, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2020, 0, 17), + new Date(2020, 0, 18), + new Date(2020, 0, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'W')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.week_bucket(x))); + expect( + result[`bucket("a", 'W')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.week_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (W), datetime with null", async function() { + it("Bucket (W), datetime with null", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'W')`] + expressions: [`bucket("a", 'W')`], }); table.update({ - a: [new Date(2020, 0, 12), null, undefined, new Date(2020, 0, 18), new Date(2020, 0, 29)] + a: [ + new Date(2020, 0, 12), + null, + undefined, + new Date(2020, 0, 18), + new Date(2020, 0, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'W')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.week_bucket(x) : null))); + expect( + result[`bucket("a", 'W')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.week_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (W), datetime shouldn't ever overflow at beginning of year", async function() { + it("Bucket (W), datetime shouldn't ever overflow at beginning of year", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'W')`] + expressions: [`bucket("a", 'W')`], }); table.update({ - a: [new Date(2015, 0, 3, 15), new Date(2015, 0, 4)] + a: [new Date(2015, 0, 3, 15), new Date(2015, 0, 4)], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'W')`].map(x => new Date(x))).toEqual(result.a.map(x => common.week_bucket(x))); + expect(result[`bucket("a", 'W')`].map((x) => new Date(x))).toEqual( + result.a.map((x) => common.week_bucket(x)) + ); view.delete(); table.delete(); }); - it("Bucket (M), datetime", async function() { + it("Bucket (M), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'M')`] + expressions: [`bucket("a", 'M')`], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2020, 1, 17), new Date(2020, 2, 18), new Date(2020, 2, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2020, 1, 17), + new Date(2020, 2, 18), + new Date(2020, 2, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'M')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.month_bucket(x))); + expect( + result[`bucket("a", 'M')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.month_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (M), datetime with nulls", async function() { + it("Bucket (M), datetime with nulls", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'M')`] + expressions: [`bucket("a", 'M')`], }); table.update({ - a: [new Date(2020, 0, 12), null, undefined, new Date(2020, 2, 18), new Date(2020, 2, 29)] + a: [ + new Date(2020, 0, 12), + null, + undefined, + new Date(2020, 2, 18), + new Date(2020, 2, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'M')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.month_bucket(x) : null))); + expect( + result[`bucket("a", 'M')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.month_bucket(x) : null))); view.delete(); table.delete(); }); - it("Bucket (Y), datetime", async function() { + it("Bucket (Y), datetime", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'Y')`] + expressions: [`bucket("a", 'Y')`], }); table.update({ - a: [new Date(2020, 0, 12), new Date(2020, 0, 15), new Date(2021, 11, 17), new Date(2019, 2, 18), new Date(2019, 2, 29)] + a: [ + new Date(2020, 0, 12), + new Date(2020, 0, 15), + new Date(2021, 11, 17), + new Date(2019, 2, 18), + new Date(2019, 2, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'Y')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => common.year_bucket(x))); + expect( + result[`bucket("a", 'Y')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => common.year_bucket(x))); view.delete(); table.delete(); }); - it("Bucket (Y), datetime with nulls", async function() { + it("Bucket (Y), datetime with nulls", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`bucket("a", 'Y')`] + expressions: [`bucket("a", 'Y')`], }); table.update({ - a: [new Date(2020, 0, 12), null, undefined, new Date(2019, 2, 18), new Date(2019, 2, 29)] + a: [ + new Date(2020, 0, 12), + null, + undefined, + new Date(2019, 2, 18), + new Date(2019, 2, 29), + ], }); let result = await view.to_columns(); - expect(result[`bucket("a", 'Y')`].map(x => (x ? new Date(x) : null))).toEqual(result.a.map(x => (x ? common.year_bucket(x) : null))); + expect( + result[`bucket("a", 'Y')`].map((x) => (x ? new Date(x) : null)) + ).toEqual(result.a.map((x) => (x ? common.year_bucket(x) : null))); view.delete(); table.delete(); }); diff --git a/packages/perspective/test/js/expressions/deltas.js b/packages/perspective/test/js/expressions/deltas.js index 3aab89c9fc..4c65bfacbd 100644 --- a/packages/perspective/test/js/expressions/deltas.js +++ b/packages/perspective/test/js/expressions/deltas.js @@ -7,7 +7,7 @@ * */ -const match_delta = async function(perspective, delta, expected) { +const match_delta = async function (perspective, delta, expected) { let table = await perspective.table(delta); let view = await table.view(); let json = await view.to_json(); @@ -20,23 +20,33 @@ const match_delta = async function(perspective, delta, expected) { * Tests the correctness of updates on Tables with expression columns created * through `View` and deltas created through `on_update`. */ -module.exports = perspective => { - describe("Expression column update deltas", function() { - describe("0-sided expression column deltas", function() { - it("Returns appended rows for normal and expression columns", async function(done) { +module.exports = (perspective) => { + describe("Expression column update deltas", function () { + describe("0-sided expression column deltas", function () { + it("Returns appended rows for normal and expression columns", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ['lower("y")', '-"x"'] + expressions: ['lower("y")', '-"x"'], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ - {x: 1, y: "HELLO", 'lower("y")': "hello", '-"x"': -1}, - {x: 3, y: "WORLD", 'lower("y")': "world", '-"x"': -3} + { + x: 1, + y: "HELLO", + 'lower("y")': "hello", + '-"x"': -1, + }, + { + x: 3, + y: "WORLD", + 'lower("y")': "world", + '-"x"': -3, + }, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -49,22 +59,22 @@ module.exports = perspective => { table.update({x: [1, 3], y: ["HELLO", "WORLD"]}); }); - it("Returns appended rows for normal and expression columns from schema", async function(done) { + it("Returns appended rows for normal and expression columns from schema", async function (done) { const table = await perspective.table({ x: "integer", - y: "string" + y: "string", }); const view = await table.view({ - expressions: ['upper("y")'] + expressions: ['upper("y")'], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "a", 'upper("y")': "A"}, {x: 2, y: "b", 'upper("y")': "B"}, {x: 3, y: "c", 'upper("y")': "C"}, - {x: 4, y: "d", 'upper("y")': "D"} + {x: 4, y: "d", 'upper("y")': "D"}, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -76,33 +86,33 @@ module.exports = perspective => { table.update({ x: [1, 2, 3, 4], - y: ["a", "b", "c", "d"] + y: ["a", "b", "c", "d"], }); }); - it("Returns partially updated rows for normal and expression columns", async function(done) { + it("Returns partially updated rows for normal and expression columns", async function (done) { const table = await perspective.table( { x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, {index: "x"} ); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); view.on_update( - async function(updated) { + async function (updated) { const full = await view.to_columns(); const expected = [ {x: 1, y: "HELLO", 'lower("y")': "hello"}, - {x: 3, y: "WORLD", 'lower("y")': "world"} + {x: 3, y: "WORLD", 'lower("y")': "world"}, ]; expect(full).toEqual({ x: [1, 2, 3, 4], y: ["HELLO", "B", "WORLD", "D"], - 'lower("y")': ["hello", "b", "world", "d"] + 'lower("y")': ["hello", "b", "world", "d"], }); await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -115,27 +125,27 @@ module.exports = perspective => { table.update({x: [1, 3], y: ["HELLO", "WORLD"]}); }); - it("Returns appended rows with missing columns for normal and expression columns", async function(done) { + it("Returns appended rows with missing columns for normal and expression columns", async function (done) { const self = this; const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); self.view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); self.view.on_update( - async function(updated) { + async function (updated) { const full = await self.view.to_columns(); const expected = [ {x: 1, y: null, 'lower("y")': null}, - {x: 3, y: null, 'lower("y")': null} + {x: 3, y: null, 'lower("y")': null}, ]; expect(full).toEqual({ x: [1, 2, 3, 4, 1, 3], y: ["A", "B", "C", "D", null, null], - 'lower("y")': ["a", "b", "c", "d", null, null] + 'lower("y")': ["a", "b", "c", "d", null, null], }); await match_delta(perspective, updated.delta, expected); await self.view.delete(); @@ -149,23 +159,23 @@ module.exports = perspective => { }); }); - describe("1-sided expression column deltas", function() { - it("Returns appended rows for normal and expression columns, 1-sided", async function(done) { + describe("1-sided expression column deltas", function () { + it("Returns appended rows for normal and expression columns, 1-sided", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ row_pivots: ['lower("y")'], - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 14, y: 6, 'lower("y")': 6}, {x: 1, y: 1, 'lower("y")': 1}, - {x: 3, y: 1, 'lower("y")': 1} + {x: 3, y: 1, 'lower("y")': 1}, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -178,20 +188,20 @@ module.exports = perspective => { table.update({x: [1, 3], y: ["HELLO", "WORLD"]}); }); - it("Returns appended rows for normal and expression columns, 1-sided hidden sorted", async function(done) { + it("Returns appended rows for normal and expression columns, 1-sided hidden sorted", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ row_pivots: ['lower("y")'], expressions: ['lower("y")'], sort: [['lower("y")', "desc"]], - columns: ["x"] + columns: ["x"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [{x: 14}, {x: 3}, {x: 1}]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -205,23 +215,23 @@ module.exports = perspective => { }); }); - describe("2-sided expression column deltas", function() { - it("Returns appended rows for normal and expression columns, 2-sided", async function(done) { + describe("2-sided expression column deltas", function () { + it("Returns appended rows for normal and expression columns, 2-sided", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ aggregates: { - 'lower("y")': "last" + 'lower("y")': "last", }, row_pivots: ['lower("y")'], column_pivots: ["y"], - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ { 'A|lower("y")': "a", @@ -241,7 +251,7 @@ module.exports = perspective => { "HELLO|y": 1, 'WORLD|lower("y")': "world", "WORLD|x": 3, - "WORLD|y": 1 + "WORLD|y": 1, }, { 'A|lower("y")': null, @@ -261,7 +271,7 @@ module.exports = perspective => { "HELLO|y": 1, 'WORLD|lower("y")': null, "WORLD|x": null, - "WORLD|y": null + "WORLD|y": null, }, { 'A|lower("y")': null, @@ -281,8 +291,8 @@ module.exports = perspective => { "HELLO|y": null, 'WORLD|lower("y")': "world", "WORLD|x": 3, - "WORLD|y": 1 - } + "WORLD|y": 1, + }, ]; await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -296,34 +306,34 @@ module.exports = perspective => { }); }); - describe("0-sided expression column deltas with multiple views", function() { - it("`on_update` on a view with expression column should contain expression delta when columns are appended", async function(done) { + describe("0-sided expression column deltas with multiple views", function () { + it("`on_update` on a view with expression column should contain expression delta when columns are appended", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const pre_update = await view.to_columns(); expect(pre_update).toEqual({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"] + 'lower("y")': ["a", "b", "c", "d"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: null, 'lower("y")': null}, - {x: 3, y: null, 'lower("y")': null} + {x: 3, y: null, 'lower("y")': null}, ]; const full = await view.to_columns(); expect(full).toEqual({ x: [1, 2, 3, 4, 1, 3], y: ["A", "B", "C", "D", null, null], - 'lower("y")': ["a", "b", "c", "d", null, null] + 'lower("y")': ["a", "b", "c", "d", null, null], }); await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -336,16 +346,16 @@ module.exports = perspective => { table.update({x: [1, 3]}); }); - it("`on_update` on a view with expression column should contain expression delta when columns are updated", async function(done) { + it("`on_update` on a view with expression column should contain expression delta when columns are updated", async function (done) { const table = await perspective.table( { x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, {index: "x"} ); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const pre_update = await view.to_columns(); @@ -353,20 +363,20 @@ module.exports = perspective => { expect(pre_update).toEqual({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"] + 'lower("y")': ["a", "b", "c", "d"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "ABCD", 'lower("y")': "abcd"}, - {x: 3, y: null, 'lower("y")': null} + {x: 3, y: null, 'lower("y")': null}, ]; const full = await view.to_columns(); expect(full).toEqual({ x: [1, 2, 3, 4], y: ["ABCD", "B", null, "D"], - 'lower("y")': ["abcd", "b", null, "d"] + 'lower("y")': ["abcd", "b", null, "d"], }); await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -379,36 +389,36 @@ module.exports = perspective => { table.update({x: [1, 3], y: ["ABCD", null]}); }); - it("`on_update` on different views with different expression columns should only be notified of their columns", async function(done) { + it("`on_update` on different views with different expression columns should only be notified of their columns", async function (done) { const table = await perspective.table( { x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, { - index: "x" + index: "x", } ); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const view2 = await table.view({ - expressions: ['-"x"'] + expressions: ['-"x"'], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "HELLO", 'lower("y")': "hello"}, - {x: 3, y: "WORLD", 'lower("y")': "world"} + {x: 3, y: "WORLD", 'lower("y")': "world"}, ]; const full = await view.to_columns(); expect(full).toEqual({ x: [1, 2, 3, 4], y: ["HELLO", "B", "WORLD", "D"], - 'lower("y")': ["hello", "b", "world", "d"] + 'lower("y")': ["hello", "b", "world", "d"], }); await match_delta(perspective, updated.delta, expected); await view.delete(); @@ -417,16 +427,16 @@ module.exports = perspective => { ); view2.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "HELLO", '-"x"': -1}, - {x: 3, y: "WORLD", '-"x"': -3} + {x: 3, y: "WORLD", '-"x"': -3}, ]; const full = await view2.to_columns(); expect(full).toEqual({ x: [1, 2, 3, 4], y: ["HELLO", "B", "WORLD", "D"], - '-"x"': [-1, -2, -3, -4] + '-"x"': [-1, -2, -3, -4], }); await match_delta(perspective, updated.delta, expected); await view2.delete(); @@ -439,28 +449,28 @@ module.exports = perspective => { table.update({x: [1, 3], y: ["HELLO", "WORLD"]}); }); - it("`on_update` on view without expression column should not be notified of expression column", async function(done) { + it("`on_update` on view without expression column should not be notified of expression column", async function (done) { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view(); const view2 = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); expect(await view2.to_columns()).toEqual({ x: [1, 2, 3, 4], y: ["A", "B", "C", "D"], - 'lower("y")': ["a", "b", "c", "d"] + 'lower("y")': ["a", "b", "c", "d"], }); view.on_update( - async function(updated) { + async function (updated) { const expected = [ {x: 1, y: "abc"}, - {x: 3, y: "def"} + {x: 3, y: "def"}, ]; await match_delta(perspective, updated.delta, expected); await view2.delete(); diff --git a/packages/perspective/test/js/expressions/functionality.js b/packages/perspective/test/js/expressions/functionality.js index c23709345b..1250c5ff27 100644 --- a/packages/perspective/test/js/expressions/functionality.js +++ b/packages/perspective/test/js/expressions/functionality.js @@ -14,135 +14,212 @@ const expressions_common = require("./common.js"); * existing column/view semantics (pivots, aggregates, columns, sorts, filters) * continue to be functional on expressions. */ -module.exports = perspective => { - describe("Expression structures", function() { - describe("if statements", function() { - it("if without else should be invalid", async function() { - const table = await perspective.table(expressions_common.int_float_data); - const validate = await table.validate_expressions(['if ("w" > 1) 5;', 'if ("w" > 1) 5']); - - expect(validate.expression_schema['if ("w" > 1) 5;']).toBeUndefined(); - expect(validate.expression_schema['if ("w" > 1) 5']).toBeUndefined(); +module.exports = (perspective) => { + describe("Expression structures", function () { + describe("if statements", function () { + it("if without else should be invalid", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); + const validate = await table.validate_expressions([ + 'if ("w" > 1) 5;', + 'if ("w" > 1) 5', + ]); + + expect( + validate.expression_schema['if ("w" > 1) 5;'] + ).toBeUndefined(); + expect( + validate.expression_schema['if ("w" > 1) 5'] + ).toBeUndefined(); expect(validate.errors).toEqual({ 'if ("w" > 1) 5': { column: 0, error_message: "", - line: 8 + line: 8, }, 'if ("w" > 1) 5;': { column: 0, error_message: "", - line: 8 - } + line: 8, + }, }); table.delete(); }); - it.skip("if else with multiple return types should be invalid", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it.skip("if else with multiple return types should be invalid", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["if (\"w\" == 1.5) 5; else 'abc'"] + expressions: ["if (\"w\" == 1.5) 5; else 'abc'"], }); const results = await view.to_columns(); // TODO: we should prevent this polymorphic return from being // allowed, but the machinery around how if/else returns is // opaque to me at the moment. - expect(results["if (\"w\" == 1.5) 5; else 'abc'"]).toEqual(["", "abc", "abc", "abc"]); + expect(results["if (\"w\" == 1.5) 5; else 'abc'"]).toEqual([ + "", + "abc", + "abc", + "abc", + ]); view.delete(); table.delete(); }); - it("functional if", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("functional if", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['if ("w" > 2, 5, 10);', 'if ("w" == 1.5, 5, 10)'] + expressions: [ + 'if ("w" > 2, 5, 10);', + 'if ("w" == 1.5, 5, 10)', + ], }); const results = await view.to_columns(); expect(results['if ("w" > 2, 5, 10);']).toEqual([10, 5, 5, 5]); - expect(results['if ("w" == 1.5, 5, 10)']).toEqual([5, 10, 10, 10]); + expect(results['if ("w" == 1.5, 5, 10)']).toEqual([ + 5, 10, 10, 10, + ]); view.delete(); table.delete(); }); - it("functional if string", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("functional if string", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["if (\"y\" == 'a', 5, 10);", "if (\"y\" != 'a', 5, 10)"] + expressions: [ + "if (\"y\" == 'a', 5, 10);", + "if (\"y\" != 'a', 5, 10)", + ], }); const results = await view.to_columns(); - expect(results["if (\"y\" == 'a', 5, 10);"]).toEqual([5, 10, 10, 10]); - expect(results["if (\"y\" != 'a', 5, 10)"]).toEqual([10, 5, 5, 5]); + expect(results["if (\"y\" == 'a', 5, 10);"]).toEqual([ + 5, 10, 10, 10, + ]); + expect(results["if (\"y\" != 'a', 5, 10)"]).toEqual([ + 10, 5, 5, 5, + ]); view.delete(); table.delete(); }); - it.skip("functional if bool", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it.skip("functional if bool", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['if ("z" == true, 5, 10);', 'if ("z" != true, 5, 10)'] + expressions: [ + 'if ("z" == true, 5, 10);', + 'if ("z" != true, 5, 10)', + ], }); const results = await view.to_columns(); - expect(results['if ("z" == 1, 5, 10);']).toEqual([5, 10, 5, 10]); - expect(results['if ("z" != true, 5, 10)']).toEqual([10, 5, 10, 5]); + expect(results['if ("z" == 1, 5, 10);']).toEqual([ + 5, 10, 5, 10, + ]); + expect(results['if ("z" != true, 5, 10)']).toEqual([ + 10, 5, 10, 5, + ]); view.delete(); table.delete(); }); - it("functional if date", async function() { + it("functional if date", async function () { const table = await perspective.table({ a: "date", - b: "date" + b: "date", }); const view = await table.view({ - expressions: ['if ("a" == "b", 5, 10);', 'if ("a" < "b", 5, 10)'] + expressions: [ + 'if ("a" == "b", 5, 10);', + 'if ("a" < "b", 5, 10)', + ], }); table.update({ - a: [new Date(2020, 3, 1), new Date(2012, 0, 30), new Date(2018, 9, 31), new Date(2020, 1, 29)], - b: [new Date(2020, 3, 1), new Date(2012, 1, 30), new Date(2018, 10, 31), new Date(2020, 1, 29)] + a: [ + new Date(2020, 3, 1), + new Date(2012, 0, 30), + new Date(2018, 9, 31), + new Date(2020, 1, 29), + ], + b: [ + new Date(2020, 3, 1), + new Date(2012, 1, 30), + new Date(2018, 10, 31), + new Date(2020, 1, 29), + ], }); const results = await view.to_columns(); - expect(results['if ("a" == "b", 5, 10);']).toEqual([5, 10, 10, 5]); - expect(results['if ("a" < "b", 5, 10)']).toEqual([10, 5, 5, 10]); + expect(results['if ("a" == "b", 5, 10);']).toEqual([ + 5, 10, 10, 5, + ]); + expect(results['if ("a" < "b", 5, 10)']).toEqual([ + 10, 5, 5, 10, + ]); view.delete(); table.delete(); }); - it("functional if datetime", async function() { + it("functional if datetime", async function () { const table = await perspective.table({ a: "datetime", - b: "datetime" + b: "datetime", }); const view = await table.view({ - expressions: ['if ("a" >= "b", 5, 10);', 'if ("a" < "b", 5, 10)'] + expressions: [ + 'if ("a" >= "b", 5, 10);', + 'if ("a" < "b", 5, 10)', + ], }); table.update({ - a: [new Date(2020, 3, 1, 12, 30, 45, 850), new Date(2012, 0, 30, 19, 25), new Date(2018, 9, 31, 3, 13), new Date(2020, 1, 29, 23, 59, 59)], - b: [new Date(2020, 3, 1, 12, 30, 45, 850), new Date(2012, 1, 30), new Date(2018, 10, 31), new Date(2020, 1, 29)] + a: [ + new Date(2020, 3, 1, 12, 30, 45, 850), + new Date(2012, 0, 30, 19, 25), + new Date(2018, 9, 31, 3, 13), + new Date(2020, 1, 29, 23, 59, 59), + ], + b: [ + new Date(2020, 3, 1, 12, 30, 45, 850), + new Date(2012, 1, 30), + new Date(2018, 10, 31), + new Date(2020, 1, 29), + ], }); const results = await view.to_columns(); - expect(results['if ("a" >= "b", 5, 10);']).toEqual([5, 10, 10, 5]); - expect(results['if ("a" < "b", 5, 10)']).toEqual([10, 5, 5, 10]); + expect(results['if ("a" >= "b", 5, 10);']).toEqual([ + 5, 10, 10, 5, + ]); + expect(results['if ("a" < "b", 5, 10)']).toEqual([ + 10, 5, 5, 10, + ]); view.delete(); table.delete(); }); - it("ternary if", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("ternary if", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" > 2 ? 5 : 10;', '"w" == 1.5 ? 5 : 10'] + expressions: ['"w" > 2 ? 5 : 10;', '"w" == 1.5 ? 5 : 10'], }); const results = await view.to_columns(); @@ -152,23 +229,35 @@ module.exports = perspective => { table.delete(); }); - it("ternary if string", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("ternary if string", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["\"y\" == 'a' ? 5 : 10;", "\"y\" != 'a' ? 5 : 10"] + expressions: [ + "\"y\" == 'a' ? 5 : 10;", + "\"y\" != 'a' ? 5 : 10", + ], }); const results = await view.to_columns(); - expect(results["\"y\" == 'a' ? 5 : 10;"]).toEqual([5, 10, 10, 10]); + expect(results["\"y\" == 'a' ? 5 : 10;"]).toEqual([ + 5, 10, 10, 10, + ]); expect(results["\"y\" != 'a' ? 5 : 10"]).toEqual([10, 5, 5, 5]); view.delete(); table.delete(); }); - it.skip("ternary if bool", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it.skip("ternary if bool", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"z" == true ? 5 : 10;', '"z" != true ? 5 : 10'] + expressions: [ + '"z" == true ? 5 : 10;', + '"z" != true ? 5 : 10', + ], }); const results = await view.to_columns(); @@ -178,19 +267,29 @@ module.exports = perspective => { table.delete(); }); - it("ternary if date", async function() { + it("ternary if date", async function () { const table = await perspective.table({ a: "date", - b: "date" + b: "date", }); const view = await table.view({ - expressions: ['"a" == "b" ? 5 : 10;', '"a" < "b" ? 5 : 10'] + expressions: ['"a" == "b" ? 5 : 10;', '"a" < "b" ? 5 : 10'], }); table.update({ - a: [new Date(2020, 3, 1), new Date(2012, 0, 30), new Date(2018, 9, 31), new Date(2020, 1, 29)], - b: [new Date(2020, 3, 1), new Date(2012, 1, 30), new Date(2018, 10, 31), new Date(2020, 1, 29)] + a: [ + new Date(2020, 3, 1), + new Date(2012, 0, 30), + new Date(2018, 9, 31), + new Date(2020, 1, 29), + ], + b: [ + new Date(2020, 3, 1), + new Date(2012, 1, 30), + new Date(2018, 10, 31), + new Date(2020, 1, 29), + ], }); const results = await view.to_columns(); @@ -201,19 +300,29 @@ module.exports = perspective => { table.delete(); }); - it("ternary if datetime", async function() { + it("ternary if datetime", async function () { const table = await perspective.table({ a: "datetime", - b: "datetime" + b: "datetime", }); const view = await table.view({ - expressions: ['"a" >= "b" ? 5 : 10;', '"a" < "b" ? 5 : 10'] + expressions: ['"a" >= "b" ? 5 : 10;', '"a" < "b" ? 5 : 10'], }); table.update({ - a: [new Date(2020, 3, 1, 12, 30, 45, 850), new Date(2012, 0, 30, 19, 25), new Date(2018, 9, 31, 3, 13), new Date(2020, 1, 29, 23, 59, 59)], - b: [new Date(2020, 3, 1, 12, 30, 45, 850), new Date(2012, 1, 30), new Date(2018, 10, 31), new Date(2020, 1, 29)] + a: [ + new Date(2020, 3, 1, 12, 30, 45, 850), + new Date(2012, 0, 30, 19, 25), + new Date(2018, 9, 31, 3, 13), + new Date(2020, 1, 29, 23, 59, 59), + ], + b: [ + new Date(2020, 3, 1, 12, 30, 45, 850), + new Date(2012, 1, 30), + new Date(2018, 10, 31), + new Date(2020, 1, 29), + ], }); const results = await view.to_columns(); @@ -223,141 +332,223 @@ module.exports = perspective => { table.delete(); }); - it("if else", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("if else", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['if ("w" > 2) 5; else 10', 'if ("w" > 2) 5; else 10;', 'if ("w" > 2) { \n5; } else { \n10;}'] + expressions: [ + 'if ("w" > 2) 5; else 10', + 'if ("w" > 2) 5; else 10;', + 'if ("w" > 2) { \n5; } else { \n10;}', + ], }); const results = await view.to_columns(); - expect(results['if ("w" > 2) 5; else 10']).toEqual([10, 5, 5, 5]); - expect(results['if ("w" > 2) 5; else 10;']).toEqual([10, 5, 5, 5]); - expect(results['if ("w" > 2) { \n5; } else { \n10;}']).toEqual([10, 5, 5, 5]); + expect(results['if ("w" > 2) 5; else 10']).toEqual([ + 10, 5, 5, 5, + ]); + expect(results['if ("w" > 2) 5; else 10;']).toEqual([ + 10, 5, 5, 5, + ]); + expect(results['if ("w" > 2) { \n5; } else { \n10;}']).toEqual([ + 10, 5, 5, 5, + ]); view.delete(); table.delete(); }); - it("if else if", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("if else if", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ expressions: [ 'if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10', 'if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10;', - 'if ("w" == 3.5) { \n15; } else if ("w" > 2) { \n5; } else { \n10;}' - ] + 'if ("w" == 3.5) { \n15; } else if ("w" > 2) { \n5; } else { \n10;}', + ], }); const results = await view.to_columns(); - expect(results['if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10']).toEqual([10, 5, 15, 5]); - expect(results['if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10;']).toEqual([10, 5, 15, 5]); - expect(results['if ("w" == 3.5) { \n15; } else if ("w" > 2) { \n5; } else { \n10;}']).toEqual([10, 5, 15, 5]); + expect( + results['if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10'] + ).toEqual([10, 5, 15, 5]); + expect( + results['if ("w" == 3.5) 15; else if ("w" > 2) 5; else 10;'] + ).toEqual([10, 5, 15, 5]); + expect( + results[ + 'if ("w" == 3.5) { \n15; } else if ("w" > 2) { \n5; } else { \n10;}' + ] + ).toEqual([10, 5, 15, 5]); view.delete(); table.delete(); }); }); - describe("Switch", function() { - it("Switch on multiple columns", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe("Switch", function () { + it("Switch on multiple columns", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`switch { case "w" > 2: sqrt(144); case "y" == 'a': sqrt(121); default: 0; }`] + expressions: [ + `switch { case "w" > 2: sqrt(144); case "y" == 'a': sqrt(121); default: 0; }`, + ], }); const results = await view.to_columns(); - expect(results[`switch { case "w" > 2: sqrt(144); case "y" == 'a': sqrt(121); default: 0; }`]).toEqual([11, 12, 12, 12]); + expect( + results[ + `switch { case "w" > 2: sqrt(144); case "y" == 'a': sqrt(121); default: 0; }` + ] + ).toEqual([11, 12, 12, 12]); await view.delete(); await table.delete(); }); - it("Switch on multiple columns with resulting expression", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Switch on multiple columns with resulting expression", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`switch { case "w" > 2: (sqrt(144) * "w"); case "y" == 'a': (sqrt(121) * "w"); default: "x"; }`] + expressions: [ + `switch { case "w" > 2: (sqrt(144) * "w"); case "y" == 'a': (sqrt(121) * "w"); default: "x"; }`, + ], }); const results = await view.to_columns(); - expect(results[`switch { case "w" > 2: (sqrt(144) * "w"); case "y" == 'a': (sqrt(121) * "w"); default: "x"; }`]).toEqual([16.5, 30, 42, 54]); + expect( + results[ + `switch { case "w" > 2: (sqrt(144) * "w"); case "y" == 'a': (sqrt(121) * "w"); default: "x"; }` + ] + ).toEqual([16.5, 30, 42, 54]); await view.delete(); await table.delete(); }); }); - describe("While loop", function() { - it("Scalar while", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe("While loop", function () { + it("Scalar while", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 1; var y := 0; while (y < 10) { x := x * 2; y += 1; } x`] + expressions: [ + `var x := 1; var y := 0; while (y < 10) { x := x * 2; y += 1; } x`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 1; var y := 0; while (y < 10) { x := x * 2; y += 1; } x`]).toEqual([10240, 10240, 10240, 10240]); + expect( + results[ + `var x := 1; var y := 0; while (y < 10) { x := x * 2; y += 1; } x` + ] + ).toEqual([10240, 10240, 10240, 10240]); await view.delete(); await table.delete(); }); - it("Column while", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Column while", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 0; var y := 0; while (y < 10) { x := "w" * 2; y += 1; } x`] + expressions: [ + `var x := 0; var y := 0; while (y < 10) { x := "w" * 2; y += 1; } x`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 0; var y := 0; while (y < 10) { x := "w" * 2; y += 1; } x`]).toEqual([30, 50, 70, 90]); + expect( + results[ + `var x := 0; var y := 0; while (y < 10) { x := "w" * 2; y += 1; } x` + ] + ).toEqual([30, 50, 70, 90]); await view.delete(); await table.delete(); }); - it.skip("Column while with break", async function() { + it.skip("Column while with break", async function () { // FIXME: this can be parsed but the call to expression.value() // throws an error or aborts with an empty message. - const table = await perspective.table(expressions_common.int_float_data); + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 0; var y := 0; while (y < 10) { if (y % 2 == 0) { break; }; x := "w" * 2; y += 1; } x`] + expressions: [ + `var x := 0; var y := 0; while (y < 10) { if (y % 2 == 0) { break; }; x := "w" * 2; y += 1; } x`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 0; var y := 0; while (y < 10) { if (y % 2 == 0) { break; }; x := "w" * 2; y += 1; } x`]).toEqual([16.5, 30, 42, 54]); + expect( + results[ + `var x := 0; var y := 0; while (y < 10) { if (y % 2 == 0) { break; }; x := "w" * 2; y += 1; } x` + ] + ).toEqual([16.5, 30, 42, 54]); await view.delete(); await table.delete(); }); }); - describe("For loop", function() { - it("Scalar for", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe("For loop", function () { + it("Scalar for", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`for (var x := 0; x < 10; x += 1) { var y := x + 1; y}`] + expressions: [ + `for (var x := 0; x < 10; x += 1) { var y := x + 1; y}`, + ], }); const results = await view.to_columns(); - expect(results[`for (var x := 0; x < 10; x += 1) { var y := x + 1; y}`]).toEqual([10, 10, 10, 10]); + expect( + results[ + `for (var x := 0; x < 10; x += 1) { var y := x + 1; y}` + ] + ).toEqual([10, 10, 10, 10]); await view.delete(); await table.delete(); }); - it("Column for", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Column for", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`for (var x := 0; x < 10; x += 1) { var y := "w" + 1; y}`] + expressions: [ + `for (var x := 0; x < 10; x += 1) { var y := "w" + 1; y}`, + ], }); const results = await view.to_columns(); - expect(results[`for (var x := 0; x < 10; x += 1) { var y := "w" + 1; y}`]).toEqual([2.5, 3.5, 4.5, 5.5]); + expect( + results[ + `for (var x := 0; x < 10; x += 1) { var y := "w" + 1; y}` + ] + ).toEqual([2.5, 3.5, 4.5, 5.5]); await view.delete(); await table.delete(); }); - it.skip("for loop only valid on numbers", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it.skip("for loop only valid on numbers", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); // `for (var x := today(); x < 10; x += 1) { var y := "w" + 1; y}` - const expressions = [`for (var x := 'abc'; x < 10; x += 1) { var y := "w" + 1; y}`]; + const expressions = [ + `for (var x := 'abc'; x < 10; x += 1) { var y := "w" + 1; y}`, + ]; const validate = await table.validate_expressions(expressions); for (const expr of expressions) { @@ -368,28 +559,44 @@ module.exports = perspective => { }); }); - describe.skip("Repeat loop", function() { - it("Scalar repeat", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe.skip("Repeat loop", function () { + it("Scalar repeat", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 1; var y := 1; repeat x := x + (y * 2) until (x > 10)`] + expressions: [ + `var x := 1; var y := 1; repeat x := x + (y * 2) until (x > 10)`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 1; var y := 1; repeat x := x + (y * 2) until (x > 10)`]).toEqual([11, 11, 11, 11]); + expect( + results[ + `var x := 1; var y := 1; repeat x := x + (y * 2) until (x > 10)` + ] + ).toEqual([11, 11, 11, 11]); await view.delete(); await table.delete(); }); - it("Column repeat", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Column repeat", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 1; repeat x := x + ("x" * 2) until (x > 10)`] + expressions: [ + `var x := 1; repeat x := x + ("x" * 2) until (x > 10)`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 1; repeat x := x + ("x" * 2) until (x > 10)`]).toEqual([16.5, 30, 42, 54]); + expect( + results[ + `var x := 1; repeat x := x + ("x" * 2) until (x > 10)` + ] + ).toEqual([16.5, 30, 42, 54]); await view.delete(); await table.delete(); @@ -397,93 +604,135 @@ module.exports = perspective => { }); }); - describe("Local variables", function() { - it("Declare numeric", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe("Local variables", function () { + it("Declare numeric", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["1", "100.0000001"] + expressions: ["1", "100.0000001"], }); const results = await view.to_columns(); expect(results["1"]).toEqual([1, 1, 1, 1]); - expect(results["100.0000001"]).toEqual([100.0000001, 100.0000001, 100.0000001, 100.0000001]); + expect(results["100.0000001"]).toEqual([ + 100.0000001, 100.0000001, 100.0000001, 100.0000001, + ]); await view.delete(); await table.delete(); }); - it("Declare string", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare string", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["'hello'", "'very long string that is very long and has many characters'"] + expressions: [ + "'hello'", + "'very long string that is very long and has many characters'", + ], }); const results = await view.to_columns(); - expect(results["'hello'"]).toEqual(["hello", "hello", "hello", "hello"]); - expect(results["'very long string that is very long and has many characters'"]).toEqual([ + expect(results["'hello'"]).toEqual([ + "hello", + "hello", + "hello", + "hello", + ]); + expect( + results[ + "'very long string that is very long and has many characters'" + ] + ).toEqual([ + "very long string that is very long and has many characters", "very long string that is very long and has many characters", "very long string that is very long and has many characters", "very long string that is very long and has many characters", - "very long string that is very long and has many characters" ]); await view.delete(); await table.delete(); }); - it("Declare numeric var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare numeric var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 10; var y := -100.00005; var z := x + y; abs(z)`] + expressions: [ + `var x := 10; var y := -100.00005; var z := x + y; abs(z)`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 10; var y := -100.00005; var z := x + y; abs(z)`]).toEqual([90.00005, 90.00005, 90.00005, 90.00005]); + expect( + results[ + `var x := 10; var y := -100.00005; var z := x + y; abs(z)` + ] + ).toEqual([90.00005, 90.00005, 90.00005, 90.00005]); await view.delete(); await table.delete(); }); - it("Declare string var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare string var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ expressions: [ `var x := 'long literal string here'; var y := 'long literal string here'; x == y ? concat('strings: ', x, ', ', y) : 'nope'`, - `var x := 'hello'; var y := upper(x); lower(y);` - ] + `var x := 'hello'; var y := upper(x); lower(y);`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 'long literal string here'; var y := 'long literal string here'; x == y ? concat('strings: ', x, ', ', y) : 'nope'`]).toEqual([ + expect( + results[ + `var x := 'long literal string here'; var y := 'long literal string here'; x == y ? concat('strings: ', x, ', ', y) : 'nope'` + ] + ).toEqual([ + "strings: long literal string here, long literal string here", "strings: long literal string here, long literal string here", "strings: long literal string here, long literal string here", "strings: long literal string here, long literal string here", - "strings: long literal string here, long literal string here" ]); - expect(results[`var x := 'hello'; var y := upper(x); lower(y);`]).toEqual(["hello", "hello", "hello", "hello"]); + expect( + results[`var x := 'hello'; var y := upper(x); lower(y);`] + ).toEqual(["hello", "hello", "hello", "hello"]); await view.delete(); await table.delete(); }); - it("Declare date var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare date var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := today(); var y := today(); x == y ? 1 : 0;`] + expressions: [ + `var x := today(); var y := today(); x == y ? 1 : 0;`, + ], }); const results = await view.to_columns(); - expect(results[`var x := today(); var y := today(); x == y ? 1 : 0;`]).toEqual([1, 1, 1, 1]); + expect( + results[`var x := today(); var y := today(); x == y ? 1 : 0;`] + ).toEqual([1, 1, 1, 1]); await view.delete(); await table.delete(); }); - it("Declare datetime var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare datetime var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const now = new Date().getTime(); const view = await table.view({ - expressions: ["now()"] + expressions: ["now()"], }); const results = await view.to_columns(); @@ -496,145 +745,218 @@ module.exports = perspective => { await table.delete(); }); - it("Declare boolean var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare boolean var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := true; var y := false; x and y ? 1 : 0`] + expressions: [`var x := true; var y := false; x and y ? 1 : 0`], }); const results = await view.to_columns(); - expect(results[`var x := true; var y := false; x and y ? 1 : 0`]).toEqual([0, 0, 0, 0]); + expect( + results[`var x := true; var y := false; x and y ? 1 : 0`] + ).toEqual([0, 0, 0, 0]); await view.delete(); await table.delete(); }); - it("Declare column as var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Declare column as var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := "w"; var y := 1.5; x > 2.5 ? x : y`] + expressions: [`var x := "w"; var y := 1.5; x > 2.5 ? x : y`], }); const results = await view.to_columns(); - expect(results[`var x := "w"; var y := 1.5; x > 2.5 ? x : y`]).toEqual([1.5, 1.5, 3.5, 4.5]); + expect( + results[`var x := "w"; var y := 1.5; x > 2.5 ? x : y`] + ).toEqual([1.5, 1.5, 3.5, 4.5]); await view.delete(); await table.delete(); }); - it("Assign one type to var of another", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Assign one type to var of another", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 'w'; var y := 1.5; x := y; x`, `var x := 'w'; var y := 1.5; y := x; y`] + expressions: [ + `var x := 'w'; var y := 1.5; x := y; x`, + `var x := 'w'; var y := 1.5; y := x; y`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 'w'; var y := 1.5; x := y; x`]).toEqual([1.5, 1.5, 1.5, 1.5]); - expect(results[`var x := 'w'; var y := 1.5; y := x; y`]).toEqual(["w", "w", "w", "w"]); + expect(results[`var x := 'w'; var y := 1.5; x := y; x`]).toEqual([ + 1.5, 1.5, 1.5, 1.5, + ]); + expect(results[`var x := 'w'; var y := 1.5; y := x; y`]).toEqual([ + "w", + "w", + "w", + "w", + ]); await view.delete(); await table.delete(); }); - it("Assign to declared numeric var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Assign to declared numeric var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 100; var y := 200.5; x := y; x`, `var x := 10; var y := pow(x, 3); y := x; y`] + expressions: [ + `var x := 100; var y := 200.5; x := y; x`, + `var x := 10; var y := pow(x, 3); y := x; y`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 100; var y := 200.5; x := y; x`]).toEqual([200.5, 200.5, 200.5, 200.5]); - expect(results[`var x := 10; var y := pow(x, 3); y := x; y`]).toEqual([10, 10, 10, 10]); + expect(results[`var x := 100; var y := 200.5; x := y; x`]).toEqual([ + 200.5, 200.5, 200.5, 200.5, + ]); + expect( + results[`var x := 10; var y := pow(x, 3); y := x; y`] + ).toEqual([10, 10, 10, 10]); await view.delete(); await table.delete(); }); - it("Assign to declared string var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Assign to declared string var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: [`var x := 'long literal string here'; var y := lower('ANOTHER VERY LONG STRING HERE'); x := y`] + expressions: [ + `var x := 'long literal string here'; var y := lower('ANOTHER VERY LONG STRING HERE'); x := y`, + ], }); const results = await view.to_columns(); - expect(results[`var x := 'long literal string here'; var y := lower('ANOTHER VERY LONG STRING HERE'); x := y`]).toEqual([ + expect( + results[ + `var x := 'long literal string here'; var y := lower('ANOTHER VERY LONG STRING HERE'); x := y` + ] + ).toEqual([ + "another very long string here", "another very long string here", "another very long string here", "another very long string here", - "another very long string here" ]); await view.delete(); await table.delete(); }); - it("Assign to declared date var", async function() { + it("Assign to declared date var", async function () { const table = await perspective.table({ - a: "date" + a: "date", }); const view = await table.view({ - expressions: [`var x := today(); var y := today(); var z := "a"; (x > z) and (y > z) ? 1 : 0`] + expressions: [ + `var x := today(); var y := today(); var z := "a"; (x > z) and (y > z) ? 1 : 0`, + ], }); const tomorrow = new Date(); tomorrow.setDate(new Date().getDate() + 1); table.update({ - a: [new Date(2020, 3, 1), new Date(2012, 0, 30), new Date(2018, 9, 31), tomorrow] + a: [ + new Date(2020, 3, 1), + new Date(2012, 0, 30), + new Date(2018, 9, 31), + tomorrow, + ], }); const results = await view.to_columns(); - expect(results[`var x := today(); var y := today(); var z := "a"; (x > z) and (y > z) ? 1 : 0`]).toEqual([1, 1, 1, 0]); + expect( + results[ + `var x := today(); var y := today(); var z := "a"; (x > z) and (y > z) ? 1 : 0` + ] + ).toEqual([1, 1, 1, 0]); await view.delete(); await table.delete(); }); - it("Assign to declared datetime var", async function() { + it("Assign to declared datetime var", async function () { const table = await perspective.table({ - a: "datetime" + a: "datetime", }); const view = await table.view({ - expressions: [`var x := now(); var y := now(); var z := "a"; (x > z) and (y > z) ? 1 : 0`] + expressions: [ + `var x := now(); var y := now(); var z := "a"; (x > z) and (y > z) ? 1 : 0`, + ], }); const tomorrow = new Date(); tomorrow.setDate(new Date().getDate() + 1); table.update({ - a: [new Date(2020, 3, 1, 12, 30, 45, 850), new Date(2012, 0, 30, 19, 25), new Date(2018, 9, 31, 3, 13), tomorrow] + a: [ + new Date(2020, 3, 1, 12, 30, 45, 850), + new Date(2012, 0, 30, 19, 25), + new Date(2018, 9, 31, 3, 13), + tomorrow, + ], }); const results = await view.to_columns(); - expect(results[`var x := now(); var y := now(); var z := "a"; (x > z) and (y > z) ? 1 : 0`]).toEqual([1, 1, 1, 0]); + expect( + results[ + `var x := now(); var y := now(); var z := "a"; (x > z) and (y > z) ? 1 : 0` + ] + ).toEqual([1, 1, 1, 0]); await view.delete(); await table.delete(); }); - it("Assign to declared column as var", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Assign to declared column as var", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); // test type transitions as assignments happen - from str to int, // from float to str, etc. const view = await table.view({ - expressions: [`var x := "w"; var y := x; x := "y"; x;`, `var x := "z"; var y := x; x := "w"; x;`] + expressions: [ + `var x := "w"; var y := x; x := "y"; x;`, + `var x := "z"; var y := x; x := "w"; x;`, + ], }); const results = await view.to_columns(); - expect(results[`var x := "w"; var y := x; x := "y"; x;`]).toEqual(["a", "b", "c", "d"]); - expect(results[`var x := "z"; var y := x; x := "w"; x;`]).toEqual([1.5, 2.5, 3.5, 4.5]); + expect(results[`var x := "w"; var y := x; x := "y"; x;`]).toEqual([ + "a", + "b", + "c", + "d", + ]); + expect(results[`var x := "z"; var y := x; x := "w"; x;`]).toEqual([ + 1.5, 2.5, 3.5, 4.5, + ]); await view.delete(); await table.delete(); }); }); - describe("Functionality", function() { - it("Should be able to create an expression column in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + describe("Functionality", function () { + it("Should be able to create an expression column in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -642,11 +964,13 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to alias a real column in `view()`", async function() { - const table = await perspective.table(expressions_common.all_types_arrow.slice()); + it("Should be able to alias a real column in `view()`", async function () { + const table = await perspective.table( + expressions_common.all_types_arrow.slice() + ); const columns = await table.columns(); const view = await table.view({ - expressions: columns.map(col => `"${col}"`) + expressions: columns.map((col) => `"${col}"`), }); const schema = await view.schema(); @@ -662,10 +986,12 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create an expression column with scalars in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create an expression column with scalars in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["1 + 2 + 3 + 4 + 5 + 6"] + expressions: ["1 + 2 + 3 + 4 + 5 + 6"], }); const result = await view.to_columns(); expect(result["1 + 2 + 3 + 4 + 5 + 6"]).toEqual([21, 21, 21, 21]); @@ -673,11 +999,13 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create a string expression column with scalars in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create a string expression column with scalars in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ row_pivots: ["'abc'"], // checks that the strings are interned - expressions: ["'abc'"] + expressions: ["'abc'"], }); const result = await view.to_columns(); expect(result["__ROW_PATH__"]).toEqual([[], ["abc"]]); @@ -686,10 +1014,12 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create a boolean expression column with scalars in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create a boolean expression column with scalars in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ["0 and 1", "0 or 1"] + expressions: ["0 and 1", "0 or 1"], }); expect(await view.schema()).toEqual({ w: "float", @@ -697,7 +1027,7 @@ module.exports = perspective => { y: "string", z: "boolean", "0 and 1": "float", - "0 or 1": "float" + "0 or 1": "float", }); const result = await view.to_columns(); expect(result["0 and 1"]).toEqual([0, 0, 0, 0]); @@ -706,22 +1036,28 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create an expression column with scalars and columns `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create an expression column with scalars and columns `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ columns: ['("w" + "x") ^ 2'], - expressions: ['("w" + "x") ^ 2'] + expressions: ['("w" + "x") ^ 2'], }); const result = await view.to_columns(); - expect(result['("w" + "x") ^ 2']).toEqual([2.5, 4.5, 6.5, 8.5].map(x => Math.pow(x, 2))); + expect(result['("w" + "x") ^ 2']).toEqual( + [2.5, 4.5, 6.5, 8.5].map((x) => Math.pow(x, 2)) + ); view.delete(); table.delete(); }); - it("Should be able to create a boolean expression column with scalars and columns in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create a boolean expression column with scalars and columns in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"x" and 1', '"x" or 1'] + expressions: ['"x" and 1', '"x" or 1'], }); expect(await view.schema()).toEqual({ w: "float", @@ -729,7 +1065,7 @@ module.exports = perspective => { y: "string", z: "boolean", '"x" and 1': "float", - '"x" or 1': "float" + '"x" or 1': "float", }); const result = await view.to_columns(); expect(result['"x" and 1']).toEqual([1, 1, 1, 1]); @@ -738,15 +1074,15 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create an expression column in `view()` from schema, and updates propagate", async function() { + it("Should be able to create an expression column in `view()` from schema, and updates propagate", async function () { const table = await perspective.table({ w: "float", x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const view = await table.view({ - expressions: ['("w" + "x") * 10'] + expressions: ['("w" + "x") * 10'], }); const result = await view.to_columns(); @@ -760,16 +1096,20 @@ module.exports = perspective => { table.update(expressions_common.int_float_data); const new_result2 = await view.to_columns(); - expect(new_result2['("w" + "x") * 10']).toEqual([25, 45, 65, 85, 25, 45, 65, 85]); + expect(new_result2['("w" + "x") * 10']).toEqual([ + 25, 45, 65, 85, 25, 45, 65, 85, + ]); view.delete(); table.delete(); }); - it("Should be able to create an expression column using an alias.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create an expression column using an alias.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"'] + expressions: ['// new column\n"w" + "x"'], }); const result = await view.to_columns(); expect(result["new column"]).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -777,17 +1117,19 @@ module.exports = perspective => { await table.delete(); }); - it("Alias should be trimmed.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Alias should be trimmed.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"'] + expressions: ['// new column\n"w" + "x"'], }); const result = await view.to_columns(); expect(result["new column"]).toEqual([2.5, 4.5, 6.5, 8.5]); const view2 = await table.view({ - expressions: ['// new column\n"w" - "x"'] + expressions: ['// new column\n"w" - "x"'], }); const result2 = await view2.to_columns(); @@ -798,21 +1140,33 @@ module.exports = perspective => { await table.delete(); }); - it("Should be able to create an expression column using a very long alias.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create an expression column using a very long alias.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column with lots of different strings and names and lots of good characters\n"w" + "x"'] + expressions: [ + '// new column with lots of different strings and names and lots of good characters\n"w" + "x"', + ], }); const result = await view.to_columns(); - expect(result["new column with lots of different strings and names and lots of good characters"]).toEqual([2.5, 4.5, 6.5, 8.5]); + expect( + result[ + "new column with lots of different strings and names and lots of good characters" + ] + ).toEqual([2.5, 4.5, 6.5, 8.5]); await view.delete(); await table.delete(); }); - it("Comments after the alias are not picked up.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Comments after the alias are not picked up.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// alias\nvar x := "w" + "x"\n// a comment\n# another comment\n/* another comment here */\n'] + expressions: [ + '// alias\nvar x := "w" + "x"\n// a comment\n# another comment\n/* another comment here */\n', + ], }); const result = await view.to_columns(); expect(result["alias"]).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -820,16 +1174,24 @@ module.exports = perspective => { await table.delete(); }); - it("Duplicate alias within the same view should resolve to the last expression.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Duplicate alias within the same view should resolve to the last expression.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"', '// new column 1\n"w" - "x"', "// new column\n100 * 10", '// new column 1\n"w" + "x"', '// new column\n"w" - "x"'] + expressions: [ + '// new column\n"w" + "x"', + '// new column 1\n"w" - "x"', + "// new column\n100 * 10", + '// new column 1\n"w" + "x"', + '// new column\n"w" - "x"', + ], }); expect(await view.expression_schema()).toEqual({ "new column": "float", - "new column 1": "float" + "new column 1": "float", }); const result = await view.to_columns(); @@ -841,16 +1203,24 @@ module.exports = perspective => { await table.delete(); }); - it("Duplicate alias within the same view should resolve to the last expression, different types.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Duplicate alias within the same view should resolve to the last expression, different types.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"', '// new column 1\n"w" - "x"', "// new column\n100", "// new column\nupper('abc')", '// new column 1\n"w" + "x"'] + expressions: [ + '// new column\n"w" + "x"', + '// new column 1\n"w" - "x"', + "// new column\n100", + "// new column\nupper('abc')", + '// new column 1\n"w" + "x"', + ], }); expect(await view.expression_schema()).toEqual({ "new column": "string", - "new column 1": "float" + "new column 1": "float", }); const result = await view.to_columns(); @@ -862,20 +1232,24 @@ module.exports = perspective => { await table.delete(); }); - it("Duplicate alias across new views will not overwrite results in old view.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Duplicate alias across new views will not overwrite results in old view.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"'] + expressions: ['// new column\n"w" + "x"'], }); let result = await view.to_columns(); expect(result["new column"]).toEqual([2.5, 4.5, 6.5, 8.5]); const view2 = await table.view({ - expressions: ["// new column\n100 * 10"] + expressions: ["// new column\n100 * 10"], }); - expect(await view.expression_schema()).toEqual(await view2.expression_schema()); + expect(await view.expression_schema()).toEqual( + await view2.expression_schema() + ); result = await view.to_columns(); const result2 = await view2.to_columns(); @@ -884,11 +1258,13 @@ module.exports = perspective => { const view3 = await table.view({ expressions: ["// new column\n100 * 100"], - row_pivots: ["y"] + row_pivots: ["y"], }); const result3 = await view3.to_columns(); - expect(result3["new column"]).toEqual([40000, 10000, 10000, 10000, 10000]); + expect(result3["new column"]).toEqual([ + 40000, 10000, 10000, 10000, 10000, + ]); await view3.delete(); await view2.delete(); @@ -896,31 +1272,37 @@ module.exports = perspective => { await table.delete(); }); - it("Should not be able to overwrite table column with an expression", async function(done) { + it("Should not be able to overwrite table column with an expression", async function (done) { expect.assertions(1); - const table = await perspective.table(expressions_common.int_float_data); + const table = await perspective.table( + expressions_common.int_float_data + ); table .view({ - expressions: ["// w\nupper('abc')"] + expressions: ["// w\nupper('abc')"], }) - .catch(e => { - expect(e.message).toMatch(`Abort(): View creation failed: cannot create expression column 'w' that overwrites a column that already exists.\n`); + .catch((e) => { + expect(e.message).toMatch( + `Abort(): View creation failed: cannot create expression column 'w' that overwrites a column that already exists.\n` + ); table.delete(); done(); }); }); - it("Should be able to overwrite expression column with one that returns a different type", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to overwrite expression column with one that returns a different type", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['// new column\n"w" + "x"'] + expressions: ['// new column\n"w" + "x"'], }); let result = await view.to_columns(); expect(result["new column"]).toEqual([2.5, 4.5, 6.5, 8.5]); const view2 = await table.view({ - expressions: ["// new column\n upper('abc')"] + expressions: ["// new column\n upper('abc')"], }); const result2 = await view2.to_columns(); @@ -931,57 +1313,101 @@ module.exports = perspective => { table.delete(); }); - it("A new view should not reference expression columns it did not create.", async function(done) { + it("A new view should not reference expression columns it did not create.", async function (done) { expect.assertions(2); - const table = await perspective.table(expressions_common.int_float_data); + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); table .view({ - columns: ['"w" + "x"', "x"] + columns: ['"w" + "x"', "x"], }) - .catch(e => { - expect(e.message).toMatch(`Abort(): Invalid column '"w" + "x"' found in View columns.\n`); + .catch((e) => { + expect(e.message).toMatch( + `Abort(): Invalid column '"w" + "x"' found in View columns.\n` + ); view.delete(); table.delete(); done(); }); }); - it("A view should be able to shadow real columns with an expression column", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("A view should be able to shadow real columns with an expression column", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w"', '"x"', '"y"', '"z"'] + expressions: ['"w"', '"x"', '"y"', '"z"'], }); expect(await view.expression_schema()).toEqual({ '"w"': "float", '"x"': "integer", '"y"': "string", - '"z"': "boolean" + '"z"': "boolean", }); expect(await view.to_json()).toEqual([ - {w: 1.5, x: 1, y: "a", z: true, '"w"': 1.5, '"x"': 1, '"y"': "a", '"z"': true}, - {w: 2.5, x: 2, y: "b", z: false, '"w"': 2.5, '"x"': 2, '"y"': "b", '"z"': false}, - {w: 3.5, x: 3, y: "c", z: true, '"w"': 3.5, '"x"': 3, '"y"': "c", '"z"': true}, - {w: 4.5, x: 4, y: "d", z: false, '"w"': 4.5, '"x"': 4, '"y"': "d", '"z"': false} + { + w: 1.5, + x: 1, + y: "a", + z: true, + '"w"': 1.5, + '"x"': 1, + '"y"': "a", + '"z"': true, + }, + { + w: 2.5, + x: 2, + y: "b", + z: false, + '"w"': 2.5, + '"x"': 2, + '"y"': "b", + '"z"': false, + }, + { + w: 3.5, + x: 3, + y: "c", + z: true, + '"w"': 3.5, + '"x"': 3, + '"y"': "c", + '"z"': true, + }, + { + w: 4.5, + x: 4, + y: "d", + z: false, + '"w"': 4.5, + '"x"': 4, + '"y"': "d", + '"z"': false, + }, ]); view.delete(); table.delete(); }); - it("Should be able to create multiple expression columns in `view()`", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create multiple expression columns in `view()`", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"', 'upper("y")'] + expressions: ['"w" + "x"', 'upper("y")'], }); expect(await view.expression_schema()).toEqual({ '"w" + "x"': "float", - 'upper("y")': "string" + 'upper("y")': "string", }); const result = await view.to_columns(); @@ -991,13 +1417,15 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create multiple expression columns with unique names in multiple `view()`s", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create multiple expression columns with unique names in multiple `view()`s", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"', '"w" * "x"'] + expressions: ['"w" + "x"', '"w" - "x"', '"w" * "x"'], }); const view2 = await table.view({ - expressions: ['"w" / "x"', '"x" * "w"'] + expressions: ['"w" / "x"', '"x" * "w"'], }); const schema = await view.schema(); @@ -1010,7 +1438,7 @@ module.exports = perspective => { z: "boolean", '"w" + "x"': "float", '"w" - "x"': "float", - '"w" * "x"': "float" + '"w" * "x"': "float", }); expect(schema2).toEqual({ @@ -1019,7 +1447,7 @@ module.exports = perspective => { y: "string", z: "boolean", '"w" / "x"': "float", - '"x" * "w"': "float" + '"x" * "w"': "float", }); const result = await view.to_columns(); @@ -1032,7 +1460,7 @@ module.exports = perspective => { z: [true, false, true, false], '"w" + "x"': [2.5, 4.5, 6.5, 8.5], '"w" - "x"': [0.5, 0.5, 0.5, 0.5], - '"w" * "x"': [1.5, 5, 10.5, 18] + '"w" * "x"': [1.5, 5, 10.5, 18], }); expect(result2).toEqual({ @@ -1041,7 +1469,7 @@ module.exports = perspective => { y: ["a", "b", "c", "d"], z: [true, false, true, false], '"w" / "x"': [1.5, 1.25, 1.1666666666666667, 1.125], - '"x" * "w"': [1.5, 5, 10.5, 18] + '"x" * "w"': [1.5, 5, 10.5, 18], }); view2.delete(); @@ -1049,13 +1477,15 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create multiple expression columns in multiple `view()`s, and arbitarily delete views.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create multiple expression columns in multiple `view()`s, and arbitarily delete views.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" - "x"'] + expressions: ['"w" - "x"'], }); const schema = await view.schema(); @@ -1065,7 +1495,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); const result = await view.to_columns(); @@ -1074,7 +1504,7 @@ module.exports = perspective => { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], z: [true, false, true, false], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5], }); view.delete(); @@ -1089,7 +1519,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" - "x"': "float" + '"w" - "x"': "float", }); const result2 = await view2.to_columns(); @@ -1099,21 +1529,23 @@ module.exports = perspective => { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], z: [true, false, true, false], - '"w" - "x"': [0.5, 0.5, 0.5, 0.5] + '"w" - "x"': [0.5, 0.5, 0.5, 0.5], }); view2.delete(); table.delete(); }); - it("Should be able to create multiple duplicate expression columns in multiple `view()`s, and delete preceding views without affecting later columns.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create multiple duplicate expression columns in multiple `view()`s, and delete preceding views without affecting later columns.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"'] + expressions: ['"w" + "x"', '"w" - "x"'], }); const schema = await view.schema(); @@ -1123,7 +1555,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); const result = await view.to_columns(); @@ -1132,7 +1564,7 @@ module.exports = perspective => { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], z: [true, false, true, false], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5], }); view.delete(); @@ -1148,7 +1580,7 @@ module.exports = perspective => { y: "string", z: "boolean", '"w" + "x"': "float", - '"w" - "x"': "float" + '"w" - "x"': "float", }); const result2 = await view2.to_columns(); @@ -1159,18 +1591,20 @@ module.exports = perspective => { y: ["a", "b", "c", "d"], z: [true, false, true, false], '"w" + "x"': [2.5, 4.5, 6.5, 8.5], - '"w" - "x"': [0.5, 0.5, 0.5, 0.5] + '"w" - "x"': [0.5, 0.5, 0.5, 0.5], }); view2.delete(); table.delete(); }); - it("Multiple views inheriting the same expression columns with the same names should not conflict", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Multiple views inheriting the same expression columns with the same names should not conflict", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); expect(await view.schema()).toEqual({ @@ -1178,11 +1612,11 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); const view2 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"'] + expressions: ['"w" + "x"', '"w" - "x"'], }); expect(await view2.schema()).toEqual({ @@ -1191,11 +1625,11 @@ module.exports = perspective => { y: "string", z: "boolean", '"w" + "x"': "float", - '"w" - "x"': "float" + '"w" - "x"': "float", }); const view3 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"', '("w" - "x") + "x"'] + expressions: ['"w" + "x"', '"w" - "x"', '("w" - "x") + "x"'], }); expect(await view3.schema()).toEqual({ @@ -1205,7 +1639,7 @@ module.exports = perspective => { z: "boolean", '"w" + "x"': "float", '"w" - "x"': "float", - '("w" - "x") + "x"': "float" + '("w" - "x") + "x"': "float", }); const result = await view3.to_columns(); @@ -1217,10 +1651,12 @@ module.exports = perspective => { table.delete(); }); - it("A view should be able to create an expression column with the same name as another deleted view's expression columns.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("A view should be able to create an expression column with the same name as another deleted view's expression columns.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); @@ -1228,7 +1664,7 @@ module.exports = perspective => { view.delete(); const view2 = await table.view({ - expressions: ['"w" - "x"'] + expressions: ['"w" - "x"'], }); const result2 = await view2.to_columns(); @@ -1238,10 +1674,12 @@ module.exports = perspective => { table.delete(); }); - it("A view without expression columns should not serialize expression columns from other views.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("A view without expression columns should not serialize expression columns from other views.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); @@ -1257,12 +1695,23 @@ module.exports = perspective => { table.delete(); }); - it("When expression columns are repeated between views, column indices should grow linearly.", async function() { - let expressions = ['"w" + "x"', '"w" - "x"', '"w" * "x"', '"w" / "x"']; - const table = await perspective.table(expressions_common.int_float_data); + it("When expression columns are repeated between views, column indices should grow linearly.", async function () { + let expressions = [ + '"w" + "x"', + '"w" - "x"', + '"w" * "x"', + '"w" / "x"', + ]; + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({expressions: [expressions[0]]}); - const view2 = await table.view({expressions: [expressions[0], expressions[1]]}); - const view3 = await table.view({expressions: [expressions[0], expressions[1], expressions[2]]}); + const view2 = await table.view({ + expressions: [expressions[0], expressions[1]], + }); + const view3 = await table.view({ + expressions: [expressions[0], expressions[1], expressions[2]], + }); const view4 = await table.view({expressions: expressions}); const schema = await view.schema(); @@ -1275,7 +1724,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); expect(schema2).toEqual({ @@ -1284,7 +1733,7 @@ module.exports = perspective => { y: "string", z: "boolean", '"w" + "x"': "float", - '"w" - "x"': "float" + '"w" - "x"': "float", }); expect(schema3).toEqual({ @@ -1294,7 +1743,7 @@ module.exports = perspective => { z: "boolean", '"w" + "x"': "float", '"w" - "x"': "float", - '"w" * "x"': "float" + '"w" * "x"': "float", }); expect(schema4).toEqual({ @@ -1305,7 +1754,7 @@ module.exports = perspective => { '"w" + "x"': "float", '"w" - "x"': "float", '"w" * "x"': "float", - '"w" / "x"': "float" + '"w" / "x"': "float", }); view4.delete(); @@ -1315,13 +1764,15 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to create duplicate expressions in multiple views, and updates should propagate to both", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to create duplicate expressions in multiple views, and updates should propagate to both", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const schema = await view.schema(); @@ -1332,7 +1783,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); expect(schema2).toEqual({ @@ -1340,7 +1791,7 @@ module.exports = perspective => { x: "integer", y: "string", z: "boolean", - '"w" + "x"': "float" + '"w" + "x"': "float", }); const result = await view.to_columns(); @@ -1351,7 +1802,7 @@ module.exports = perspective => { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], z: [true, false, true, false], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5], }); expect(result2).toEqual({ @@ -1359,12 +1810,12 @@ module.exports = perspective => { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], z: [true, false, true, false], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5], }); table.update({ w: [5, 6, 7, 8], - x: [1, 2, 3, 4] + x: [1, 2, 3, 4], }); expect(await view.to_columns()).toEqual({ @@ -1372,7 +1823,7 @@ module.exports = perspective => { x: [1, 2, 3, 4, 1, 2, 3, 4], y: ["a", "b", "c", "d", null, null, null, null], z: [true, false, true, false, null, null, null, null], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5, 6, 8, 10, 12] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5, 6, 8, 10, 12], }); expect(await view2.to_columns()).toEqual({ @@ -1380,7 +1831,7 @@ module.exports = perspective => { x: [1, 2, 3, 4, 1, 2, 3, 4], y: ["a", "b", "c", "d", null, null, null, null], z: [true, false, true, false, null, null, null, null], - '"w" + "x"': [2.5, 4.5, 6.5, 8.5, 6, 8, 10, 12] + '"w" + "x"': [2.5, 4.5, 6.5, 8.5, 6, 8, 10, 12], }); view2.delete(); @@ -1388,31 +1839,37 @@ module.exports = perspective => { table.delete(); }); - it("A new view should not inherit expression columns if not created.", async function() { + it("A new view should not inherit expression columns if not created.", async function () { expect.assertions(2); - const table = await perspective.table(expressions_common.int_float_data); + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); try { await table.view({ - columns: ['"w" + "x"', "x"] + columns: ['"w" + "x"', "x"], }); } catch (e) { - expect(e.message).toEqual(`Abort(): Invalid column '"w" + "x"' found in View columns.\n`); + expect(e.message).toEqual( + `Abort(): Invalid column '"w" + "x"' found in View columns.\n` + ); } view.delete(); table.delete(); }); - it("The view's underlying table should not have a mutated schema.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("The view's underlying table should not have a mutated schema.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -1420,17 +1877,19 @@ module.exports = perspective => { w: "float", x: "integer", y: "string", - z: "boolean" + z: "boolean", }); view.delete(); table.delete(); }); - it("Should be able to show an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to show an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ columns: ['"w" + "x"'], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -1438,17 +1897,19 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to hide an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to hide an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ columns: ["x"], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); expect(await view.schema()).toEqual({ - x: "integer" + x: "integer", }); expect(await view.expression_schema()).toEqual({ - '"w" + "x"': "float" + '"w" + "x"': "float", }); const result = await view.to_columns(); expect(result['"w" + "x"']).toEqual(undefined); @@ -1457,11 +1918,13 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to row pivot on a non-expression column and get correct results for the expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to row pivot on a non-expression column and get correct results for the expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ row_pivots: ["w"], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1470,17 +1933,19 @@ module.exports = perspective => { w: [12, 1.5, 2.5, 3.5, 4.5], x: [10, 1, 2, 3, 4], y: [4, 1, 1, 1, 1], - z: [4, 1, 1, 1, 1] + z: [4, 1, 1, 1, 1], }); view.delete(); table.delete(); }); - it("Should be able to row pivot on an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to row pivot on an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ row_pivots: ['"w" + "x"'], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1489,27 +1954,40 @@ module.exports = perspective => { w: [12, 1.5, 2.5, 3.5, 4.5], x: [10, 1, 2, 3, 4], y: [4, 1, 1, 1, 1], - z: [4, 1, 1, 1, 1] + z: [4, 1, 1, 1, 1], }); view.delete(); table.delete(); }); - it("Row-pivoted expression columns return correct column_paths()", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Row-pivoted expression columns return correct column_paths()", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); // default order let view = await table.view({ row_pivots: ["y"], - expressions: ['// column\n"w" + "x"'] + expressions: ['// column\n"w" + "x"'], }); let paths = await view.column_paths(); - expect(paths).toEqual(["__ROW_PATH__", "w", "x", "y", "z", "column"]); + expect(paths).toEqual([ + "__ROW_PATH__", + "w", + "x", + "y", + "z", + "column", + ]); await view.delete(); - const expected_paths = [["x", "column"], ["column"], ["x", "column", "y"]]; + const expected_paths = [ + ["x", "column"], + ["column"], + ["x", "column", "y"], + ]; for (const expected of expected_paths) { const output = expected.slice(); @@ -1517,7 +1995,7 @@ module.exports = perspective => { view = await table.view({ row_pivots: ["y"], expressions: ['// column\n"w" + "x"'], - columns: expected + columns: expected, }); paths = await view.column_paths(); expect(paths).toEqual(output); @@ -1530,7 +2008,7 @@ module.exports = perspective => { view = await table.view({ row_pivots: ["column"], expressions: ['// column\n"w" + "x"'], - columns: expected + columns: expected, }); paths = await view.column_paths(); expect(paths).toEqual(output); @@ -1540,16 +2018,18 @@ module.exports = perspective => { table.delete(); }); - it("Row-pivoted numeric expression columns return correct column_paths()", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Row-pivoted numeric expression columns return correct column_paths()", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const config = { row_pivots: ["y"], expressions: ["1234"], aggregates: { x: "sum", y: "count", - "1234": "sum" - } + 1234: "sum", + }, }; // default order let view = await table.view(config); @@ -1559,14 +2039,18 @@ module.exports = perspective => { await view.delete(); - const expected_paths = [["x", "1234"], ["1234"], ["x", "1234", "y"]]; + const expected_paths = [ + ["x", "1234"], + ["1234"], + ["x", "1234", "y"], + ]; for (const expected of expected_paths) { const output = expected.slice(); output.unshift("__ROW_PATH__"); view = await table.view({ ...config, - columns: expected + columns: expected, }); paths = await view.column_paths(); expect(paths).toEqual(output); @@ -1578,7 +2062,7 @@ module.exports = perspective => { output.unshift("__ROW_PATH__"); view = await table.view({ ...config, - columns: expected + columns: expected, }); paths = await view.column_paths(); expect(paths).toEqual(output); @@ -1588,11 +2072,13 @@ module.exports = perspective => { table.delete(); }); - it("Should be able to column pivot on an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to column pivot on an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ column_pivots: ['"w" + "x"'], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1615,18 +2101,20 @@ module.exports = perspective => { "8.5|x": [null, null, null, 4], "8.5|y": [null, null, null, "d"], "8.5|z": [null, null, null, false], - '8.5|"w" + "x"': [null, null, null, 8.5] + '8.5|"w" + "x"': [null, null, null, 8.5], }); view.delete(); table.delete(); }); - it("Should be able to row + column pivot on an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to row + column pivot on an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ row_pivots: ['"w" + "x"'], column_pivots: ['"w" + "x"'], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1650,25 +2138,25 @@ module.exports = perspective => { "8.5|x": [4, null, null, null, 4], "8.5|y": [1, null, null, null, 1], "8.5|z": [1, null, null, null, 1], - '8.5|"w" + "x"': [8.5, null, null, null, 8.5] + '8.5|"w" + "x"': [8.5, null, null, null, 8.5], }); view.delete(); table.delete(); }); - it("Should be able to aggregate a numeric expression column.", async function() { + it("Should be able to aggregate a numeric expression column.", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], y: [100, 200, 300, 400], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view({ row_pivots: ['"x" + "z"'], aggregates: { '"x" + "z"': "median", - x: "median" + x: "median", }, - expressions: ['"x" + "z"'] + expressions: ['"x" + "z"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1676,48 +2164,48 @@ module.exports = perspective => { '"x" + "z"': [6.5, 2.5, 4.5, 6.5, 8.5], x: [3, 1, 2, 3, 4], y: [1000, 100, 200, 300, 400], - z: [12, 1.5, 2.5, 3.5, 4.5] + z: [12, 1.5, 2.5, 3.5, 4.5], }); view.delete(); table.delete(); }); - it("Should be able to weighted mean aggregate a numeric expression column.", async function() { + it("Should be able to weighted mean aggregate a numeric expression column.", async function () { const table = await perspective.table({ x: [2, 2, 4, 4], y: [100, 200, 300, 400], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view({ row_pivots: ["y"], columns: ['"x" + "z"'], aggregates: { - '"x" + "z"': ["weighted mean", "y"] + '"x" + "z"': ["weighted mean", "y"], }, - expressions: ['"x" + "z"'] + expressions: ['"x" + "z"'], }); const result = await view.to_columns(); expect(result).toEqual({ __ROW_PATH__: [[], [100], [200], [300], [400]], - '"x" + "z"': [6.9, 3.5, 4.5, 7.5, 8.5] + '"x" + "z"': [6.9, 3.5, 4.5, 7.5, 8.5], }); view.delete(); table.delete(); }); - it("Should be able to aggregate a numeric expression column that aliases a real column.", async function() { + it("Should be able to aggregate a numeric expression column that aliases a real column.", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], y: [100, 200, 300, 400], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view({ row_pivots: ['"x"'], aggregates: { '"x"': "median", - x: "median" + x: "median", }, - expressions: ['"x"'] + expressions: ['"x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1725,69 +2213,73 @@ module.exports = perspective => { '"x"': [3, 1, 2, 3, 4], x: [3, 1, 2, 3, 4], y: [1000, 100, 200, 300, 400], - z: [12, 1.5, 2.5, 3.5, 4.5] + z: [12, 1.5, 2.5, 3.5, 4.5], }); view.delete(); table.delete(); }); - it("Should be able to aggregate a string expression column.", async function() { + it("Should be able to aggregate a string expression column.", async function () { const table = await perspective.table({ x: ["a", "a", "c", "a"], - y: ["w", "w", "y", "w"] + y: ["w", "w", "y", "w"], }); const view = await table.view({ row_pivots: ['upper("x")'], aggregates: { - 'upper("x")': "distinct count" + 'upper("x")': "distinct count", }, - expressions: ['upper("x")'] + expressions: ['upper("x")'], }); const result = await view.to_columns(); expect(result).toEqual({ __ROW_PATH__: [[], ["A"], ["C"]], 'upper("x")': [2, 1, 1], x: [4, 3, 1], - y: [4, 3, 1] + y: [4, 3, 1], }); view.delete(); table.delete(); }); - it("Should be able to aggregate a date expression column.", async function() { + it("Should be able to aggregate a date expression column.", async function () { const table = await perspective.table({ - x: [new Date(2019, 0, 15), new Date(2019, 0, 30), new Date(2019, 1, 15)] + x: [ + new Date(2019, 0, 15), + new Date(2019, 0, 30), + new Date(2019, 1, 15), + ], }); const view = await table.view({ row_pivots: [`bucket("x", 'M')`], aggregates: { - "bucket(\"x\", 'M')": "distinct count" + "bucket(\"x\", 'M')": "distinct count", }, - expressions: [`bucket("x", 'M')`] + expressions: [`bucket("x", 'M')`], }); const result = await view.to_columns(); expect(result).toEqual({ __ROW_PATH__: [[], [1546300800000], [1548979200000]], "bucket(\"x\", 'M')": [2, 1, 1], - x: [3, 2, 1] + x: [3, 2, 1], }); view.delete(); table.delete(); }); - it("Should be able to weighted mean on an expression column.", async function() { + it("Should be able to weighted mean on an expression column.", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], y: [100, 200, 300, 400], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view({ row_pivots: ['"x" + "z"'], aggregates: { x: ["weighted mean", '"x" + "z"'], - '"x" + "z"': ["weighted mean", "y"] + '"x" + "z"': ["weighted mean", "y"], }, - expressions: ['"x" + "z"'] + expressions: ['"x" + "z"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1795,17 +2287,19 @@ module.exports = perspective => { '"x" + "z"': [6.5, 2.5, 4.5, 6.5, 8.5], x: [2.9545454545454546, 1, 2, 3, 4], y: [1000, 100, 200, 300, 400], - z: [12, 1.5, 2.5, 3.5, 4.5] + z: [12, 1.5, 2.5, 3.5, 4.5], }); view.delete(); table.delete(); }); - it("Should be able to filter on an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to filter on an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ filter: [['"w" + "x"', ">", 6.5]], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1813,17 +2307,19 @@ module.exports = perspective => { w: [4.5], x: [4], y: ["d"], - z: [false] + z: [false], }); view.delete(); table.delete(); }); - it("Should be able to sort on an expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to sort on an expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ sort: [['"w" + "x"', "desc"]], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ @@ -1831,35 +2327,46 @@ module.exports = perspective => { w: [4.5, 3.5, 2.5, 1.5], x: [4, 3, 2, 1], y: ["d", "c", "b", "a"], - z: [false, true, false, true] + z: [false, true, false, true], }); view.delete(); table.delete(); }); - it("Should be able to sort on a hidden expression column.", async function() { - const table = await perspective.table(expressions_common.int_float_data); + it("Should be able to sort on a hidden expression column.", async function () { + const table = await perspective.table( + expressions_common.int_float_data + ); const view = await table.view({ columns: ["w"], sort: [['"w" + "x"', "desc"]], - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const result = await view.to_columns(); expect(result).toEqual({ - w: [4.5, 3.5, 2.5, 1.5] + w: [4.5, 3.5, 2.5, 1.5], }); view.delete(); table.delete(); }); - describe("Validation using table expression schema", function() { - it("Should show correct expression column types in table expression schema.", async function() { + describe("Validation using table expression schema", function () { + it("Should show correct expression column types in table expression schema.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); - const expressions = ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`]; + const expressions = [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ '"a" ^ 2': "float", @@ -1869,60 +2376,66 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "string", "bucket(\"b\", 'M')": "date", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); table.delete(); }); - it("Should skip expressions that try to overwrite table columns", async function() { + it("Should skip expressions that try to overwrite table columns", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const expressions = [ "// abc\n 123 + 345", //valid "// a\n sqrt(144)", // invalid "// b\n upper('abc')", // invalid "// c\n concat('a', 'b')", // invalid - "// d\n today()" // invalid + "// d\n today()", // invalid ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ - abc: "float" + abc: "float", }); table.delete(); }); - it("Should not skip expressions that try to overwrite expressions with different types", async function() { + it("Should not skip expressions that try to overwrite expressions with different types", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view({ - expressions: ["// abc\n 123 + 345", "//def \n lower('ABC')"] + expressions: [ + "// abc\n 123 + 345", + "//def \n lower('ABC')", + ], }); - const results = await table.validate_expressions(["// abc\n upper('abc')", "// def\n 1 + 2"]); + const results = await table.validate_expressions([ + "// abc\n upper('abc')", + "// def\n 1 + 2", + ]); expect(results.expression_schema).toEqual({ abc: "string", - def: "float" + def: "float", }); view.delete(); table.delete(); }); - it("Should skip invalid columns due to type error, with alias", async function() { + it("Should skip invalid columns due to type error, with alias", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const expressions = [ '// abc \n"a" + "d"', // valid @@ -1935,7 +2448,7 @@ module.exports = perspective => { 'upper("c")', // valid 'lower("a")', // invalid, 'min("a", "c")', // invalid, - 'max(100, "a")' // valid + 'max(100, "a")', // valid ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ @@ -1944,17 +2457,17 @@ module.exports = perspective => { "new column": "date", 'upper("c")': "string", 'max(100, "a")': "float", - "more columns": "string" + "more columns": "string", }); table.delete(); }); - it("Should skip invalid columns due to type error", async function() { + it("Should skip invalid columns due to type error", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const expressions = [ '"a" + "d"', // valid @@ -1967,7 +2480,7 @@ module.exports = perspective => { 'upper("c")', // valid 'lower("a")', // invalid, 'min("a", "c")', // invalid, - 'max(100, "a")' // valid + 'max(100, "a")', // valid ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ @@ -1976,17 +2489,17 @@ module.exports = perspective => { "bucket(\"b\", 'M')": "date", 'upper("c")': "string", 'max(100, "a")': "float", - "concat(\"c\", ' ', \"c\", 'abc')": "string" + "concat(\"c\", ' ', \"c\", 'abc')": "string", }); table.delete(); }); - it("Should skip a invalid column due to invalid column name", async function() { + it("Should skip a invalid column due to invalid column name", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const expressions = [ '"a" + "d"', // valid @@ -1995,49 +2508,58 @@ module.exports = perspective => { "bucket(\"b\", 'M')", // valid "bucket(\"basdsa\", 'Y')", // invalid 'upper("c")', // valid - 'lower("sdfadsj")' // invalid + 'lower("sdfadsj")', // invalid ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ '"a" + "d"': "float", '"c"': "string", "bucket(\"b\", 'M')": "date", - 'upper("c")': "string" + 'upper("c")': "string", }); table.delete(); }); - it("Should skip a invalid column due to parse error", async function() { + it("Should skip a invalid column due to parse error", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], c: ["a", "b", "c", "d"], - d: [1.5, 2.5, 3.5, 4.5] + d: [1.5, 2.5, 3.5, 4.5], }); const expressions = [ '"a" + "d"', // valid "{", // invalid "if (\"c\" == 'a') 123; else 0;", // valid - "if (" // invalid + "if (", // invalid ]; const results = await table.validate_expressions(expressions); expect(results.expression_schema).toEqual({ '"a" + "d"': "float", - "if (\"c\" == 'a') 123; else 0;": "float" + "if (\"c\" == 'a') 123; else 0;": "float", }); table.delete(); }); }); - describe("View expression schema", function() { - it("Column types in view schema on 0-sided view.", async function() { + describe("View expression schema", function () { + it("Column types in view schema on 0-sided view.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.schema(); expect(schema).toEqual({ @@ -2051,20 +2573,29 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "string", "bucket(\"b\", 'M')": "date", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); view.delete(); table.delete(); }); - it("Column types in view expression schema on 0-sided view.", async function() { + it("Column types in view expression schema on 0-sided view.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2075,21 +2606,30 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "string", "bucket(\"b\", 'M')": "date", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); view.delete(); table.delete(); }); - it("Should show ALL columns in view expression schema regardless of custom columns.", async function() { + it("Should show ALL columns in view expression schema regardless of custom columns.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ columns: ["a"], - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2100,21 +2640,30 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "string", "bucket(\"b\", 'M')": "date", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); view.delete(); table.delete(); }); - it("Aggregated types in view expression schema on 1-sided view.", async function() { + it("Aggregated types in view expression schema on 1-sided view.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ row_pivots: ["c"], - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2125,26 +2674,35 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "integer", "bucket(\"b\", 'M')": "integer", - "bucket(\"b\", 's')": "integer" + "bucket(\"b\", 's')": "integer", }); view.delete(); table.delete(); }); - it("Aggregated types in view expression schema on 1-sided view with custom aggregates.", async function() { + it("Aggregated types in view expression schema on 1-sided view with custom aggregates.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ row_pivots: ["c"], aggregates: { "0 and 1": "any", "0 or 1": "any", - "bucket(\"b\", 's')": "last" + "bucket(\"b\", 's')": "last", }, - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2155,22 +2713,31 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "integer", "bucket(\"b\", 'M')": "integer", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); view.delete(); table.delete(); }); - it("Aggregated types in view expression schema on 2-sided view.", async function() { + it("Aggregated types in view expression schema on 2-sided view.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ row_pivots: ["c"], column_pivots: ["a"], - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2181,17 +2748,17 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "integer", "bucket(\"b\", 'M')": "integer", - "bucket(\"b\", 's')": "integer" + "bucket(\"b\", 's')": "integer", }); view.delete(); table.delete(); }); - it("Aggregated types in view expression schema on 2-sided column-only view.", async function() { + it("Aggregated types in view expression schema on 2-sided column-only view.", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [new Date(), new Date(), new Date(), new Date()], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); const view = await table.view({ row_pivots: ["c"], @@ -2199,9 +2766,18 @@ module.exports = perspective => { aggregates: { "0 and 1": "any", "0 or 1": "any", - "bucket(\"b\", 's')": "last" + "bucket(\"b\", 's')": "last", }, - expressions: ['"a" ^ 2', "sqrt(144)", "0 and 1", "0 or 1", '-"a"', 'upper("c")', `bucket("b", 'M')`, `bucket("b", 's')`] + expressions: [ + '"a" ^ 2', + "sqrt(144)", + "0 and 1", + "0 or 1", + '-"a"', + 'upper("c")', + `bucket("b", 'M')`, + `bucket("b", 's')`, + ], }); const schema = await view.expression_schema(); expect(schema).toEqual({ @@ -2212,7 +2788,7 @@ module.exports = perspective => { "0 or 1": "float", 'upper("c")': "integer", "bucket(\"b\", 'M')": "integer", - "bucket(\"b\", 's')": "datetime" + "bucket(\"b\", 's')": "datetime", }); view.delete(); table.delete(); diff --git a/packages/perspective/test/js/expressions/invariant.js b/packages/perspective/test/js/expressions/invariant.js index e54062dea6..138b73061e 100644 --- a/packages/perspective/test/js/expressions/invariant.js +++ b/packages/perspective/test/js/expressions/invariant.js @@ -10,266 +10,358 @@ const jsc = require("jsverify"); const replicate = (n, g) => jsc.tuple(new Array(n).fill(g)); -const array_equals = function(a, b) { +const array_equals = function (a, b) { return JSON.stringify(a) === JSON.stringify(b); }; -const generator = function(length = 100, has_zero = true) { +const generator = function (length = 100, has_zero = true) { const min = has_zero ? 0 : 1; return jsc.record({ a: replicate(length, jsc.number(min, 1000000)), b: replicate(length, jsc.number(min, 1000000)), c: replicate(length, jsc.string), d: replicate(length, jsc.datetime), - e: replicate(length, jsc.bool) + e: replicate(length, jsc.bool), }); }; /** * Uses invariant testing to assert base correctness of computed columns. */ -module.exports = perspective => { - describe("Invariant testing", function() { - describe("Inverse expressions should be invariant", function() { - jsc.property("(x - y) + x == y", generator(), async data => { +module.exports = (perspective) => { + describe("Invariant testing", function () { + describe("Inverse expressions should be invariant", function () { + jsc.property("(x - y) + x == y", generator(), async (data) => { const table = await perspective.table(data); const view = await table.view({ - expressions: ['("a" - "b") + "b"'] + expressions: ['("a" - "b") + "b"'], }); const result = await view.to_columns(); - const expected = array_equals(result['("a" - "b") + "b"'], data["a"]); + const expected = array_equals( + result['("a" - "b") + "b"'], + data["a"] + ); view.delete(); table.delete(); return expected; }); - jsc.property("(x + y) - x - y == 0", generator(), async data => { + jsc.property("(x + y) - x - y == 0", generator(), async (data) => { const table = await perspective.table(data); const view = await table.view({ - expressions: ['("a" + "b") - "a" - "b"'] + expressions: ['("a" + "b") - "a" - "b"'], }); const result = await view.to_columns(); - const expected = array_equals(result['("a" + "b") - "a" - "b"'], Array(data["a"].length).fill(0)); + const expected = array_equals( + result['("a" + "b") - "a" - "b"'], + Array(data["a"].length).fill(0) + ); view.delete(); table.delete(); return expected; }); - jsc.property("(x + y) - (x + y) == 0", generator(), async data => { + jsc.property( + "(x + y) - (x + y) == 0", + generator(), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['("a" + "b") - ("a" + "b")'], + }); + const result = await view.to_columns(); + const expected = array_equals( + result['("a" + "b") - ("a" + "b")'], + Array(data["a"].length).fill(0) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property("(x - x) == 0", generator(), async (data) => { const table = await perspective.table(data); const view = await table.view({ - expressions: ['("a" + "b") - ("a" + "b")'] + expressions: ['"a" - "a"'], }); const result = await view.to_columns(); - const expected = array_equals(result['("a" + "b") - ("a" + "b")'], Array(data["a"].length).fill(0)); + const expected = array_equals( + result['"a" - "a"'], + Array(data["a"].length).fill(0) + ); view.delete(); table.delete(); return expected; }); - jsc.property("(x - x) == 0", generator(), async data => { + jsc.property("(x / x) == 1", generator(), async (data) => { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"a" - "a"'] + expressions: ['"a" / "a"'], }); const result = await view.to_columns(); - const expected = array_equals(result['"a" - "a"'], Array(data["a"].length).fill(0)); + const expected = array_equals( + result['"a" / "a"'], + Array(data["a"].length).fill(1) + ); view.delete(); table.delete(); return expected; }); - jsc.property("(x / x) == 1", generator(), async data => { + jsc.property("(x + x) - x - x == 0", generator(), async (data) => { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"a" / "a"'] + expressions: ['("a" + "a") - "a" - "a"'], }); const result = await view.to_columns(); - const expected = array_equals(result['"a" / "a"'], Array(data["a"].length).fill(1)); + const expected = array_equals( + result['("a" + "a") - "a" - "a"'], + Array(data["a"].length).fill(0) + ); view.delete(); table.delete(); return expected; }); - jsc.property("(x + x) - x - x == 0", generator(), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['("a" + "a") - "a" - "a"'] - }); - const result = await view.to_columns(); - const expected = array_equals(result['("a" + "a") - "a" - "a"'], Array(data["a"].length).fill(0)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("sqrt(x ^ 2) == x", generator(100, false), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['sqrt(pow("a", 2))'] - }); - const result = await view.to_columns(); - const expected = array_equals(result['sqrt(pow("a", 2))'], data["a"]); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("x ^ 2 == (x * x)", generator(100, false), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['pow("a", 2)', '"a" * "a"'] - }); - const result = await view.to_columns(); - const expected = array_equals(result['pow("a", 2)'], result['"a" * "a"']); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("x % x == 100", generator(100, false), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['percent_of("a", "a")'] - }); - const result = await view.to_columns(); - const expected = array_equals(result['percent_of("a", "a")'], Array(data["a"].length).fill(100)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("abs(x) ? x > 0 == x", generator(100, false), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['abs("a")'] - }); - const result = await view.to_columns(); - const expected = array_equals(result['abs("a")'], data["a"]); - view.delete(); - table.delete(); - return expected; - }); + jsc.property( + "sqrt(x ^ 2) == x", + generator(100, false), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['sqrt(pow("a", 2))'], + }); + const result = await view.to_columns(); + const expected = array_equals( + result['sqrt(pow("a", 2))'], + data["a"] + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "x ^ 2 == (x * x)", + generator(100, false), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['pow("a", 2)', '"a" * "a"'], + }); + const result = await view.to_columns(); + const expected = array_equals( + result['pow("a", 2)'], + result['"a" * "a"'] + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "x % x == 100", + generator(100, false), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['percent_of("a", "a")'], + }); + const result = await view.to_columns(); + const expected = array_equals( + result['percent_of("a", "a")'], + Array(data["a"].length).fill(100) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "abs(x) ? x > 0 == x", + generator(100, false), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['abs("a")'], + }); + const result = await view.to_columns(); + const expected = array_equals( + result['abs("a")'], + data["a"] + ); + view.delete(); + table.delete(); + return expected; + } + ); }); - describe("Comparison operations should be pure with the same inputs.", function() { - jsc.property("== should always be true with the same input column", generator(), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['"a" == "a"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"a" == "a"'], Array(data["a"].length).fill(1)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("> should always be false with the same input column", generator(), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['"a" > "a"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"a" > "a"'], Array(data["a"].length).fill(0)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("< should always be false with the same input column", generator(), async data => { - const table = await perspective.table(data); - - const view = await table.view({ - expressions: ['"a" < "a"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"a" < "a"'], Array(data["a"].length).fill(0)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("String == should always be true with the same input column", generator(), async data => { - const table = await perspective.table({ - a: "float", - b: "float", - c: "string", - d: "datetime", - e: "boolean" - }); - - table.update(data); - - const view = await table.view({ - expressions: ['"c" == "c"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"c" == "c"'], Array(data["c"].length).fill(1)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("Datetime == should always be true with the same input column", generator(), async data => { - const table = await perspective.table({ - a: "float", - b: "float", - c: "string", - d: "datetime", - e: "boolean" - }); - - table.update(data); - - const view = await table.view({ - expressions: ['"d" == "d"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"d" == "d"'], Array(data["d"].length).fill(1)); - view.delete(); - table.delete(); - return expected; - }); - - jsc.property("Date == should always be true with the same input column", generator(), async data => { - const table = await perspective.table({ - a: "float", - b: "float", - c: "string", - d: "date", - e: "boolean" - }); - - table.update(data); - - const view = await table.view({ - expressions: ['"d" == "d"'] - }); - - const result = await view.to_columns(); - const expected = array_equals(result['"d" == "d"'], Array(data["d"].length).fill(1)); - view.delete(); - table.delete(); - return expected; - }); + describe("Comparison operations should be pure with the same inputs.", function () { + jsc.property( + "== should always be true with the same input column", + generator(), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['"a" == "a"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"a" == "a"'], + Array(data["a"].length).fill(1) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "> should always be false with the same input column", + generator(), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['"a" > "a"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"a" > "a"'], + Array(data["a"].length).fill(0) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "< should always be false with the same input column", + generator(), + async (data) => { + const table = await perspective.table(data); + + const view = await table.view({ + expressions: ['"a" < "a"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"a" < "a"'], + Array(data["a"].length).fill(0) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "String == should always be true with the same input column", + generator(), + async (data) => { + const table = await perspective.table({ + a: "float", + b: "float", + c: "string", + d: "datetime", + e: "boolean", + }); + + table.update(data); + + const view = await table.view({ + expressions: ['"c" == "c"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"c" == "c"'], + Array(data["c"].length).fill(1) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "Datetime == should always be true with the same input column", + generator(), + async (data) => { + const table = await perspective.table({ + a: "float", + b: "float", + c: "string", + d: "datetime", + e: "boolean", + }); + + table.update(data); + + const view = await table.view({ + expressions: ['"d" == "d"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"d" == "d"'], + Array(data["d"].length).fill(1) + ); + view.delete(); + table.delete(); + return expected; + } + ); + + jsc.property( + "Date == should always be true with the same input column", + generator(), + async (data) => { + const table = await perspective.table({ + a: "float", + b: "float", + c: "string", + d: "date", + e: "boolean", + }); + + table.update(data); + + const view = await table.view({ + expressions: ['"d" == "d"'], + }); + + const result = await view.to_columns(); + const expected = array_equals( + result['"d" == "d"'], + Array(data["d"].length).fill(1) + ); + view.delete(); + table.delete(); + return expected; + } + ); }); }); }; diff --git a/packages/perspective/test/js/expressions/multiple_views.js b/packages/perspective/test/js/expressions/multiple_views.js index bba63c6676..df18adb8f4 100644 --- a/packages/perspective/test/js/expressions/multiple_views.js +++ b/packages/perspective/test/js/expressions/multiple_views.js @@ -14,7 +14,7 @@ const expressions_common = require("./common.js"); * existing column/view semantics (pivots, aggregates, columns, sorts, filters) * continue to be functional on expressions. */ -module.exports = perspective => { +module.exports = (perspective) => { const validate_delta = async (colname, delta, expected) => { const t = await perspective.table(delta.slice()); const v = await t.view(); @@ -30,14 +30,14 @@ module.exports = perspective => { const table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const v1 = await table.view({ - expressions: [`// column \n"x" + 10`] + expressions: [`// column \n"x" + 10`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -63,15 +63,15 @@ module.exports = perspective => { const table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper(concat("y", 'bcd'))`] + expressions: [`// column \n upper(concat("y", 'bcd'))`], }); let result = await v1.to_columns(); @@ -86,7 +86,7 @@ module.exports = perspective => { for (let i = 1; i < 10; i++) { table.update({ x: [i], - y: [`${i}`] + y: [`${i}`], }); result = await v1.to_columns(); @@ -108,17 +108,17 @@ module.exports = perspective => { const table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - filter: [["column", ">", 5]] + filter: [["column", ">", 5]], }); const v2 = await table.view({ expressions: [`// column \n upper(concat("y", 'bcd'))`], - filter: [["column", "contains", "A"]] + filter: [["column", "contains", "A"]], }); let result = await v1.to_columns(); @@ -133,7 +133,7 @@ module.exports = perspective => { for (let i = 1; i < 10; i++) { table.update({ x: [i], - y: [`A${i}`] + y: [`A${i}`], }); result = await v1.to_columns(); @@ -159,17 +159,17 @@ module.exports = perspective => { const table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const v2 = await table.view({ expressions: [`// column \n upper(concat("y", 'bcd'))`], - sort: [["column", "asc"]] + sort: [["column", "asc"]], }); let result = await v1.to_columns(); @@ -185,7 +185,7 @@ module.exports = perspective => { for (let i = 5; i < 9; i++) { table.update({ x: [i], - y: [`${i}`] + y: [`${i}`], }); result = await v1.to_columns(); @@ -209,16 +209,16 @@ module.exports = perspective => { { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -229,14 +229,20 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); result2 = await v2.to_columns(); expect(result["column"]).toEqual([null, 4, 6, 8, 20]); - expect(result2["column"]).toEqual(["DEF", "X", "Z", "Y", "ABC"]); + expect(result2["column"]).toEqual([ + "DEF", + "X", + "Z", + "Y", + "ABC", + ]); await v2.delete(); await v1.delete(); @@ -248,18 +254,18 @@ module.exports = perspective => { { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - filter: [["column", "==", 8]] + filter: [["column", "==", 8]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - filter: [["column", "==", "Z"]] + filter: [["column", "==", "Z"]], }); let result = await v1.to_columns(); @@ -270,7 +276,7 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); @@ -289,18 +295,18 @@ module.exports = perspective => { { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {index: "x"} ); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); let result = await v1.to_columns(); @@ -311,18 +317,24 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); result2 = await v2.to_columns(); expect(result["column"]).toEqual([20, 8, 6, 4, null]); - expect(result2["column"]).toEqual(["Z", "Y", "X", "DEF", "ABC"]); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "X", + "DEF", + "ABC", + ]); table.update({ x: [2, 10], - y: ["XYZ", "DEF"] + y: ["XYZ", "DEF"], }); result = await v1.to_columns(); @@ -330,7 +342,13 @@ module.exports = perspective => { expect(result["column"]).toEqual([20, 8, 6, 4, null]); expect(result2["x"]).toEqual([3, 4, 2, null, 10]); - expect(result2["column"]).toEqual(["Z", "Y", "XYZ", "DEF", "DEF"]); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "XYZ", + "DEF", + "DEF", + ]); await v2.delete(); await v1.delete(); @@ -342,16 +360,16 @@ module.exports = perspective => { { x: "integer", y: "string", - z: "boolean" + z: "boolean", }, {limit: 2} ); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -362,7 +380,7 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); @@ -382,10 +400,10 @@ module.exports = perspective => { const table = await perspective.table(expressions_common.data); const v1 = await table.view({ - expressions: [`// column \n"x" + 10`] + expressions: [`// column \n"x" + 10`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -399,8 +417,19 @@ module.exports = perspective => { result = await v1.to_columns(); result2 = await v2.to_columns(); - expect(result["column"]).toEqual([11, 12, 13, 14, 11, 12, 13, 14]); - expect(result2["column"]).toEqual(["A", "B", "C", "D", "A", "B", "C", "D"]); + expect(result["column"]).toEqual([ + 11, 12, 13, 14, 11, 12, 13, 14, + ]); + expect(result2["column"]).toEqual([ + "A", + "B", + "C", + "D", + "A", + "B", + "C", + "D", + ]); await v2.delete(); await v1.delete(); @@ -411,18 +440,23 @@ module.exports = perspective => { const table = await perspective.table(expressions_common.data); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper(concat("y", 'bcd'))`] + expressions: [`// column \n upper(concat("y", 'bcd'))`], }); let result = await v1.to_columns(); let result2 = await v2.to_columns(); expect(result["column"]).toEqual([2, 4, 6, 8]); - expect(result2["column"]).toEqual(["ABCD", "BBCD", "CBCD", "DBCD"]); + expect(result2["column"]).toEqual([ + "ABCD", + "BBCD", + "CBCD", + "DBCD", + ]); const expected = [2, 4, 6, 8]; const expected2 = ["ABCD", "BBCD", "CBCD", "DBCD"]; @@ -430,7 +464,7 @@ module.exports = perspective => { for (let i = 0; i < 10; i++) { table.update({ x: [i + 4], - y: [`${i + 4}`] + y: [`${i + 4}`], }); result = await v1.to_columns(); @@ -453,12 +487,12 @@ module.exports = perspective => { const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - filter: [["column", ">", 5]] + filter: [["column", ">", 5]], }); const v2 = await table.view({ expressions: [`// column \n upper(concat("y", 'bcd'))`], - filter: [["column", "contains", "A"]] + filter: [["column", "contains", "A"]], }); let result = await v1.to_columns(); @@ -472,7 +506,7 @@ module.exports = perspective => { for (let i = 0; i < 10; i++) { table.update({ x: [i + 4], - y: [`${i + 4}`] + y: [`${i + 4}`], }); result = await v1.to_columns(); @@ -494,12 +528,12 @@ module.exports = perspective => { const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const v2 = await table.view({ expressions: [`// column \n upper(concat("y", 'bcd'))`], - sort: [["column", "asc"]] + sort: [["column", "asc"]], }); let result = await v1.to_columns(); @@ -515,7 +549,7 @@ module.exports = perspective => { for (let i = 5; i < 9; i++) { table.update({ x: [i], - y: [`${i}`] + y: [`${i}`], }); result = await v1.to_columns(); @@ -535,13 +569,15 @@ module.exports = perspective => { }); it("Partial update", async () => { - const table = await perspective.table(expressions_common.data, {index: "x"}); + const table = await perspective.table(expressions_common.data, { + index: "x", + }); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -552,14 +588,21 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); result2 = await v2.to_columns(); expect(result["column"]).toEqual([null, 2, 4, 6, 8, 20]); - expect(result2["column"]).toEqual(["DEF", "A", "X", "Z", "Y", "ABC"]); + expect(result2["column"]).toEqual([ + "DEF", + "A", + "X", + "Z", + "Y", + "ABC", + ]); await v2.delete(); await v1.delete(); @@ -567,15 +610,17 @@ module.exports = perspective => { }); it("Partial update, filtered", async () => { - const table = await perspective.table(expressions_common.data, {index: "x"}); + const table = await perspective.table(expressions_common.data, { + index: "x", + }); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - filter: [["column", "==", 8]] + filter: [["column", "==", 8]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - filter: [["column", "==", "B"]] + filter: [["column", "==", "B"]], }); let result = await v1.to_columns(); @@ -586,7 +631,7 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); @@ -603,15 +648,17 @@ module.exports = perspective => { }); it("Partial update, sorted", async () => { - const table = await perspective.table(expressions_common.data, {index: "x"}); + const table = await perspective.table(expressions_common.data, { + index: "x", + }); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); let result = await v1.to_columns(); @@ -622,18 +669,25 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); result2 = await v2.to_columns(); expect(result["column"]).toEqual([20, 8, 6, 4, 2, null]); - expect(result2["column"]).toEqual(["Z", "Y", "X", "DEF", "ABC", "A"]); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "X", + "DEF", + "ABC", + "A", + ]); table.update({ x: [2, 10], - y: ["XYZ", "DEF"] + y: ["XYZ", "DEF"], }); result = await v1.to_columns(); @@ -641,7 +695,14 @@ module.exports = perspective => { expect(result["column"]).toEqual([20, 8, 6, 4, 2, null]); expect(result2["x"]).toEqual([3, 4, 2, null, 10, 1]); - expect(result2["column"]).toEqual(["Z", "Y", "XYZ", "DEF", "DEF", "A"]); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "XYZ", + "DEF", + "DEF", + "A", + ]); await v2.delete(); await v1.delete(); @@ -649,13 +710,15 @@ module.exports = perspective => { }); it("Limit", async () => { - const table = await perspective.table(expressions_common.data, {limit: 2}); + const table = await perspective.table(expressions_common.data, { + limit: 2, + }); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -666,7 +729,7 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); result = await v1.to_columns(); @@ -682,17 +745,17 @@ module.exports = perspective => { }); describe("Deltas", () => { - it("Appends delta", async done => { + it("Appends delta", async (done) => { expect.assertions(6); const table = await perspective.table(expressions_common.data); const v1 = await table.view({ - expressions: [`// column \n"x" + 10`] + expressions: [`// column \n"x" + 10`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); const result = await v1.to_columns(); @@ -702,19 +765,39 @@ module.exports = perspective => { expect(result2["column"]).toEqual(["A", "B", "C", "D"]); v1.on_update( - async updated => { - await validate_delta("column", updated.delta, [11, 12, 13, 14]); + async (updated) => { + await validate_delta( + "column", + updated.delta, + [11, 12, 13, 14] + ); const result = await v1.to_columns(); - expect(result["column"]).toEqual([11, 12, 13, 14, 11, 12, 13, 14]); + expect(result["column"]).toEqual([ + 11, 12, 13, 14, 11, 12, 13, 14, + ]); }, {mode: "row"} ); v2.on_update( - async updated => { - await validate_delta("column", updated.delta, ["A", "B", "C", "D"]); + async (updated) => { + await validate_delta("column", updated.delta, [ + "A", + "B", + "C", + "D", + ]); const result2 = await v2.to_columns(); - expect(result2["column"]).toEqual(["A", "B", "C", "D", "A", "B", "C", "D"]); + expect(result2["column"]).toEqual([ + "A", + "B", + "C", + "D", + "A", + "B", + "C", + "D", + ]); await v2.delete(); await v1.delete(); await table.delete(); @@ -726,17 +809,19 @@ module.exports = perspective => { table.update(expressions_common.data); }); - it("Partial update delta", async done => { + it("Partial update delta", async (done) => { expect.assertions(6); - const table = await perspective.table(expressions_common.data, {index: "x"}); + const table = await perspective.table(expressions_common.data, { + index: "x", + }); const v1 = await table.view({ - expressions: [`// column \n"x" * 2`] + expressions: [`// column \n"x" * 2`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); const result = await v1.to_columns(); @@ -746,19 +831,45 @@ module.exports = perspective => { expect(result2["column"]).toEqual(["A", "B", "C", "D"]); v1.on_update( - async updated => { - await validate_delta("column", updated.delta, [null, 4, 6, 8, 20]); + async (updated) => { + await validate_delta("column", updated.delta, [ + null, + 4, + 6, + 8, + 20, + ]); const result = await v1.to_columns(); - expect(result["column"]).toEqual([null, 2, 4, 6, 8, 20]); + expect(result["column"]).toEqual([ + null, + 2, + 4, + 6, + 8, + 20, + ]); }, {mode: "row"} ); v2.on_update( - async updated => { - await validate_delta("column", updated.delta, ["DEF", "X", "Z", "Y", "ABC"]); + async (updated) => { + await validate_delta("column", updated.delta, [ + "DEF", + "X", + "Z", + "Y", + "ABC", + ]); const result = await v2.to_columns(); - expect(result["column"]).toEqual(["DEF", "A", "X", "Z", "Y", "ABC"]); + expect(result["column"]).toEqual([ + "DEF", + "A", + "X", + "Z", + "Y", + "ABC", + ]); await v2.delete(); await v1.delete(); await table.delete(); @@ -769,21 +880,23 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); }); - it("Partial update, sorted delta", async done => { + it("Partial update, sorted delta", async (done) => { expect.assertions(6); - const table = await perspective.table(expressions_common.data, {index: "x"}); + const table = await perspective.table(expressions_common.data, { + index: "x", + }); const v1 = await table.view({ expressions: [`// column \n"x" * 2`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - sort: [["column", "desc"]] + sort: [["column", "desc"]], }); const result = await v1.to_columns(); @@ -793,19 +906,45 @@ module.exports = perspective => { expect(result2["column"]).toEqual(["D", "C", "B", "A"]); v1.on_update( - async updated => { - await validate_delta("column", updated.delta, [20, 8, 6, 4, null]); + async (updated) => { + await validate_delta("column", updated.delta, [ + 20, + 8, + 6, + 4, + null, + ]); const result = await v1.to_columns(); - expect(result["column"]).toEqual([20, 8, 6, 4, 2, null]); + expect(result["column"]).toEqual([ + 20, + 8, + 6, + 4, + 2, + null, + ]); }, {mode: "row"} ); v2.on_update( - async updated => { - await validate_delta("column", updated.delta, ["Z", "Y", "X", "DEF", "ABC"]); + async (updated) => { + await validate_delta("column", updated.delta, [ + "Z", + "Y", + "X", + "DEF", + "ABC", + ]); const result2 = await v2.to_columns(); - expect(result2["column"]).toEqual(["Z", "Y", "X", "DEF", "ABC", "A"]); + expect(result2["column"]).toEqual([ + "Z", + "Y", + "X", + "DEF", + "ABC", + "A", + ]); await v2.delete(); await v1.delete(); await table.delete(); @@ -816,7 +955,7 @@ module.exports = perspective => { table.update({ x: [2, 4, 3, 10, null], - y: ["X", "Y", "Z", "ABC", "DEF"] + y: ["X", "Y", "Z", "ABC", "DEF"], }); }); }); @@ -828,26 +967,32 @@ module.exports = perspective => { const table = await perspective.table({ x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [now, now, now, now] + z: [now, now, now, now], }); const v1 = await table.view({ columns: ["column", "column2"], - expressions: [`// column \n"x" + 10`, `// column2 \n concat('a', 'b', 'c')`] + expressions: [ + `// column \n"x" + 10`, + `// column2 \n concat('a', 'b', 'c')`, + ], }); const v2 = await table.view({ columns: ["column2", "column"], - expressions: [`// column \n upper("y")`, `// column2 \n bucket("z", 'Y')`] + expressions: [ + `// column \n upper("y")`, + `// column2 \n bucket("z", 'Y')`, + ], }); expect(await v1.expression_schema()).toEqual({ column: "float", - column2: "string" + column2: "string", }); expect(await v2.expression_schema()).toEqual({ column: "string", - column2: "date" + column2: "date", }); let result = await v1.to_columns(); @@ -880,26 +1025,32 @@ module.exports = perspective => { const table = await perspective.table({ x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [now, now, now, now] + z: [now, now, now, now], }); const v1 = await table.view({ columns: ["column", "column2"], - expressions: [`// column \n"x" + 10`, `// column2 \n concat('a', 'b', 'c')`] + expressions: [ + `// column \n"x" + 10`, + `// column2 \n concat('a', 'b', 'c')`, + ], }); const v2 = await table.view({ columns: ["column2", "column"], - expressions: [`// column \n upper("y")`, `// column2 \n bucket("z", 'Y')`] + expressions: [ + `// column \n upper("y")`, + `// column2 \n bucket("z", 'Y')`, + ], }); expect(await v1.expression_schema()).toEqual({ column: "float", - column2: "string" + column2: "string", }); expect(await v2.expression_schema()).toEqual({ column: "string", - column2: "date" + column2: "date", }); let result = await v1.to_columns(); @@ -913,7 +1064,7 @@ module.exports = perspective => { await table.replace({ x: [100, 300], y: ["x", "y"], - z: [now, now] + z: [now, now], }); expect(await v1.num_rows()).toEqual(2); @@ -941,28 +1092,34 @@ module.exports = perspective => { { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [now, now, now, now] + z: [now, now, now, now], }, {index: "x"} ); const v1 = await table.view({ columns: ["column", "column2"], - expressions: [`// column \n"x" + 10`, `// column2 \n concat('a', 'b', 'c')`] + expressions: [ + `// column \n"x" + 10`, + `// column2 \n concat('a', 'b', 'c')`, + ], }); const v2 = await table.view({ columns: ["column2", "column"], - expressions: [`// column \n upper("y")`, `// column2 \n bucket("z", 'Y')`] + expressions: [ + `// column \n upper("y")`, + `// column2 \n bucket("z", 'Y')`, + ], }); expect(await v1.expression_schema()).toEqual({ column: "float", - column2: "string" + column2: "string", }); expect(await v2.expression_schema()).toEqual({ column: "string", - column2: "date" + column2: "date", }); let result = await v1.to_columns(); @@ -1000,26 +1157,32 @@ module.exports = perspective => { const table = await perspective.table({ x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [now, now, now, now] + z: [now, now, now, now], }); const v1 = await table.view({ columns: ["column", "column2"], - expressions: [`// column \n"x" + 10`, `// column2 \n concat('a', 'b', 'c')`] + expressions: [ + `// column \n"x" + 10`, + `// column2 \n concat('a', 'b', 'c')`, + ], }); const v2 = await table.view({ columns: ["column2", "column"], - expressions: [`// column \n upper("y")`, `// column2 \n bucket("z", 'Y')`] + expressions: [ + `// column \n upper("y")`, + `// column2 \n bucket("z", 'Y')`, + ], }); expect(await v1.expression_schema()).toEqual({ column: "float", - column2: "string" + column2: "string", }); expect(await v2.expression_schema()).toEqual({ column: "string", - column2: "date" + column2: "date", }); const result = await v1.to_columns(); @@ -1039,10 +1202,10 @@ module.exports = perspective => { const table = await perspective.table(expressions_common.data); const v1 = await table.view({ - expressions: [`// column \n"x" + 10`] + expressions: [`// column \n"x" + 10`], }); const v2 = await table.view({ - expressions: [`// column \n upper("y")`] + expressions: [`// column \n upper("y")`], }); let result = await v1.to_columns(); @@ -1077,11 +1240,11 @@ module.exports = perspective => { const v1 = await table.view({ expressions: [`// column \n"x" + 10`], - filter: [["column", "==", 12]] + filter: [["column", "==", 12]], }); const v2 = await table.view({ expressions: [`// column \n upper("y")`], - filter: [["column", "==", "D"]] + filter: [["column", "==", "D"]], }); const result = await v1.to_columns(); @@ -1101,15 +1264,15 @@ module.exports = perspective => { const v1 = await table.view({ row_pivots: ["y"], column_pivots: ["x"], - expressions: [`// column \n"x" + 10`] + expressions: [`// column \n"x" + 10`], }); const v2 = await table.view({ row_pivots: ["x"], column_pivots: ["y"], expressions: [`// column \n upper("y")`], aggregates: { - column: "last" - } + column: "last", + }, }); const result = await v1.to_columns(); @@ -1134,31 +1297,31 @@ module.exports = perspective => { const table = await perspective.table({ x: [10, 10, 20, 20], y: ["A", "B", "C", "D"], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const v1 = await table.view({ row_pivots: ["x"], expressions: [`// column \n"z" + 10`], aggregates: { - column: "avg" - } + column: "avg", + }, }); const v2 = await table.view({ row_pivots: ["x"], expressions: [`// column \n upper("y")`], aggregates: { - column: "last" - } + column: "last", + }, }); const v3 = await table.view({ row_pivots: ["x"], expressions: [`// column \n 2"z"`], aggregates: { - column: ["weighted mean", "z"] - } + column: ["weighted mean", "z"], + }, }); const result = await v1.to_columns(); @@ -1179,7 +1342,7 @@ module.exports = perspective => { const table = await perspective.table({ x: [10, 10, 20, 20], y: ["A", "B", "C", "D"], - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const v1 = await table.view({ @@ -1187,8 +1350,8 @@ module.exports = perspective => { column_pivots: ["y"], expressions: [`// column \n"z" + 10`], aggregates: { - column: "avg" - } + column: "avg", + }, }); const v2 = await table.view({ @@ -1196,8 +1359,8 @@ module.exports = perspective => { column_pivots: ["y"], expressions: [`// column \n upper("y")`], aggregates: { - column: "last" - } + column: "last", + }, }); const v3 = await table.view({ @@ -1205,8 +1368,8 @@ module.exports = perspective => { column_pivots: ["y"], expressions: [`// column \n 2"z"`], aggregates: { - column: ["weighted mean", "z"] - } + column: ["weighted mean", "z"], + }, }); const result = await v1.to_columns(); @@ -1230,7 +1393,7 @@ module.exports = perspective => { "D|x": [20, null, 20], "D|y": [1, null, 1], "D|z": [4.5, null, 4.5], - "D|column": [14.5, null, 14.5] + "D|column": [14.5, null, 14.5], }); expect(result2).toEqual({ __ROW_PATH__: [[], [10], [20]], @@ -1249,7 +1412,7 @@ module.exports = perspective => { "D|x": [20, null, 20], "D|y": [1, null, 1], "D|z": [4.5, null, 4.5], - "D|column": ["D", null, "D"] + "D|column": ["D", null, "D"], }); expect(result3).toEqual({ __ROW_PATH__: [[], [10], [20]], @@ -1268,7 +1431,7 @@ module.exports = perspective => { "D|x": [20, null, 20], "D|y": [1, null, 1], "D|z": [4.5, null, 4.5], - "D|column": [9, null, 9] + "D|column": [9, null, 9], }); await v3.delete(); diff --git a/packages/perspective/test/js/expressions/numeric.js b/packages/perspective/test/js/expressions/numeric.js index 339a0e2044..b13d2e2659 100644 --- a/packages/perspective/test/js/expressions/numeric.js +++ b/packages/perspective/test/js/expressions/numeric.js @@ -7,7 +7,18 @@ * */ const common = require("./common.js"); -const NUMERIC_TYPES = ["i8", "ui8", "i16", "ui16", "i32", "ui32", "i64", "ui64", "f32", "f64"]; +const NUMERIC_TYPES = [ + "i8", + "ui8", + "i16", + "ui16", + "i32", + "ui32", + "i64", + "ui64", + "f32", + "f64", +]; /** * Return all possible permutations of unary operators for all numeric types. @@ -73,7 +84,9 @@ function validate_unary_operations(output, expressions, operator) { const output_col = output[expr]; const input = expr.substr(2, expr.length - 3); console.log(input); - expect(output_col).toEqual(output[input].map(v => calc_unary(operator, v))); + expect(output_col).toEqual( + output[input].map((v) => calc_unary(operator, v)) + ); } } @@ -88,22 +101,28 @@ function validate_binary_operations(output, expressions, operator) { const inputs = expr.split(` ${operator} `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(output[left].map((v, idx) => calc_binary(operator, v, output[right][idx]))); + expect(output_col).toEqual( + output[left].map((v, idx) => + calc_binary(operator, v, output[right][idx]) + ) + ); } } /** * Tests the correctness of expressions allowed by exprtk, using as many * of the expression structures as we can, such as conditionals, loops, etc. */ -module.exports = perspective => { - describe("Numeric operations", function() { - describe("All numeric types", function() { - describe("unary", function() { - it("negative", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); +module.exports = (perspective) => { + describe("Numeric operations", function () { + describe("All numeric types", function () { + describe("unary", function () { + it("negative", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_unary_operations("+"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -119,12 +138,14 @@ module.exports = perspective => { }); }); - describe("binary", function() { - it("add", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); + describe("binary", function () { + it("add", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_binary_operations("+"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -139,11 +160,13 @@ module.exports = perspective => { await table.delete(); }); - it("subtract", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); + it("subtract", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_binary_operations("-"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -158,11 +181,13 @@ module.exports = perspective => { await table.delete(); }); - it("multiply", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); + it("multiply", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_binary_operations("*"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -177,12 +202,14 @@ module.exports = perspective => { await table.delete(); }); - it("divide", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); + it("divide", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_binary_operations("/"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -197,12 +224,14 @@ module.exports = perspective => { await table.delete(); }); - it("modulus", async function() { - const table = await perspective.table(common.all_types_arrow.slice()); + it("modulus", async function () { + const table = await perspective.table( + common.all_types_arrow.slice() + ); const expressions = generate_binary_operations("%"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -218,11 +247,11 @@ module.exports = perspective => { await table.delete(); }); - it("power", async function() { + it("power", async function () { const table = await perspective.table(common.arrow.slice()); const expressions = generate_binary_operations("^"); const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -236,8 +265,10 @@ module.exports = perspective => { validate_binary_operations(result, expressions, "^"); }); - it("==", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it("==", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -246,7 +277,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -262,12 +293,18 @@ module.exports = perspective => { const inputs = expr.split(` == `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v == result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v == result[right][idx] ? 1 : 0 + ) + ); } }); - it("!=", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it("!=", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -276,7 +313,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -292,12 +329,18 @@ module.exports = perspective => { const inputs = expr.split(` != `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v != result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v != result[right][idx] ? 1 : 0 + ) + ); } }); - it(">", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it(">", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -306,7 +349,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -322,12 +365,18 @@ module.exports = perspective => { const inputs = expr.split(` > `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v > result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v > result[right][idx] ? 1 : 0 + ) + ); } }); - it("<", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it("<", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -336,7 +385,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -352,12 +401,18 @@ module.exports = perspective => { const inputs = expr.split(` < `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v < result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v < result[right][idx] ? 1 : 0 + ) + ); } }); - it(">=", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it(">=", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -366,7 +421,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -382,12 +437,18 @@ module.exports = perspective => { const inputs = expr.split(` >= `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v >= result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v >= result[right][idx] ? 1 : 0 + ) + ); } }); - it("<=", async function() { - const table = await perspective.table(common.all_types_multi_arrow.slice()); + it("<=", async function () { + const table = await perspective.table( + common.all_types_multi_arrow.slice() + ); const expressions = []; // comparisons only work when the two types are the same @@ -396,7 +457,7 @@ module.exports = perspective => { } const view = await table.view({ - expressions + expressions, }); const col_names = await view.column_paths(); @@ -412,112 +473,160 @@ module.exports = perspective => { const inputs = expr.split(` <= `); const left = inputs[0].substr(1, inputs[0].length - 2); const right = inputs[1].substr(1, inputs[1].length - 2); - expect(output_col).toEqual(result[left].map((v, idx) => (v <= result[right][idx] ? 1 : 0))); + expect(output_col).toEqual( + result[left].map((v, idx) => + v <= result[right][idx] ? 1 : 0 + ) + ); } }); - it("Numeric comparisons should be false for different types", async function() { + it("Numeric comparisons should be false for different types", async function () { const table = await perspective.table({ a: "integer", b: "float", c: "integer", - d: "float" + d: "float", }); const view = await table.view({ - expressions: ['"a" == "b"', '"a" != "b"'] + expressions: ['"a" == "b"', '"a" != "b"'], }); table.update({ a: [1, 2, 3, 4], b: [1.0, 2.0, 3.0, 4.0], c: [1, 0, 1, 0], - d: [1.0, 0, 3.0, 0.0] + d: [1.0, 0, 3.0, 0.0], }); const result = await view.to_columns(); - expect(result['"a" == "b"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result['"a" != "b"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); + expect(result['"a" == "b"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"a" != "b"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("Numeric comparisons should be true for same types", async function() { + it("Numeric comparisons should be true for same types", async function () { const table = await perspective.table({ a: "integer", b: "float", c: "integer", - d: "float" + d: "float", }); const view = await table.view({ - expressions: ['"a" == "c"', '"a" != "c"', '"b" == "d"', '"b" != "d"'] + expressions: [ + '"a" == "c"', + '"a" != "c"', + '"b" == "d"', + '"b" != "d"', + ], }); table.update({ a: [1, 2, 3, 4], b: [1.0, 2.0, 3.0, 4.0], c: [1, 0, 1, 0], - d: [1.0, 0, 3.0, 0.0] + d: [1.0, 0, 3.0, 0.0], }); const result = await view.to_columns(); - expect(result['"a" == "c"']).toEqual([true, false, false, false].map(x => (x ? 1 : 0))); - expect(result['"a" != "c"']).toEqual([false, true, true, true].map(x => (x ? 1 : 0))); - expect(result['"b" == "d"']).toEqual([true, false, true, false].map(x => (x ? 1 : 0))); - expect(result['"b" != "d"']).toEqual([false, true, false, true].map(x => (x ? 1 : 0))); + expect(result['"a" == "c"']).toEqual( + [true, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"a" != "c"']).toEqual( + [false, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"b" == "d"']).toEqual( + [true, false, true, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"b" != "d"']).toEqual( + [false, true, false, true].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("Should not divide by 0", async function() { + it("Should not divide by 0", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [0, 0, 0, 0], - c: [1.5, 2.123, 3.125, 4.123809] + c: [1.5, 2.123, 3.125, 4.123809], }); const view = await table.view({ - expressions: ['"a" / "b"', '"c" / "b"'] + expressions: ['"a" / "b"', '"c" / "b"'], }); const result = await view.to_columns(); - expect(result['"a" / "b"']).toEqual([null, null, null, null]); - expect(result['"c" / "b"']).toEqual([null, null, null, null]); + expect(result['"a" / "b"']).toEqual([ + null, + null, + null, + null, + ]); + expect(result['"c" / "b"']).toEqual([ + null, + null, + null, + null, + ]); await view.delete(); await table.delete(); }); - it("Should not modulo by 0", async function() { + it("Should not modulo by 0", async function () { const table = await perspective.table({ a: [1, 2, 3, 4], b: [0, 0, 0, 0], - c: [1.5, 2.123, 3.125, 4.123809] + c: [1.5, 2.123, 3.125, 4.123809], }); const view = await table.view({ - expressions: ['"a" % "b"', '"c" % "b"'] + expressions: ['"a" % "b"', '"c" % "b"'], }); const result = await view.to_columns(); - expect(result['"a" % "b"']).toEqual([null, null, null, null]); - expect(result['"c" % "b"']).toEqual([null, null, null, null]); + expect(result['"a" % "b"']).toEqual([ + null, + null, + null, + null, + ]); + expect(result['"c" % "b"']).toEqual([ + null, + null, + null, + null, + ]); await view.delete(); await table.delete(); }); }); }); - describe("Functions", function() { - it("min", async function() { + describe("Functions", function () { + it("min", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); - const expressions = ["min(1)", 'min("a")', 'min("a", -10, -10.001)', 'min("b")', 'min("b", 0.00000000001, -10, -100, -100.1)', 'min("a", "b")']; + const expressions = [ + "min(1)", + 'min("a")', + 'min("a", -10, -10.001)', + 'min("b")', + 'min("b", 0.00000000001, -10, -100, -100.1)', + 'min("a", "b")', + ]; const view = await table.view({ - expressions + expressions, }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const schema = await view.expression_schema(); @@ -528,18 +637,22 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result["min(1)"]).toEqual([1, 1, 1, 1]); expect(result['min("a")']).toEqual([1, 2, 3, 4]); - expect(result['min("a", -10, -10.001)']).toEqual([-10.001, -10.001, -10.001, -10.001]); + expect(result['min("a", -10, -10.001)']).toEqual([ + -10.001, -10.001, -10.001, -10.001, + ]); expect(result['min("b")']).toEqual([1.5, 2.5, 3.5, 4.5]); - expect(result['min("b", 0.00000000001, -10, -100, -100.1)']).toEqual([-100.1, -100.1, -100.1, -100.1]); + expect( + result['min("b", 0.00000000001, -10, -100, -100.1)'] + ).toEqual([-100.1, -100.1, -100.1, -100.1]); expect(result['min("a", "b")']).toEqual([1, 2, 3, 4]); await view.delete(); await table.delete(); }); - it("max", async function() { + it("max", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const expressions = [ @@ -550,16 +663,16 @@ module.exports = perspective => { 'max("a", "b")', 'max("b")', 'max("a", 10, 20, 0.1, 0.00000001)', - 'max("b", -1, -100, 100)' + 'max("b", -1, -100, 100)', ]; const view = await table.view({ - expressions + expressions, }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const schema = await view.expression_schema(); @@ -569,31 +682,40 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result["max(1)"]).toEqual([1, 1, 1, 1]); - expect(result["max(2000000000000.11, 2000000000000.1)"]).toEqual([2000000000000.11, 2000000000000.11, 2000000000000.11, 2000000000000.11]); + expect( + result["max(2000000000000.11, 2000000000000.1)"] + ).toEqual([ + 2000000000000.11, 2000000000000.11, 2000000000000.11, + 2000000000000.11, + ]); expect(result['max("a")']).toEqual([1, 2, 3, 4]); expect(result['max("a", "b")']).toEqual([1.5, 2.5, 3.5, 4.5]); expect(result['max("b")']).toEqual([1.5, 2.5, 3.5, 4.5]); - expect(result['max("a", 10, 20, 0.1, 0.00000001)']).toEqual([20, 20, 20, 20]); - expect(result['max("b", -1, -100, 100)']).toEqual([100, 100, 100, 100]); + expect(result['max("a", 10, 20, 0.1, 0.00000001)']).toEqual([ + 20, 20, 20, 20, + ]); + expect(result['max("b", -1, -100, 100)']).toEqual([ + 100, 100, 100, 100, + ]); await view.delete(); await table.delete(); }); - it("inrange", async function() { + it("inrange", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); // works for floats only const view = await table.view({ - expressions: ['inrange(9, "b", 20)'] + expressions: ['inrange(9, "b", 20)'], }); table.update({ a: [10, 15, 20, 30], - b: [10.5, 15.5, 20.5, 30.5] + b: [10.5, 15.5, 20.5, 30.5], }); const result = await view.to_columns(); @@ -602,152 +724,188 @@ module.exports = perspective => { await table.delete(); }); - it("iclamp", async function() { + it("iclamp", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['iclamp(10, "a", 20)', 'iclamp(10, "b", 20)'] + expressions: ['iclamp(10, "a", 20)', 'iclamp(10, "b", 20)'], }); table.update({ a: [10, 15, 20, 30], - b: [10.5, 15.5, 20.5, 30.5] + b: [10.5, 15.5, 20.5, 30.5], }); const result = await view.to_columns(); expect(result['iclamp(10, "a", 20)']).toEqual([10, 15, 20, 30]); - expect(result['iclamp(10, "b", 20)']).toEqual([10, 20, 20.5, 30.5]); + expect(result['iclamp(10, "b", 20)']).toEqual([ + 10, 20, 20.5, 30.5, + ]); await view.delete(); await table.delete(); }); - it("pow", async function() { + it("pow", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['pow("a", 1)', 'pow("b", 3)'] + expressions: ['pow("a", 1)', 'pow("b", 3)'], }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const result = await view.to_columns(); expect(result['pow("a", 1)']).toEqual([1, 2, 3, 4]); - expect(result['pow("b", 3)']).toEqual([1.5, 2.5, 3.5, 4.5].map(x => Math.pow(x, 3))); + expect(result['pow("b", 3)']).toEqual( + [1.5, 2.5, 3.5, 4.5].map((x) => Math.pow(x, 3)) + ); await view.delete(); await table.delete(); }); - it("logn", async function() { + it("logn", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['logn("a", 5)', 'logn("b", 3)'] + expressions: ['logn("a", 5)', 'logn("b", 3)'], }); table.update({ a: [100, 200, 300, 400], - b: [100.5, 200.5, 300.5, 400.5] + b: [100.5, 200.5, 300.5, 400.5], }); const result = await view.to_columns(); - expect(result['logn("a", 5)']).toEqual([100, 200, 300, 400].map(x => Math.log(x) / Math.log(5))); - expect(result['logn("b", 3)']).toEqual([100.5, 200.5, 300.5, 400.5].map(x => Math.log(x) / Math.log(3))); + expect(result['logn("a", 5)']).toEqual( + [100, 200, 300, 400].map((x) => Math.log(x) / Math.log(5)) + ); + expect(result['logn("b", 3)']).toEqual( + [100.5, 200.5, 300.5, 400.5].map( + (x) => Math.log(x) / Math.log(3) + ) + ); await view.delete(); await table.delete(); }); - it("root", async function() { + it("root", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['root("a", 5)', 'root("b", 3)'] + expressions: ['root("a", 5)', 'root("b", 3)'], }); table.update({ a: [100, 200, 300, 400], - b: [100.5, 200.5, 300.5, 400.5] + b: [100.5, 200.5, 300.5, 400.5], }); const result = await view.to_columns(); - expect(result['root("a", 5)'].map(x => Number(x.toFixed(5)))).toEqual([100, 200, 300, 400].map(x => Number(Math.pow(x, 1 / 5).toFixed(5)))); - expect(result['root("b", 3)'].map(x => Number(x.toFixed(5)))).toEqual([100.5, 200.5, 300.5, 400.5].map(x => Number(Math.pow(x, 1 / 3).toFixed(5)))); + expect( + result['root("a", 5)'].map((x) => Number(x.toFixed(5))) + ).toEqual( + [100, 200, 300, 400].map((x) => + Number(Math.pow(x, 1 / 5).toFixed(5)) + ) + ); + expect( + result['root("b", 3)'].map((x) => Number(x.toFixed(5))) + ).toEqual( + [100.5, 200.5, 300.5, 400.5].map((x) => + Number(Math.pow(x, 1 / 3).toFixed(5)) + ) + ); await view.delete(); await table.delete(); }); - it("avg", async function() { + it("avg", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['avg("a", 10, 20, 30, 40, "a")', 'avg("b", 3, 4, 5, "b")'] + expressions: [ + 'avg("a", 10, 20, 30, 40, "a")', + 'avg("b", 3, 4, 5, "b")', + ], }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const result = await view.to_columns(); - expect(result['avg("a", 10, 20, 30, 40, "a")']).toEqual([1, 2, 3, 4].map(x => (x + x + 10 + 20 + 30 + 40) / 6)); - expect(result['avg("b", 3, 4, 5, "b")']).toEqual([1.5, 2.5, 3.5, 4.5].map(x => (x + x + 3 + 4 + 5) / 5)); + expect(result['avg("a", 10, 20, 30, 40, "a")']).toEqual( + [1, 2, 3, 4].map((x) => (x + x + 10 + 20 + 30 + 40) / 6) + ); + expect(result['avg("b", 3, 4, 5, "b")']).toEqual( + [1.5, 2.5, 3.5, 4.5].map((x) => (x + x + 3 + 4 + 5) / 5) + ); await view.delete(); await table.delete(); }); - it("sum", async function() { + it("sum", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['sum("a", 10, 20, 30, 40, "a")', 'sum("b", 3, 4, 5, "b")'] + expressions: [ + 'sum("a", 10, 20, 30, 40, "a")', + 'sum("b", 3, 4, 5, "b")', + ], }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const result = await view.to_columns(); - expect(result['sum("a", 10, 20, 30, 40, "a")']).toEqual([1, 2, 3, 4].map(x => x + x + 10 + 20 + 30 + 40)); - expect(result['sum("b", 3, 4, 5, "b")']).toEqual([1.5, 2.5, 3.5, 4.5].map(x => x + x + 3 + 4 + 5)); + expect(result['sum("a", 10, 20, 30, 40, "a")']).toEqual( + [1, 2, 3, 4].map((x) => x + x + 10 + 20 + 30 + 40) + ); + expect(result['sum("b", 3, 4, 5, "b")']).toEqual( + [1.5, 2.5, 3.5, 4.5].map((x) => x + x + 3 + 4 + 5) + ); await view.delete(); await table.delete(); }); - it("trunc", async function() { + it("trunc", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['trunc("a")', 'trunc("b")'] + expressions: ['trunc("a")', 'trunc("b")'], }); table.update({ a: [1, 2, 3, 4], - b: [1.5, 2.5, 3.5, 4.5] + b: [1.5, 2.5, 3.5, 4.5], }); const result = await view.to_columns(); @@ -757,271 +915,438 @@ module.exports = perspective => { await table.delete(); }); - it("d2r", async function() { + it("d2r", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['deg2rad("a")', 'deg2rad("b")'] + expressions: ['deg2rad("a")', 'deg2rad("b")'], }); table.update({ a: [30, 60, 90, 120], - b: [25.5, 45.5, 88.721282, 91.12983] + b: [25.5, 45.5, 88.721282, 91.12983], }); const result = await view.to_columns(); - expect(result['deg2rad("a")']).toEqual([30, 60, 90, 120].map(x => x * (Math.PI / 180))); - expect(result['deg2rad("b")']).toEqual([25.5, 45.5, 88.721282, 91.12983].map(x => x * (Math.PI / 180))); + expect(result['deg2rad("a")']).toEqual( + [30, 60, 90, 120].map((x) => x * (Math.PI / 180)) + ); + expect(result['deg2rad("b")']).toEqual( + [25.5, 45.5, 88.721282, 91.12983].map( + (x) => x * (Math.PI / 180) + ) + ); await view.delete(); await table.delete(); }); - it("r2d", async function() { + it("r2d", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['rad2deg("a")', 'rad2deg("b")'] + expressions: ['rad2deg("a")', 'rad2deg("b")'], }); table.update({ a: [1, 2, 3, 4], - b: [25.5, 45.5, 88.721282, 91.12983].map(x => x * (Math.PI / 180)) + b: [25.5, 45.5, 88.721282, 91.12983].map( + (x) => x * (Math.PI / 180) + ), }); const result = await view.to_columns(); - expect(result['rad2deg("a")']).toEqual([1, 2, 3, 4].map(x => x * (180 / Math.PI))); - expect(result['rad2deg("b")']).toEqual([25.5, 45.5, 88.721282, 91.12983]); + expect(result['rad2deg("a")']).toEqual( + [1, 2, 3, 4].map((x) => x * (180 / Math.PI)) + ); + expect(result['rad2deg("b")']).toEqual([ + 25.5, 45.5, 88.721282, 91.12983, + ]); await view.delete(); await table.delete(); }); - it("is_null", async function() { + it("is_null", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['is_null("a")', 'is_null("b")', 'if(is_null("a")) 100; else 0;', 'if(is_null("b")) 100; else 0;'] + expressions: [ + 'is_null("a")', + 'is_null("b")', + 'if(is_null("a")) 100; else 0;', + 'if(is_null("b")) 100; else 0;', + ], }); table.update({ a: [1, null, null, 4], - b: [null, 2.5, null, 4.5] + b: [null, 2.5, null, 4.5], }); const result = await view.to_columns(); expect(result['is_null("a")']).toEqual([0, 1, 1, 0]); expect(result['is_null("b")']).toEqual([1, 0, 1, 0]); - expect(result['if(is_null("a")) 100; else 0;']).toEqual([0, 100, 100, 0]); - expect(result['if(is_null("b")) 100; else 0;']).toEqual([100, 0, 100, 0]); + expect(result['if(is_null("a")) 100; else 0;']).toEqual([ + 0, 100, 100, 0, + ]); + expect(result['if(is_null("b")) 100; else 0;']).toEqual([ + 100, 0, 100, 0, + ]); await view.delete(); await table.delete(); }); - it("is_not_null", async function() { + it("is_not_null", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['is_not_null("a")', 'is_not_null("b")', 'if(is_not_null("a")) 100; else 0;', 'if(is_not_null("b")) 100; else 0;'] + expressions: [ + 'is_not_null("a")', + 'is_not_null("b")', + 'if(is_not_null("a")) 100; else 0;', + 'if(is_not_null("b")) 100; else 0;', + ], }); table.update({ a: [1, null, null, 4], - b: [null, 2.5, null, 4.5] + b: [null, 2.5, null, 4.5], }); const result = await view.to_columns(); expect(result['is_not_null("a")']).toEqual([1, 0, 0, 1]); expect(result['is_not_null("b")']).toEqual([0, 1, 0, 1]); - expect(result['if(is_not_null("a")) 100; else 0;']).toEqual([100, 0, 0, 100]); - expect(result['if(is_not_null("b")) 100; else 0;']).toEqual([0, 100, 0, 100]); + expect(result['if(is_not_null("a")) 100; else 0;']).toEqual([ + 100, 0, 0, 100, + ]); + expect(result['if(is_not_null("b")) 100; else 0;']).toEqual([ + 0, 100, 0, 100, + ]); await view.delete(); await table.delete(); }); - it("percent_of", async function() { + it("percent_of", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['percent_of("a", 500)', 'percent_of("a", "b")', "percent_of(1, 3)"] + expressions: [ + 'percent_of("a", 500)', + 'percent_of("a", "b")', + "percent_of(1, 3)", + ], }); table.update({ a: [100, 200, 300, 400], - b: [100.5, 200.5, 300.5, 400.5] + b: [100.5, 200.5, 300.5, 400.5], }); const result = await view.to_columns(); - expect(result['percent_of("a", 500)']).toEqual([100, 200, 300, 400].map(x => (x / 500) * 100)); - expect(result['percent_of("a", "b")']).toEqual([100, 200, 300, 400].map((x, idx) => (x / result["b"][idx]) * 100)); - expect(result["percent_of(1, 3)"]).toEqual([33.33333333333333, 33.33333333333333, 33.33333333333333, 33.33333333333333]); + expect(result['percent_of("a", 500)']).toEqual( + [100, 200, 300, 400].map((x) => (x / 500) * 100) + ); + expect(result['percent_of("a", "b")']).toEqual( + [100, 200, 300, 400].map( + (x, idx) => (x / result["b"][idx]) * 100 + ) + ); + expect(result["percent_of(1, 3)"]).toEqual([ + 33.33333333333333, 33.33333333333333, 33.33333333333333, + 33.33333333333333, + ]); await view.delete(); await table.delete(); }); - it("bucket", async function() { + it("bucket", async function () { const table = await perspective.table({ a: "integer", - b: "float" + b: "float", }); const view = await table.view({ - expressions: ['bucket("a", 5)', 'bucket("b", 2.5)', `bucket("b", 10)`] + expressions: [ + 'bucket("a", 5)', + 'bucket("b", 2.5)', + `bucket("b", 10)`, + ], }); table.update({ a: [15, 15, 35, 40, 1250, 1255], - b: [2.25, 2, 3.5, 16.5, 28, 8] + b: [2.25, 2, 3.5, 16.5, 28, 8], }); const result = await view.to_columns(); - expect(result['bucket("a", 5)']).toEqual([15, 15, 35, 40, 1250, 1255]); - expect(result['bucket("b", 2.5)']).toEqual([0, 0, 2.5, 15, 27.5, 7.5]); + expect(result['bucket("a", 5)']).toEqual([ + 15, 15, 35, 40, 1250, 1255, + ]); + expect(result['bucket("b", 2.5)']).toEqual([ + 0, 0, 2.5, 15, 27.5, 7.5, + ]); expect(result['bucket("b", 10)']).toEqual([0, 0, 0, 10, 20, 0]); await view.delete(); await table.delete(); }); }); - describe("Booleans", function() { - it("AND", async function() { + describe("Booleans", function () { + it("AND", async function () { const table = await perspective.table(common.comparison_data); const view = await table.view({ - expressions: ['"u" and "u"', '"u" and "z"', '"z" and "z"', "0 and 0", "1 and 1", "1 and 100", "true and false"] + expressions: [ + '"u" and "u"', + '"u" and "z"', + '"z" and "z"', + "0 and 0", + "1 and 1", + "1 and 100", + "true and false", + ], }); const result = await view.to_columns(); - expect(result['"u" and "u"']).toEqual([false, true, false, true].map(x => (x ? 1 : 0))); - expect(result['"u" and "z"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result['"u" and "z"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["0 and 0"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["1 and 1"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["1 and 100"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["true and false"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); + expect(result['"u" and "u"']).toEqual( + [false, true, false, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"u" and "z"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"u" and "z"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["0 and 0"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["1 and 1"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["1 and 100"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["true and false"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("mand", async function() { + it("mand", async function () { const table = await perspective.table(common.comparison_data); const view = await table.view({ - expressions: ['mand("u" and "u", "u" and "z", "z" and "z")', "mand(1, 2, 3)", "mand(true, true, true, true)"] + expressions: [ + 'mand("u" and "u", "u" and "z", "z" and "z")', + "mand(1, 2, 3)", + "mand(true, true, true, true)", + ], }); const result = await view.to_columns(); - expect(result['mand("u" and "u", "u" and "z", "z" and "z")']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["mand(1, 2, 3)"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["mand(true, true, true, true)"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); + expect( + result['mand("u" and "u", "u" and "z", "z" and "z")'] + ).toEqual([false, false, false, false].map((x) => (x ? 1 : 0))); + expect(result["mand(1, 2, 3)"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["mand(true, true, true, true)"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("OR", async function() { + it("OR", async function () { const table = await perspective.table({ a: [0, 1, 0, 1], b: [0, 1, 0, 1], c: [0, 0, 0, 0], d: [1, 1, 1, 1], e: [false, false, false, false], - f: [true, true, true, true] + f: [true, true, true, true], }); const view = await table.view({ - expressions: ['"a" or "b"', '"c" or "d"', '"e" or "f"', "0 or 1", "true or false", "false or false", '// filtered\n"a" > 0.5 or "d" < 0.5'] + expressions: [ + '"a" or "b"', + '"c" or "d"', + '"e" or "f"', + "0 or 1", + "true or false", + "false or false", + '// filtered\n"a" > 0.5 or "d" < 0.5', + ], }); const result = await view.to_columns(); - expect(result['"a" or "b"']).toEqual([false, true, false, true].map(x => (x ? 1 : 0))); - expect(result['"c" or "d"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result['"e" or "f"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["0 or 1"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["true or false"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["false or false"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); + expect(result['"a" or "b"']).toEqual( + [false, true, false, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"c" or "d"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"e" or "f"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["0 or 1"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["true or false"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["false or false"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); expect(result["filtered"]).toEqual([1, 1, 1, 1]); await view.delete(); await table.delete(); }); - it("mor", async function() { + it("mor", async function () { const table = await perspective.table(common.comparison_data); const view = await table.view({ - expressions: ['mor("u" and "u", "u" and "z", "z" and "z")', "mor(0, 0, 0)", "mor(false, true, false)"] + expressions: [ + 'mor("u" and "u", "u" and "z", "z" and "z")', + "mor(0, 0, 0)", + "mor(false, true, false)", + ], }); const result = await view.to_columns(); - expect(result['mor("u" and "u", "u" and "z", "z" and "z")']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["mor(0, 0, 0)"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["mor(false, true, false)"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); + expect( + result['mor("u" and "u", "u" and "z", "z" and "z")'] + ).toEqual([true, true, true, true].map((x) => (x ? 1 : 0))); + expect(result["mor(0, 0, 0)"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["mor(false, true, false)"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("NAND", async function() { + it("NAND", async function () { const table = await perspective.table(common.comparison_data); const view = await table.view({ - expressions: ['"u" nand "u"', '"u" nand "z"', '"z" nand "z"', "0 nand 0", "1 nand 1", "1 nand 100", "true nand true"] + expressions: [ + '"u" nand "u"', + '"u" nand "z"', + '"z" nand "z"', + "0 nand 0", + "1 nand 1", + "1 nand 100", + "true nand true", + ], }); const result = await view.to_columns(); - expect(result['"u" nand "u"']).toEqual([true, false, true, false].map(x => (x ? 1 : 0))); - expect(result['"u" nand "z"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result['"u" nand "z"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["0 nand 0"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["1 nand 1"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["1 nand 100"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["true nand true"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); + expect(result['"u" nand "u"']).toEqual( + [true, false, true, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"u" nand "z"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"u" nand "z"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["0 nand 0"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["1 nand 1"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["1 nand 100"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["true nand true"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("NOR", async function() { + it("NOR", async function () { const table = await perspective.table({ a: [0, 1, 0, 1], b: [0, 1, 0, 1], c: [0, 0, 0, 0], d: [1, 1, 1, 1], e: [false, false, false, false], - f: [true, true, true, true] + f: [true, true, true, true], }); const view = await table.view({ - expressions: ['"a" nor "b"', '"c" nor "d"', '"e" nor "f"', "0 nor 1", "false nor false"] + expressions: [ + '"a" nor "b"', + '"c" nor "d"', + '"e" nor "f"', + "0 nor 1", + "false nor false", + ], }); const result = await view.to_columns(); - expect(result['"a" nor "b"']).toEqual([true, false, true, false].map(x => (x ? 1 : 0))); - expect(result['"c" nor "d"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result['"e" nor "f"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["0 nor 1"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result["false nor false"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); + expect(result['"a" nor "b"']).toEqual( + [true, false, true, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"c" nor "d"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"e" nor "f"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["0 nor 1"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result["false nor false"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); - it("XOR", async function() { + it("XOR", async function () { const table = await perspective.table({ a: [0, 1, 0, 1], b: [0, 1, 0, 1], c: [0, 0, 0, 0], d: [1, 1, 1, 1], e: [false, false, false, false], - f: [true, true, true, true] + f: [true, true, true, true], }); const view = await table.view({ - expressions: ['"a" xor "b"', '"c" xor "d"', '"e" xor "f"', "0 xor 1", "false xor false"] + expressions: [ + '"a" xor "b"', + '"c" xor "d"', + '"e" xor "f"', + "0 xor 1", + "false xor false", + ], }); const result = await view.to_columns(); - expect(result['"a" xor "b"']).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); - expect(result['"c" xor "d"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result['"e" xor "f"']).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["0 xor 1"]).toEqual([true, true, true, true].map(x => (x ? 1 : 0))); - expect(result["false xor false"]).toEqual([false, false, false, false].map(x => (x ? 1 : 0))); + expect(result['"a" xor "b"']).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); + expect(result['"c" xor "d"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result['"e" xor "f"']).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["0 xor 1"]).toEqual( + [true, true, true, true].map((x) => (x ? 1 : 0)) + ); + expect(result["false xor false"]).toEqual( + [false, false, false, false].map((x) => (x ? 1 : 0)) + ); await view.delete(); await table.delete(); }); diff --git a/packages/perspective/test/js/expressions/string.js b/packages/perspective/test/js/expressions/string.js index 27a8a33bbd..0bde05b2d4 100644 --- a/packages/perspective/test/js/expressions/string.js +++ b/packages/perspective/test/js/expressions/string.js @@ -11,18 +11,20 @@ * Tests the correctness of each string computation function in various * environments and parameters - different types, nulls, undefined, etc. */ -module.exports = perspective => { - describe("String functions", function() { - it("Pivoted", async function() { +module.exports = (perspective) => { + describe("String functions", function () { + it("Pivoted", async function () { const table = await perspective.table({ a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], b: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"], - c: [2, 2, 4, 4] + c: [2, 2, 4, 4], }); const view = await table.view({ aggregates: {column: "last"}, row_pivots: ["column"], - expressions: [`//column\nconcat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `//column\nconcat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); @@ -32,346 +34,475 @@ module.exports = perspective => { "abcdefghijk, here is a long string, lMNoP", "deeeeef, here is a long string, DEF", "fg, here is a long string, EfG", - "hhs, here is a long string, HIjK" + "hhs, here is a long string, HIjK", ]); view.delete(); table.delete(); }); - it("Filtered", async function() { + it("Filtered", async function () { const table = await perspective.table({ a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], b: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"], - c: [2, 2, 4, 4] + c: [2, 2, 4, 4], }); const view = await table.view({ filter: [["column", "==", "hhs, here is a long string, HIjK"]], - expressions: [`//column\nconcat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `//column\nconcat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); - expect(result["column"]).toEqual(["hhs, here is a long string, HIjK"]); + expect(result["column"]).toEqual([ + "hhs, here is a long string, HIjK", + ]); view.delete(); table.delete(); }); - it("Length", async function() { + it("Length", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"] + a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], }); const view = await table.view({ - expressions: ['length("a")'] + expressions: ['length("a")'], }); let result = await view.to_columns(); - expect(result['length("a")']).toEqual(result.a.map(x => x.length)); + expect(result['length("a")']).toEqual( + result.a.map((x) => x.length) + ); view.delete(); table.delete(); }); - it("Length with null", async function() { + it("Length with null", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", null, undefined, "abcdefghijk"] + a: ["abc", "deeeeef", null, undefined, "abcdefghijk"], }); const view = await table.view({ - expressions: ['length("a")'] + expressions: ['length("a")'], }); let result = await view.to_columns(); - expect(result['length("a")']).toEqual(result.a.map(x => (x ? x.length : null))); + expect(result['length("a")']).toEqual( + result.a.map((x) => (x ? x.length : null)) + ); view.delete(); table.delete(); }); - it("Order", async function() { + it("Order", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"] + a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], }); const view = await table.view({ - expressions: [`order("a", 'deeeeef', 'fg', 'abcdefghijk', 'hhs', 'abc')`] + expressions: [ + `order("a", 'deeeeef', 'fg', 'abcdefghijk', 'hhs', 'abc')`, + ], }); let result = await view.to_columns(); - expect(result[`order("a", 'deeeeef', 'fg', 'abcdefghijk', 'hhs', 'abc')`]).toEqual([4, 0, 1, 3, 2]); + expect( + result[ + `order("a", 'deeeeef', 'fg', 'abcdefghijk', 'hhs', 'abc')` + ] + ).toEqual([4, 0, 1, 3, 2]); view.delete(); table.delete(); }); - it("Order with partial specification", async function() { + it("Order with partial specification", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"] + a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], }); const view = await table.view({ - expressions: [`order("a", 'deeeeef', 'fg')`] + expressions: [`order("a", 'deeeeef', 'fg')`], }); let result = await view.to_columns(); - expect(result[`order("a", 'deeeeef', 'fg')`]).toEqual([2, 0, 1, 2, 2]); + expect(result[`order("a", 'deeeeef', 'fg')`]).toEqual([ + 2, 0, 1, 2, 2, + ]); view.delete(); table.delete(); }); - it("Order with null", async function() { + it("Order with null", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", null, undefined, "abcdefghijk"] + a: ["abc", "deeeeef", null, undefined, "abcdefghijk"], }); const view = await table.view({ - expressions: [`order("a", 'deeeeef', 'abcdefghijk', 'abc')`] + expressions: [`order("a", 'deeeeef', 'abcdefghijk', 'abc')`], }); let result = await view.to_columns(); - expect(result[`order("a", 'deeeeef', 'abcdefghijk', 'abc')`]).toEqual([2, 0, null, null, 1]); + expect( + result[`order("a", 'deeeeef', 'abcdefghijk', 'abc')`] + ).toEqual([2, 0, null, null, 1]); view.delete(); table.delete(); }); - it("Upper", async function() { + it("Upper", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"] + a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], }); const view = await table.view({ - expressions: ['upper("a")'] + expressions: ['upper("a")'], }); let result = await view.to_columns(); - expect(result['upper("a")']).toEqual(result.a.map(x => x.toUpperCase())); + expect(result['upper("a")']).toEqual( + result.a.map((x) => x.toUpperCase()) + ); view.delete(); table.delete(); }); - it("Uppercase with null", async function() { + it("Uppercase with null", async function () { const table = await perspective.table({ - a: ["abc", "deeeeef", null, undefined, "abcdefghijk"] + a: ["abc", "deeeeef", null, undefined, "abcdefghijk"], }); const view = await table.view({ - expressions: ['upper("a")'] + expressions: ['upper("a")'], }); let result = await view.to_columns(); - expect(result['upper("a")']).toEqual(result.a.map(x => (x ? x.toUpperCase() : null))); + expect(result['upper("a")']).toEqual( + result.a.map((x) => (x ? x.toUpperCase() : null)) + ); view.delete(); table.delete(); }); - it.skip("Uppercase, non-utf8", async function() { + it.skip("Uppercase, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: ['upper("a")', 'upper("b")'] + expressions: ['upper("a")', 'upper("b")'], }); let result = await view.to_columns(); - expect(result['upper("a")']).toEqual(result.a.map(x => (x ? x.toUpperCase() : null))); - expect(result['upper("b")']).toEqual(result.b.map(x => (x ? x.toUpperCase() : null))); + expect(result['upper("a")']).toEqual( + result.a.map((x) => (x ? x.toUpperCase() : null)) + ); + expect(result['upper("b")']).toEqual( + result.b.map((x) => (x ? x.toUpperCase() : null)) + ); view.delete(); table.delete(); }); - it("Lowercase", async function() { + it("Lowercase", async function () { const table = await perspective.table({ - a: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"] + a: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"], }); const view = await table.view({ - expressions: ['lower("a")'] + expressions: ['lower("a")'], }); let result = await view.to_columns(); - expect(result['lower("a")']).toEqual(result.a.map(x => x.toLowerCase())); + expect(result['lower("a")']).toEqual( + result.a.map((x) => x.toLowerCase()) + ); view.delete(); table.delete(); }); - it("Lowercase with null", async function() { + it("Lowercase with null", async function () { const table = await perspective.table({ - a: ["ABC", "DEF", null, undefined, "lMNoP"] + a: ["ABC", "DEF", null, undefined, "lMNoP"], }); const view = await table.view({ - expressions: ['lower("a")'] + expressions: ['lower("a")'], }); let result = await view.to_columns(); - expect(result['lower("a")']).toEqual(result.a.map(x => (x ? x.toLowerCase() : null))); + expect(result['lower("a")']).toEqual( + result.a.map((x) => (x ? x.toLowerCase() : null)) + ); view.delete(); table.delete(); }); - it("Lowercase, non-utf8", async function() { + it("Lowercase, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: ['lower("a")', 'lower("b")'] + expressions: ['lower("a")', 'lower("b")'], }); let result = await view.to_columns(); - expect(result['lower("a")']).toEqual(result.a.map(x => (x ? x.toLowerCase() : null))); - expect(result['lower("b")']).toEqual(result.b.map(x => (x ? x.toLowerCase() : null))); + expect(result['lower("a")']).toEqual( + result.a.map((x) => (x ? x.toLowerCase() : null)) + ); + expect(result['lower("b")']).toEqual( + result.b.map((x) => (x ? x.toLowerCase() : null)) + ); view.delete(); table.delete(); }); - it("Concat", async function() { + it("Concat", async function () { const table = await perspective.table({ a: ["abc", "deeeeef", "fg", "hhs", "abcdefghijk"], - b: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"] + b: ["ABC", "DEF", "EfG", "HIjK", "lMNoP"], }); const view = await table.view({ - expressions: [`concat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `concat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); - expect(result[`concat("a", ', ', 'here is a long string, ', "b")`]).toEqual(result.a.map((x, idx) => x + ", here is a long string, " + result.b[idx])); + expect( + result[`concat("a", ', ', 'here is a long string, ', "b")`] + ).toEqual( + result.a.map( + (x, idx) => x + ", here is a long string, " + result.b[idx] + ) + ); view.delete(); table.delete(); }); - it("Concats, nulls", async function() { + it("Concats, nulls", async function () { const table = await perspective.table({ a: ["ABC", "DEF", null, "HIjK", "lMNoP"], - b: ["ABC", undefined, "EfG", "HIjK", "lMNoP"] + b: ["ABC", undefined, "EfG", "HIjK", "lMNoP"], }); const view = await table.view({ - expressions: [`concat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `concat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); - let expected = result.a.map((x, idx) => x + ", here is a long string, " + result.b[idx]); + let expected = result.a.map( + (x, idx) => x + ", here is a long string, " + result.b[idx] + ); expected[1] = null; expected[2] = null; - expect(result[`concat("a", ', ', 'here is a long string, ', "b")`]).toEqual(expected); + expect( + result[`concat("a", ', ', 'here is a long string, ', "b")`] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Concats, extra long", async function() { + it("Concats, extra long", async function () { const table = await perspective.table({ - a: ["ABC".repeat(10), "DEF".repeat(10), null, "HIjK".repeat(10), "lMNoP".repeat(10)], - b: ["ABC", undefined, "EfG", "HIjK", "lMNoP"] + a: [ + "ABC".repeat(10), + "DEF".repeat(10), + null, + "HIjK".repeat(10), + "lMNoP".repeat(10), + ], + b: ["ABC", undefined, "EfG", "HIjK", "lMNoP"], }); const view = await table.view({ - expressions: [`concat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `concat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); - let expected = result.a.map((x, idx) => x + ", here is a long string, " + result.b[idx]); + let expected = result.a.map( + (x, idx) => x + ", here is a long string, " + result.b[idx] + ); expected[1] = null; expected[2] = null; - expect(result[`concat("a", ', ', 'here is a long string, ', "b")`]).toEqual(expected); + expect( + result[`concat("a", ', ', 'here is a long string, ', "b")`] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Concats, non-utf8", async function() { + it("Concats, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: [`concat("a", ', ', 'here is a long string, ', "b")`] + expressions: [ + `concat("a", ', ', 'here is a long string, ', "b")`, + ], }); let result = await view.to_columns(); - let expected = result.a.map((x, idx) => x + ", here is a long string, " + result.b[idx]); + let expected = result.a.map( + (x, idx) => x + ", here is a long string, " + result.b[idx] + ); expected[2] = null; - expect(result[`concat("a", ', ', 'here is a long string, ', "b")`]).toEqual(expected); + expect( + result[`concat("a", ', ', 'here is a long string, ', "b")`] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Upper concats", async function() { + it("Upper concats", async function () { const table = await perspective.table({ - a: ["hello world", "abakshdaskjhlgkjasdiukjqewlkjesaljhgdaskd", null], - b: ["asjdhlkhfdshafiywhjklsjfaksdgjadkjlv", "abc", "EfG"] + a: [ + "hello world", + "abakshdaskjhlgkjasdiukjqewlkjesaljhgdaskd", + null, + ], + b: ["asjdhlkhfdshafiywhjklsjfaksdgjadkjlv", "abc", "EfG"], }); const view = await table.view({ - expressions: [`upper(concat("a", ', ', 'here is a long string, ', "b"))`] + expressions: [ + `upper(concat("a", ', ', 'here is a long string, ', "b"))`, + ], }); let result = await view.to_columns(); - let expected = result[`upper(concat("a", ', ', 'here is a long string, ', "b"))`].map(x => (x ? x.toUpperCase() : null)); + let expected = result[ + `upper(concat("a", ', ', 'here is a long string, ', "b"))` + ].map((x) => (x ? x.toUpperCase() : null)); expected[2] = null; - expect(result[`upper(concat("a", ', ', 'here is a long string, ', "b"))`]).toEqual(expected); + expect( + result[ + `upper(concat("a", ', ', 'here is a long string, ', "b"))` + ] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Lower concats", async function() { + it("Lower concats", async function () { const table = await perspective.table({ - a: ["HELLO WORLD SADJKHFUOIWNS:AJKSKJDJBCL", "KJBSJHDBGASHJDB ASCBAKISJHDKJSAHNDKASJ SJKHDJKAS", null], - b: ["LDJSA:KJFGHJAKLSoijSJDM:ALKJDAS)oewqSAPDOD", "ASdhnlsaadkjhASJKDSAHIUEHYWIUDSHDNBKJSAD", "EfG"] + a: [ + "HELLO WORLD SADJKHFUOIWNS:AJKSKJDJBCL", + "KJBSJHDBGASHJDB ASCBAKISJHDKJSAHNDKASJ SJKHDJKAS", + null, + ], + b: [ + "LDJSA:KJFGHJAKLSoijSJDM:ALKJDAS)oewqSAPDOD", + "ASdhnlsaadkjhASJKDSAHIUEHYWIUDSHDNBKJSAD", + "EfG", + ], }); const view = await table.view({ - expressions: [`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`] + expressions: [ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))`, + ], }); let result = await view.to_columns(); - let expected = result[`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`].map(x => (x ? x.toLowerCase() : null)); + let expected = result[ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))` + ].map((x) => (x ? x.toLowerCase() : null)); expected[2] = null; - expect(result[`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`]).toEqual(expected); + expect( + result[ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))` + ] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Order lower concats", async function() { + it("Order lower concats", async function () { const table = await perspective.table({ a: ["HELLO WORLD", "VERY LONG STRING HERE", null], - b: ["ALSO HELLO WORLD", "ANOTHER LONG STRING IS HERE", "EfG"] + b: ["ALSO HELLO WORLD", "ANOTHER LONG STRING IS HERE", "EfG"], }); const view = await table.view({ - expressions: [`order(lower(concat("a", ', ', 'HERE is a long string, ', "b")), 'very long string here, here is a long string, another long string is here')`] + expressions: [ + `order(lower(concat("a", ', ', 'HERE is a long string, ', "b")), 'very long string here, here is a long string, another long string is here')`, + ], }); let result = await view.to_columns(); - expect(result[`order(lower(concat("a", ', ', 'HERE is a long string, ', "b")), 'very long string here, here is a long string, another long string is here')`]).toEqual([1, 0, null]); + expect( + result[ + `order(lower(concat("a", ', ', 'HERE is a long string, ', "b")), 'very long string here, here is a long string, another long string is here')` + ] + ).toEqual([1, 0, null]); view.delete(); table.delete(); }); - it.skip("Upper concats, non-utf8", async function() { + it.skip("Upper concats, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: [`upper(concat("a", ', ', 'here is a long string, ', "b"))`] + expressions: [ + `upper(concat("a", ', ', 'here is a long string, ', "b"))`, + ], }); let result = await view.to_columns(); - let expected = result[`upper(concat("a", ', ', 'here is a long string, ', "b"))`].map(x => (x ? x.toUpperCase() : null)); + let expected = result[ + `upper(concat("a", ', ', 'here is a long string, ', "b"))` + ].map((x) => (x ? x.toUpperCase() : null)); expected[2] = null; - expect(result[`upper(concat("a", ', ', 'here is a long string, ', "b"))`]).toEqual(expected); + expect( + result[ + `upper(concat("a", ', ', 'here is a long string, ', "b"))` + ] + ).toEqual(expected); view.delete(); table.delete(); }); - it("Lower concats, non-utf8", async function() { + it("Lower concats, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: [`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`] + expressions: [ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))`, + ], }); let result = await view.to_columns(); - let expected = result[`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`].map(x => (x ? x.toLowerCase() : null)); - expect(result[`lower(concat("a", ', ', 'HERE is a long string, ', "b"))`]).toEqual(expected); + let expected = result[ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))` + ].map((x) => (x ? x.toLowerCase() : null)); + expect( + result[ + `lower(concat("a", ', ', 'HERE is a long string, ', "b"))` + ] + ).toEqual(expected); view.delete(); table.delete(); }); - it.skip("Length concats, non-utf8", async function() { + it.skip("Length concats, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: [`length(concat("a", ', ', 'here is a long string, ', "b"))`] + expressions: [ + `length(concat("a", ', ', 'here is a long string, ', "b"))`, + ], }); let result = await view.to_columns(); - let expected = result.a.map((x, idx) => (x + ", here is a long string, " + result.b[idx]).length); + let expected = result.a.map( + (x, idx) => + (x + ", here is a long string, " + result.b[idx]).length + ); expected[2] = null; - expect(result[`length(concat("a", ', ', 'here is a long string, ', "b"))`]).toEqual(expected); + expect( + result[ + `length(concat("a", ', ', 'here is a long string, ', "b"))` + ] + ).toEqual(expected); view.delete(); table.delete(); }); - it.skip("Order concats, non-utf8", async function() { + it.skip("Order concats, non-utf8", async function () { const table = await perspective.table({ a: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝓊⋁ẅ⤫𝛾𝓏", null], - b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"] + b: ["𝕙ḗľᶅở щṏᵲɭⅾ", "𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ", "EfG"], }); const view = await table.view({ - expressions: [`var x := concat("a", ', ', 'here is a long string, ', "b"); order(x, '𝓊⋁ẅ⤫𝛾𝓏, here is a long string, 𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ', '𝕙ḗľᶅở щṏᵲɭⅾ, here is a long string, 𝕙ḗľᶅở щṏᵲɭⅾ')`] + expressions: [ + `var x := concat("a", ', ', 'here is a long string, ', "b"); order(x, '𝓊⋁ẅ⤫𝛾𝓏, here is a long string, 𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ', '𝕙ḗľᶅở щṏᵲɭⅾ, here is a long string, 𝕙ḗľᶅở щṏᵲɭⅾ')`, + ], }); let result = await view.to_columns(); expect( - result[`var x := concat("a", ', ', 'here is a long string, ', "b"); order(x, '𝓊⋁ẅ⤫𝛾𝓏, here is a long string, 𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ', '𝕙ḗľᶅở щṏᵲɭⅾ, here is a long string, 𝕙ḗľᶅở щṏᵲɭⅾ')`] + result[ + `var x := concat("a", ', ', 'here is a long string, ', "b"); order(x, '𝓊⋁ẅ⤫𝛾𝓏, here is a long string, 𝑢ⱴⱳẍ𝘺𝘇ӑṣᶑᵴ', '𝕙ḗľᶅở щṏᵲɭⅾ, here is a long string, 𝕙ḗľᶅở щṏᵲɭⅾ')` + ] ).toEqual([1, 0, 2]); view.delete(); @@ -379,15 +510,15 @@ module.exports = perspective => { }); }); - describe("String comparison", function() { - it("==", async function() { + describe("String comparison", function () { + it("==", async function () { const table = await perspective.table({ a: ["ABC", "DEF", null, "HIjK", "lMNoP"], - b: ["ABC", undefined, null, "HIjK", "lMNoP"] + b: ["ABC", undefined, null, "HIjK", "lMNoP"], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); @@ -398,30 +529,34 @@ module.exports = perspective => { table.delete(); }); - it("== on expression output", async function() { + it("== on expression output", async function () { const table = await perspective.table({ a: ["ABC", "DEF", "cba", "HIjK", "lMNoP"], - b: ["ABC", "ad", "asudfh", "HIjK", "lMNoP"] + b: ["ABC", "ad", "asudfh", "HIjK", "lMNoP"], }); let view = await table.view({ - expressions: [`concat("a", ', ', "b") == concat("a", ', ', "b")`] + expressions: [ + `concat("a", ', ', "b") == concat("a", ', ', "b")`, + ], }); let result = await view.to_columns(); - expect(result[`concat("a", ', ', "b") == concat("a", ', ', "b")`]).toEqual([1, 1, 1, 1, 1]); + expect( + result[`concat("a", ', ', "b") == concat("a", ', ', "b")`] + ).toEqual([1, 1, 1, 1, 1]); view.delete(); table.delete(); }); - it("==, nulls", async function() { + it("==, nulls", async function () { const table = await perspective.table({ a: ["ABC", "DEF", undefined, null, null], - b: ["ABC", "not", "EfG", "HIjK", null] + b: ["ABC", "not", "EfG", "HIjK", null], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); @@ -430,14 +565,26 @@ module.exports = perspective => { table.delete(); }); - it("==, extra long", async function() { + it("==, extra long", async function () { const table = await perspective.table({ - a: ["ABC".repeat(10), "DEF".repeat(10), null, "HIjK".repeat(10), "lMNoP"], - b: ["ABC".repeat(10), "DEF".repeat(10), undefined, "HIjK", "lMNoP"] + a: [ + "ABC".repeat(10), + "DEF".repeat(10), + null, + "HIjK".repeat(10), + "lMNoP", + ], + b: [ + "ABC".repeat(10), + "DEF".repeat(10), + undefined, + "HIjK", + "lMNoP", + ], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); @@ -447,14 +594,14 @@ module.exports = perspective => { table.delete(); }); - it("==, short", async function() { + it("==, short", async function () { const table = await perspective.table({ a: ["A", "E", null, "h", "l"], - b: ["a", "E", undefined, "h", "l"] + b: ["a", "E", undefined, "h", "l"], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); @@ -463,14 +610,26 @@ module.exports = perspective => { table.delete(); }); - it("==, mixed length", async function() { + it("==, mixed length", async function () { const table = await perspective.table({ - a: ["ABC".repeat(100), "DEF".repeat(10), null, "hijk".repeat(10), "lm"], - b: ["arc".repeat(50), "DEf".repeat(10), undefined, "HIjK", "lMNoP"] + a: [ + "ABC".repeat(100), + "DEF".repeat(10), + null, + "hijk".repeat(10), + "lm", + ], + b: [ + "arc".repeat(50), + "DEf".repeat(10), + undefined, + "HIjK", + "lMNoP", + ], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); @@ -479,25 +638,25 @@ module.exports = perspective => { table.delete(); }); - it("==, UTF-8", async function() { + it("==, UTF-8", async function () { const table = await perspective.table({ a: [ ">ﺐ{׆Meڱ㒕宾ⷭ̽쉱L𞔚Ո拏۴ګPظǭPۋV|팺㺞㷾墁鴦򒲹|ۿ򧊊䭪񪩛𬦢񺣠񦋳򵾳蛲񖑐iM񊪝񆷯", "灙𬡍瀳։󷿙񅈕ǐ-kʂiJ!P񙺍󵝳̃੝w𬾐򕕉耨󉋦o򰵏詂3򒤹J<ꑭ񃕱Ӏ𛤦4u򉠚UPf􂢳P##Q񪂈", "ĈᔞZ񇌖Qఋ?x?#$12ボլ㕢ﺧ𷛘󽙮[񲸧I񟭝򋨰魏ճכ󽺴ۏ󫨫䆐'㓔ǃ[ְ੬䎕寽𤩚ߨ袧򲕊򓰷|%", "ęԛ򓍯󍩁𨞟㰢œ󇂣õ􌁇΍Ԥ⥯۷˝㿙צּ񬆩򤿭顂ݦۍ式+=ԋ帋񃴕譋ⴏ0l􅏎߳cί򇈊iȞڈU򆐹񍖮򷡦̥𩮏DZ", - "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖" + "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖", ], b: [ ">ﺐ{׆Meڱ㒕宾ⷭ̽쉱L𞔚Ո拏۴ګPظǭPۋV|팺㺞㷾墁鴦򒲹|ۿ򧊊䭪񪩛𬦢񺣠񦋳򵾳蛲񖑐iM񊪝񆷯", "灙𬡍瀳։󷿙񅈕ǐ-kʂiJ!P񙺍󵝳̃੝w𬾐򕕉耨󉋦o򰵏詂3򒤹J<ꑭ񃕱Ӏ𛤦4u򉠚UPf􂢳P##Q񪂈", "ĈᔞZ񇌖Qఋ?x?#$12ボլ㕢ﺧ𷛘󽙮[񲸧I񟭝򋨰魏ճכ󽺴ۏ󫨫䆐'㓔ǃ[ְ੬䎕寽𤩚ߨ袧򲕊򓰷|%", "ęԛ򓍯󍩁𨞟㰢œ󇂣õ􌁇΍Ԥ⥯۷˝㿙צּ񬆩򤿭顂ݦۍ式+=ԋ帋񃴕譋ⴏ0l􅏎߳cί򇈊iȞڈU򆐹񍖮򷡦̥𩮏DZ", - "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖2" - ] + "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖2", + ], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); expect(result['"a" == "b"']).toEqual([1, 1, 1, 1, 0]); @@ -505,13 +664,25 @@ module.exports = perspective => { table.delete(); }); - it("==, UTF-8 converted to Unicode", async function() { + it("==, UTF-8 converted to Unicode", async function () { const table = await perspective.table({ - a: [">{MeLPPV||iM", "-kiJ!Pwo3J<4uUPfP##Q", "ZQ?x?#$12[I'[|%", "ܦf+=0lciU", "030wo􎼨KOjpdD"], - b: [">{MeLPPV||iM", "-kiJ!Pwo3J<4uUPfP##Q", "ZQ?x?#$12[I'[|%", "ܦf+=0lciU", "030wo􎼨KOjpdD2"] + a: [ + ">{MeLPPV||iM", + "-kiJ!Pwo3J<4uUPfP##Q", + "ZQ?x?#$12[I'[|%", + "ܦf+=0lciU", + "030wo􎼨KOjpdD", + ], + b: [ + ">{MeLPPV||iM", + "-kiJ!Pwo3J<4uUPfP##Q", + "ZQ?x?#$12[I'[|%", + "ܦf+=0lciU", + "030wo􎼨KOjpdD2", + ], }); let view = await table.view({ - expressions: ['"a" == "b"'] + expressions: ['"a" == "b"'], }); let result = await view.to_columns(); expect(result['"a" == "b"']).toEqual([1, 1, 1, 1, 0]); diff --git a/packages/perspective/test/js/expressions/updates.js b/packages/perspective/test/js/expressions/updates.js index da9504aeea..e0c183a5a5 100644 --- a/packages/perspective/test/js/expressions/updates.js +++ b/packages/perspective/test/js/expressions/updates.js @@ -13,101 +13,148 @@ const data = [ {x: 1, y: 2}, {x: 2, y: 4}, {x: 3, y: 6}, - {x: 4, y: 8} + {x: 4, y: 8}, ]; const pivot_data = [ {int: 1, float: 2.25}, {int: 2, float: 3.5}, {int: 3, float: 4.75}, - {int: 4, float: 5.25} + {int: 4, float: 5.25}, ]; /** * Tests the correctness of updates on Tables with expression columns created * through `View`, including partial updates, appends, and removes. */ -module.exports = perspective => { - describe("Expression updates", function() { - it("Scalar-only expressions should respond to appends", async function() { +module.exports = (perspective) => { + describe("Expression updates", function () { + it("Scalar-only expressions should respond to appends", async function () { const table = await perspective.table({ x: [1.5, 2.5, 3.5, 4.5], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ["10 + 20", "lower('ABC')", "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"] + expressions: [ + "10 + 20", + "lower('ABC')", + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')", + ], }); const before = await view.to_columns(); expect(before["10 + 20"]).toEqual([30, 30, 30, 30]); - expect(before["lower('ABC')"]).toEqual(["abc", "abc", "abc", "abc"]); - expect(before["concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"]).toEqual([ + expect(before["lower('ABC')"]).toEqual([ + "abc", + "abc", + "abc", + "abc", + ]); + expect( + before[ + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')" + ] + ).toEqual([ + "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", - "hello world, here is a long, long, long string with lots of characters" ]); table.update({x: [5, 6, 7]}); const after = await view.to_columns(); expect(after["10 + 20"]).toEqual([30, 30, 30, 30, 30, 30, 30]); - expect(after["lower('ABC')"]).toEqual(["abc", "abc", "abc", "abc", "abc", "abc", "abc"]); - expect(after["concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"]).toEqual([ + expect(after["lower('ABC')"]).toEqual([ + "abc", + "abc", + "abc", + "abc", + "abc", + "abc", + "abc", + ]); + expect( + after[ + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')" + ] + ).toEqual([ + "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", - "hello world, here is a long, long, long string with lots of characters" ]); view.delete(); table.delete(); }); - it("Conditional expressions should respond to appends", async function() { + it("Conditional expressions should respond to appends", async function () { const table = await perspective.table({ x: [1.5, 2.5, 3.5, 4.5], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ // conditional here must be float, as int-float comparisons // don't work as of yet. - expressions: ['if ("x" > 4) 10; else 100', `"y" == 'A' ? true : false`] + expressions: [ + 'if ("x" > 4) 10; else 100', + `"y" == 'A' ? true : false`, + ], }); const before = await view.to_columns(); - expect(before['if ("x" > 4) 10; else 100']).toEqual([100, 100, 100, 10]); + expect(before['if ("x" > 4) 10; else 100']).toEqual([ + 100, 100, 100, 10, + ]); expect(before[`"y" == 'A' ? true : false`]).toEqual([1, 0, 0, 0]); table.update({x: [5, 6, 7], y: ["A", "A", "B"]}); const after = await view.to_columns(); - expect(after['if ("x" > 4) 10; else 100']).toEqual([100, 100, 100, 10, 10, 10, 10]); - expect(after[`"y" == 'A' ? true : false`]).toEqual([1, 0, 0, 0, 1, 1, 0]); + expect(after['if ("x" > 4) 10; else 100']).toEqual([ + 100, 100, 100, 10, 10, 10, 10, + ]); + expect(after[`"y" == 'A' ? true : false`]).toEqual([ + 1, 0, 0, 0, 1, 1, 0, + ]); view.delete(); table.delete(); }); - it("Scalar-only expressions should respond to partial updates", async function() { + it("Scalar-only expressions should respond to partial updates", async function () { const table = await perspective.table( { x: [1.5, 2.5, 3.5, 4.5], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, {index: "y"} ); const view = await table.view({ - expressions: ["10 + 20", "lower('ABC')", "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"] + expressions: [ + "10 + 20", + "lower('ABC')", + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')", + ], }); const before = await view.to_columns(); expect(before["10 + 20"]).toEqual([30, 30, 30, 30]); - expect(before["lower('ABC')"]).toEqual(["abc", "abc", "abc", "abc"]); - expect(before["concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"]).toEqual([ + expect(before["lower('ABC')"]).toEqual([ + "abc", + "abc", + "abc", + "abc", + ]); + expect( + before[ + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')" + ] + ).toEqual([ + "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", - "hello world, here is a long, long, long string with lots of characters" ]); table.update({x: [5, 6, 7], y: ["A", "B", "D"]}); @@ -115,36 +162,50 @@ module.exports = perspective => { const after = await view.to_columns(); expect(after["10 + 20"]).toEqual([30, 30, 30, 30]); expect(after["lower('ABC')"]).toEqual(["abc", "abc", "abc", "abc"]); - expect(after["concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"]).toEqual([ + expect( + after[ + "concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')" + ] + ).toEqual([ + "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", "hello world, here is a long, long, long string with lots of characters", - "hello world, here is a long, long, long string with lots of characters" ]); view.delete(); table.delete(); }); - it("Expressions should respond to multiple streaming updates", async function() { + it("Expressions should respond to multiple streaming updates", async function () { const table = await perspective.table({ x: [1.5, 2.5, 3.5, 4.5], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ["123", '//c0\n10 + "x"', '//c1\nlower("y")', `//c2\nconcat("y", ' ', 'abcd')`] + expressions: [ + "123", + '//c0\n10 + "x"', + '//c1\nlower("y")', + `//c2\nconcat("y", ' ', 'abcd')`, + ], }); const before = await view.to_columns(); expect(before["123"]).toEqual(Array(4).fill(123)); expect(before["c0"]).toEqual([11.5, 12.5, 13.5, 14.5]); expect(before["c1"]).toEqual(["a", "b", "c", "d"]); - expect(before["c2"]).toEqual(["A abcd", "B abcd", "C abcd", "D abcd"]); + expect(before["c2"]).toEqual([ + "A abcd", + "B abcd", + "C abcd", + "D abcd", + ]); for (let i = 0; i < 5; i++) { table.update({ x: [1.5, 2.5, 3.5, 4.5], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); } @@ -152,8 +213,37 @@ module.exports = perspective => { expect(await view.num_rows()).toEqual(24); console.log(after); expect(after["123"]).toEqual(Array(24).fill(123)); - expect(after["c0"]).toEqual([11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5]); - expect(after["c1"]).toEqual(["a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d"]); + expect(after["c0"]).toEqual([ + 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, + 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, + 11.5, 12.5, 13.5, 14.5, + ]); + expect(after["c1"]).toEqual([ + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + ]); expect(after["c2"]).toEqual([ "A abcd", "B abcd", @@ -178,44 +268,55 @@ module.exports = perspective => { "A abcd", "B abcd", "C abcd", - "D abcd" + "D abcd", ]); view.delete(); table.delete(); }); - it("Conditional expressions should respond to partial updates", async function() { + it("Conditional expressions should respond to partial updates", async function () { const table = await perspective.table( { x: [1.5, 2.5, 3.5, 4.5], y: ["A", "B", "C", "D"], - z: ["a", "b", "c", "d"] + z: ["a", "b", "c", "d"], }, {index: "y"} ); const view = await table.view({ - expressions: ['if ("x" > 4) 10; else 100', `"z" == 'a' ? true : false`] + expressions: [ + 'if ("x" > 4) 10; else 100', + `"z" == 'a' ? true : false`, + ], }); const before = await view.to_columns(); - expect(before['if ("x" > 4) 10; else 100']).toEqual([100, 100, 100, 10]); + expect(before['if ("x" > 4) 10; else 100']).toEqual([ + 100, 100, 100, 10, + ]); expect(before[`"z" == 'a' ? true : false`]).toEqual([1, 0, 0, 0]); - table.update({x: [5, 6, 7], y: ["A", "C", "D"], z: ["a", "a", "a"]}); + table.update({ + x: [5, 6, 7], + y: ["A", "C", "D"], + z: ["a", "a", "a"], + }); const after = await view.to_columns(); - expect(after['if ("x" > 4) 10; else 100']).toEqual([10, 100, 10, 10]); + expect(after['if ("x" > 4) 10; else 100']).toEqual([ + 10, 100, 10, 10, + ]); expect(after[`"z" == 'a' ? true : false`]).toEqual([1, 0, 1, 1]); view.delete(); table.delete(); }); - it("Appends should notify expression column", async function() { + it("Appends should notify expression column", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const before = await view.to_columns(); @@ -223,18 +324,25 @@ module.exports = perspective => { table.update({y: ["HELLO", "WORLD"]}); const after = await view.to_columns(); - expect(after['lower("y")']).toEqual(["a", "b", "c", "d", "hello", "world"]); + expect(after['lower("y")']).toEqual([ + "a", + "b", + "c", + "d", + "hello", + "world", + ]); view.delete(); table.delete(); }); - it("Multiple appends should notify expression column", async function() { + it("Multiple appends should notify expression column", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const before = await view.to_columns(); @@ -242,53 +350,73 @@ module.exports = perspective => { table.update({y: ["HELLO", "WORLD"]}); const result = await view.to_columns(); - expect(result['lower("y")']).toEqual(["a", "b", "c", "d", "hello", "world"]); + expect(result['lower("y")']).toEqual([ + "a", + "b", + "c", + "d", + "hello", + "world", + ]); table.update({x: [12, 34], y: ["XYZ", "ABCdEFg"]}); const result2 = await view.to_columns(); expect(result2["x"]).toEqual([1, 2, 3, 4, null, null, 12, 34]); - expect(result2['lower("y")']).toEqual(["a", "b", "c", "d", "hello", "world", "xyz", "abcdefg"]); + expect(result2['lower("y")']).toEqual([ + "a", + "b", + "c", + "d", + "hello", + "world", + "xyz", + "abcdefg", + ]); view.delete(); table.delete(); }); - it("Appends should notify expression column with multiple columns", async function() { + it("Appends should notify expression column with multiple columns", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], y: [1.5, 2.5, 3.5, 4.5], - z: [2, 4, 6, 8] + z: [2, 4, 6, 8], }); const view = await table.view({ - expressions: ['"x" + ("y" + 5.5) / "z"'] + expressions: ['"x" + ("y" + 5.5) / "z"'], }); const before = await view.to_columns(); - expect(before['"x" + ("y" + 5.5) / "z"']).toEqual([4.5, 4, 4.5, 5.25]); + expect(before['"x" + ("y" + 5.5) / "z"']).toEqual([ + 4.5, 4, 4.5, 5.25, + ]); table.update({ x: [5, 6, 7, 8], y: [5.5, 6.5, 7.5, 8.5], - z: [10, 10, 10, 10] + z: [10, 10, 10, 10], }); const after = await view.to_columns(); - expect(after['"x" + ("y" + 5.5) / "z"']).toEqual([4.5, 4, 4.5, 5.25, 6.1, 7.2, 8.3, 9.4]); + expect(after['"x" + ("y" + 5.5) / "z"']).toEqual([ + 4.5, 4, 4.5, 5.25, 6.1, 7.2, 8.3, 9.4, + ]); view.delete(); table.delete(); }); - it("Partial updates should notify expression column", async function() { + it("Partial updates should notify expression column", async function () { const table = await perspective.table( { x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, {index: "x"} ); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const before = await view.to_columns(); @@ -301,14 +429,14 @@ module.exports = perspective => { table.delete(); }); - it("Dependent column appends with missing columns should notify expression columns", async function() { + it("Dependent column appends with missing columns should notify expression columns", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], y: [1.5, 2.5, 3.5, 4.5], - z: ["a", "b", "c", "d"] + z: ["a", "b", "c", "d"], }); const view = await table.view({ - expressions: ['upper("z")'] + expressions: ['upper("z")'], }); const before = await view.to_columns(); expect(before['upper("z")']).toEqual(["A", "B", "C", "D"]); @@ -321,20 +449,20 @@ module.exports = perspective => { x: [1, 2, 3, 4, 2, 4], y: [1.5, 2.5, 3.5, 4.5, 10.5, 12.5], z: ["a", "b", "c", "d", null, null], - 'upper("z")': ["A", "B", "C", "D", null, null] + 'upper("z")': ["A", "B", "C", "D", null, null], }); view.delete(); table.delete(); }); - it("Dependent column update appends should notify expression columns, pivoted arity 1", async function() { + it("Dependent column update appends should notify expression columns, pivoted arity 1", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["A", "B", "C", "C"] + y: ["A", "B", "C", "C"], }); const view = await table.view({ row_pivots: ['lower("y")'], - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const before = await view.to_columns(); @@ -342,7 +470,7 @@ module.exports = perspective => { __ROW_PATH__: [[], ["a"], ["b"], ["c"]], 'lower("y")': [4, 1, 1, 2], x: [10, 1, 2, 7], - y: [4, 1, 1, 2] + y: [4, 1, 1, 2], }); table.update({y: ["HELLO", "WORLD"]}); @@ -352,16 +480,16 @@ module.exports = perspective => { __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["hello"], ["world"]], 'lower("y")': [6, 1, 1, 2, 1, 1], x: [10, 1, 2, 7, 0, 0], - y: [6, 1, 1, 2, 1, 1] + y: [6, 1, 1, 2, 1, 1], }); view.delete(); table.delete(); }); - it("Dependent column appends should notify expression columns, arity 2", async function() { + it("Dependent column appends should notify expression columns, arity 2", async function () { const table = await perspective.table(common.int_float_data); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const before = await view.to_columns(); expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -369,15 +497,19 @@ module.exports = perspective => { table.update({x: [2, 4], w: [10.5, 12.5]}); const after = await view.to_columns(); - expect(after['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5, 12.5, 16.5]); + expect(after['"w" + "x"']).toEqual([ + 2.5, 4.5, 6.5, 8.5, 12.5, 16.5, + ]); view.delete(); table.delete(); }); - it("Dependent column updates on all column updates should notify expression columns, arity 2", async function() { - const table = await perspective.table(common.int_float_data, {index: "x"}); + it("Dependent column updates on all column updates should notify expression columns, arity 2", async function () { + const table = await perspective.table(common.int_float_data, { + index: "x", + }); const view = await table.view({ - expressions: ['"w" + "x"', 'upper("y")'] + expressions: ['"w" + "x"', 'upper("y")'], }); let before = await view.to_columns(); expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]); @@ -396,14 +528,14 @@ module.exports = perspective => { table.delete(); }); - it("Dependent column appends should notify expression columns on different views, arity 2", async function() { + it("Dependent column appends should notify expression columns on different views, arity 2", async function () { const table = await perspective.table(common.int_float_data); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" - "x"'] + expressions: ['"w" - "x"'], }); const before = await view.to_columns(); @@ -415,21 +547,23 @@ module.exports = perspective => { const after = await view.to_columns(); const after2 = await view2.to_columns(); - expect(after['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5, 12.5, 16.5]); + expect(after['"w" + "x"']).toEqual([ + 2.5, 4.5, 6.5, 8.5, 12.5, 16.5, + ]); expect(after2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5, 8.5, 8.5]); view2.delete(); view.delete(); table.delete(); }); - it("Dependent column appends should notify equivalent expression columns on different views, arity 2", async function() { + it("Dependent column appends should notify equivalent expression columns on different views, arity 2", async function () { const table = await perspective.table(common.int_float_data); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"'] + expressions: ['"w" + "x"', '"w" - "x"'], }); const before = await view.to_columns(); @@ -442,22 +576,28 @@ module.exports = perspective => { const after = await view.to_columns(); const after2 = await view2.to_columns(); - expect(after['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5, 12.5, 16.5]); - expect(after2['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5, 12.5, 16.5]); + expect(after['"w" + "x"']).toEqual([ + 2.5, 4.5, 6.5, 8.5, 12.5, 16.5, + ]); + expect(after2['"w" + "x"']).toEqual([ + 2.5, 4.5, 6.5, 8.5, 12.5, 16.5, + ]); expect(after2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5, 8.5, 8.5]); view2.delete(); view.delete(); table.delete(); }); - it("Dependent column updates should notify expression columns on different views, arity 2.", async function() { - const table = await perspective.table(common.int_float_data, {index: "x"}); + it("Dependent column updates should notify expression columns on different views, arity 2.", async function () { + const table = await perspective.table(common.int_float_data, { + index: "x", + }); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" - "x"'] + expressions: ['"w" - "x"'], }); const before = await view.to_columns(); @@ -476,14 +616,16 @@ module.exports = perspective => { table.delete(); }); - it("Dependent column updates should notify equivalent expression columns on different views, arity 2.", async function() { - const table = await perspective.table(common.int_float_data, {index: "x"}); + it("Dependent column updates should notify equivalent expression columns on different views, arity 2.", async function () { + const table = await perspective.table(common.int_float_data, { + index: "x", + }); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"'] + expressions: ['"w" + "x"', '"w" - "x"'], }); const before = await view.to_columns(); @@ -504,14 +646,16 @@ module.exports = perspective => { table.delete(); }); - it("Dependent column update with `null` should notify expression columns on different views, arity 2.", async function() { - const table = await perspective.table(common.int_float_data, {index: "x"}); + it("Dependent column update with `null` should notify expression columns on different views, arity 2.", async function () { + const table = await perspective.table(common.int_float_data, { + index: "x", + }); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" - "x"'] + expressions: ['"w" - "x"'], }); const before = await view.to_columns(); @@ -530,14 +674,16 @@ module.exports = perspective => { table.delete(); }); - it("Dependent column update with `null` should notify equivalent expression columns on different views, arity 2.", async function() { - const table = await perspective.table(common.int_float_data, {index: "x"}); + it("Dependent column update with `null` should notify equivalent expression columns on different views, arity 2.", async function () { + const table = await perspective.table(common.int_float_data, { + index: "x", + }); const view = await table.view({ - expressions: ['"w" + "x"'] + expressions: ['"w" + "x"'], }); const view2 = await table.view({ - expressions: ['"w" + "x"', '"w" - "x"'] + expressions: ['"w" + "x"', '"w" - "x"'], }); const before = await view.to_columns(); @@ -558,17 +704,17 @@ module.exports = perspective => { table.delete(); }); - it("Updating with `null` should clear the output expression column.", async function() { + it("Updating with `null` should clear the output expression column.", async function () { const table = await perspective.table( { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], - y: [5, 6, 7, 8] + y: [5, 6, 7, 8], }, {index: "x"} ); const view = await table.view({ - expressions: ['"w" + "y"'] + expressions: ['"w" + "y"'], }); let before = await view.to_columns(); expect(before['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 12.5]); @@ -586,17 +732,17 @@ module.exports = perspective => { table.delete(); }); - it("Updating with `undefined` should not clear the output expression column.", async function() { + it("Updating with `undefined` should not clear the output expression column.", async function () { const table = await perspective.table( { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], - y: [5, 6, 7, 8] + y: [5, 6, 7, 8], }, {index: "x"} ); const view = await table.view({ - expressions: ['"w" + "y"'] + expressions: ['"w" + "y"'], }); let before = await view.to_columns(); expect(before['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 12.5]); @@ -614,17 +760,17 @@ module.exports = perspective => { table.delete(); }); - it("Updates on non-dependent columns should not change expression columns.", async function() { + it("Updates on non-dependent columns should not change expression columns.", async function () { var meta = { w: "float", x: "float", y: "string", - z: "boolean" + z: "boolean", }; const table = await perspective.table(meta, {index: "y"}); const view = await table.view({ columns: ["y", '"w" / "x"'], - expressions: ['"w" / "x"'] + expressions: ['"w" / "x"'], }); table.update(common.int_float_data); @@ -633,7 +779,7 @@ module.exports = perspective => { {y: "a", z: false}, {y: "b", z: true}, {y: "c", z: false}, - {y: "d", z: true} + {y: "d", z: true}, ]; table.update(delta_upd); let result = await view.to_json(); @@ -641,23 +787,23 @@ module.exports = perspective => { {y: "a", '"w" / "x"': 1.5}, {y: "b", '"w" / "x"': 1.25}, {y: "c", '"w" / "x"': 1.1666666666666667}, - {y: "d", '"w" / "x"': 1.125} + {y: "d", '"w" / "x"': 1.125}, ]; expect(result).toEqual(expected); view.delete(); table.delete(); }); - it("Should recompute after partial update using `__INDEX__`", async function() { + it("Should recompute after partial update using `__INDEX__`", async function () { const table = await perspective.table({x: "integer", y: "integer"}); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); table.update(data); table.update([ {__INDEX__: 0, x: 10}, - {__INDEX__: 2, x: 10} + {__INDEX__: 2, x: 10}, ]); const json = await view.to_json(); @@ -665,16 +811,16 @@ module.exports = perspective => { {x: 10, y: 2, '"x" * "y"': 20}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 10, y: 6, '"x" * "y"': 60}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); view.delete(); table.delete(); }); - it("Partial update without a new value shouldn't change computed output", async function() { + it("Partial update without a new value shouldn't change computed output", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); const json = await view.to_json(); @@ -682,7 +828,7 @@ module.exports = perspective => { {x: 1, y: 2, '"x" * "y"': 2}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 3, y: 6, '"x" * "y"': 18}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); table.update([{__INDEX__: 0, x: 1}]); @@ -692,10 +838,10 @@ module.exports = perspective => { table.delete(); }); - it("partial update on single computed source column", async function() { + it("partial update on single computed source column", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); table.update([{__INDEX__: 0, x: 10}]); @@ -704,69 +850,69 @@ module.exports = perspective => { {x: 10, y: 2, '"x" * "y"': 20}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 3, y: 6, '"x" * "y"': 18}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); view.delete(); table.delete(); }); - it("partial update on non-contiguous computed source columns", async function() { + it("partial update on non-contiguous computed source columns", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); table.update([ {__INDEX__: 0, x: 1, y: 10}, - {__INDEX__: 2, x: 3, y: 20} + {__INDEX__: 2, x: 3, y: 20}, ]); let json = await view.to_json(); expect(json).toEqual([ {x: 1, y: 10, '"x" * "y"': 10}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 3, y: 20, '"x" * "y"': 60}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); view.delete(); table.delete(); }); - it("partial update on non-contiguous computed source columns, indexed table", async function() { + it("partial update on non-contiguous computed source columns, indexed table", async function () { const table = await perspective.table(data, {index: "x"}); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); table.update([ {x: 1, y: 10}, - {x: 3, y: 20} + {x: 3, y: 20}, ]); let json = await view.to_json(); expect(json).toEqual([ {x: 1, y: 10, '"x" * "y"': 10}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 3, y: 20, '"x" * "y"': 60}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); view.delete(); table.delete(); }); - it("multiple partial update on single computed source column", async function() { + it("multiple partial update on single computed source column", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); table.update([ {__INDEX__: 0, x: 10}, - {__INDEX__: 2, x: 10} + {__INDEX__: 2, x: 10}, ]); table.update([ {__INDEX__: 0, x: 20}, - {__INDEX__: 2, x: 20} + {__INDEX__: 2, x: 20}, ]); table.update([ {__INDEX__: 0, x: 30}, - {__INDEX__: 2, x: 30} + {__INDEX__: 2, x: 30}, ]); let json = await view.to_json(); @@ -774,24 +920,24 @@ module.exports = perspective => { {x: 30, y: 2, '"x" * "y"': 60}, {x: 2, y: 4, '"x" * "y"': 8}, {x: 30, y: 6, '"x" * "y"': 180}, - {x: 4, y: 8, '"x" * "y"': 32} + {x: 4, y: 8, '"x" * "y"': 32}, ]); view.delete(); table.delete(); }); - it("multiple expression columns with updates on source columns", async function() { + it("multiple expression columns with updates on source columns", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); const view2 = await table.view({ - expressions: ['"x" + "y"'] + expressions: ['"x" + "y"'], }); table.update([ {__INDEX__: 0, x: 5}, - {__INDEX__: 2, x: 10} + {__INDEX__: 2, x: 10}, ]); let result = await view.to_columns(); @@ -800,13 +946,13 @@ module.exports = perspective => { expect(result).toEqual({ x: [5, 2, 10, 4], y: [2, 4, 6, 8], - '"x" * "y"': [10, 8, 60, 32] + '"x" * "y"': [10, 8, 60, 32], }); expect(result2).toEqual({ x: [5, 2, 10, 4], y: [2, 4, 6, 8], - '"x" + "y"': [7, 6, 16, 12] + '"x" + "y"': [7, 6, 16, 12], }); view2.delete(); @@ -814,19 +960,19 @@ module.exports = perspective => { table.delete(); }); - it("propagate updates to all expression columns", async function() { + it("propagate updates to all expression columns", async function () { const table = await perspective.table(data, {index: "x"}); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); const view2 = await table.view({ - expressions: ['"x" + "y"'] + expressions: ['"x" + "y"'], }); const view3 = await table.view({ - expressions: ['"x" - "y"'] + expressions: ['"x" - "y"'], }); table.update({x: [1, 2, 3, 4], y: [1, 2, 3, 4]}); @@ -838,19 +984,19 @@ module.exports = perspective => { expect(result).toEqual({ x: [1, 2, 3, 4], y: [1, 2, 3, 4], - '"x" * "y"': [1, 4, 9, 16] + '"x" * "y"': [1, 4, 9, 16], }); expect(result2).toEqual({ x: [1, 2, 3, 4], y: [1, 2, 3, 4], - '"x" + "y"': [2, 4, 6, 8] + '"x" + "y"': [2, 4, 6, 8], }); expect(result3).toEqual({ x: [1, 2, 3, 4], y: [1, 2, 3, 4], - '"x" - "y"': [0, 0, 0, 0] + '"x" - "y"': [0, 0, 0, 0], }); view3.delete(); @@ -859,19 +1005,19 @@ module.exports = perspective => { table.delete(); }); - it("propagate appends to all expression columns", async function() { + it("propagate appends to all expression columns", async function () { const table = await perspective.table(data); const view = await table.view({ - expressions: ['"x" * "y"'] + expressions: ['"x" * "y"'], }); const view2 = await table.view({ - expressions: ['"x" + "y"'] + expressions: ['"x" + "y"'], }); const view3 = await table.view({ - expressions: ['"x" - "y"'] + expressions: ['"x" - "y"'], }); table.update({x: [1, 2, 3, 4], y: [1, 2, 3, 4]}); @@ -883,19 +1029,19 @@ module.exports = perspective => { expect(result).toEqual({ x: [1, 2, 3, 4, 1, 2, 3, 4], y: [2, 4, 6, 8, 1, 2, 3, 4], - '"x" * "y"': [2, 8, 18, 32, 1, 4, 9, 16] + '"x" * "y"': [2, 8, 18, 32, 1, 4, 9, 16], }); expect(result2).toEqual({ x: [1, 2, 3, 4, 1, 2, 3, 4], y: [2, 4, 6, 8, 1, 2, 3, 4], - '"x" + "y"': [3, 6, 9, 12, 2, 4, 6, 8] + '"x" + "y"': [3, 6, 9, 12, 2, 4, 6, 8], }); expect(result3).toEqual({ x: [1, 2, 3, 4, 1, 2, 3, 4], y: [2, 4, 6, 8, 1, 2, 3, 4], - '"x" - "y"': [-1, -2, -3, -4, 0, 0, 0, 0] + '"x" - "y"': [-1, -2, -3, -4, 0, 0, 0, 0], }); view3.delete(); @@ -905,109 +1051,184 @@ module.exports = perspective => { }); }); - describe("Computed updates with row pivots", function() { - it("should update on dependent columns, add", async function() { + describe("Computed updates with row pivots", function () { + it("should update on dependent columns, add", async function () { const table = await perspective.table(pivot_data); const view = await table.view({ columns: ['"int" + "float"', "int"], aggregates: { - '"int" + "float"': "sum" + '"int" + "float"': "sum", }, row_pivots: ['"int" + "float"'], - expressions: ['"int" + "float"'] + expressions: ['"int" + "float"'], }); table.update({int: [4], __INDEX__: [0]}); let results = await view.to_columns({ - index: true + index: true, }); expect(results).toEqual({ __ROW_PATH__: [[], [5.5], [6.25], [7.75], [9.25]], int: [13, 2, 4, 3, 4], '"int" + "float"': [28.75, 5.5, 6.25, 7.75, 9.25], - __INDEX__: [[0, 3, 2, 1], [1], [0], [2], [3]] + __INDEX__: [[0, 3, 2, 1], [1], [0], [2], [3]], }); view.delete(); table.delete(); }); - it("should update on dependent columns, subtract", async function() { + it("should update on dependent columns, subtract", async function () { const table = await perspective.table(pivot_data); const view = await table.view({ columns: ['"int" - "float"', "int"], row_pivots: ['"int" - "float"'], - expressions: ['"int" - "float"'] + expressions: ['"int" - "float"'], }); table.update([{int: 4, __INDEX__: 0}]); let json = await view.to_json({ - index: true + index: true, }); expect(json).toEqual([ - {__ROW_PATH__: [], int: 13, '"int" - "float"': -2.75, __INDEX__: [0, 3, 1, 2]}, - {__ROW_PATH__: [-1.75], int: 3, '"int" - "float"': -1.75, __INDEX__: [2]}, - {__ROW_PATH__: [-1.5], int: 2, '"int" - "float"': -1.5, __INDEX__: [1]}, - {__ROW_PATH__: [-1.25], int: 4, '"int" - "float"': -1.25, __INDEX__: [3]}, - {__ROW_PATH__: [1.75], int: 4, '"int" - "float"': 1.75, __INDEX__: [0]} + { + __ROW_PATH__: [], + int: 13, + '"int" - "float"': -2.75, + __INDEX__: [0, 3, 1, 2], + }, + { + __ROW_PATH__: [-1.75], + int: 3, + '"int" - "float"': -1.75, + __INDEX__: [2], + }, + { + __ROW_PATH__: [-1.5], + int: 2, + '"int" - "float"': -1.5, + __INDEX__: [1], + }, + { + __ROW_PATH__: [-1.25], + int: 4, + '"int" - "float"': -1.25, + __INDEX__: [3], + }, + { + __ROW_PATH__: [1.75], + int: 4, + '"int" - "float"': 1.75, + __INDEX__: [0], + }, ]); view.delete(); table.delete(); }); - it("should update on dependent columns, multiply", async function() { + it("should update on dependent columns, multiply", async function () { const table = await perspective.table(pivot_data); const view = await table.view({ columns: ['"int" * "float"', "int"], row_pivots: ['"int" * "float"'], - expressions: ['"int" * "float"'] + expressions: ['"int" * "float"'], }); table.update([{int: 4, __INDEX__: 0}]); let json = await view.to_json({ - index: true + index: true, }); expect(json).toEqual([ - {__ROW_PATH__: [], int: 13, '"int" * "float"': 51.25, __INDEX__: [0, 3, 2, 1]}, - {__ROW_PATH__: [7], int: 2, '"int" * "float"': 7, __INDEX__: [1]}, - {__ROW_PATH__: [9], int: 4, '"int" * "float"': 9, __INDEX__: [0]}, - {__ROW_PATH__: [14.25], int: 3, '"int" * "float"': 14.25, __INDEX__: [2]}, - {__ROW_PATH__: [21], int: 4, '"int" * "float"': 21, __INDEX__: [3]} + { + __ROW_PATH__: [], + int: 13, + '"int" * "float"': 51.25, + __INDEX__: [0, 3, 2, 1], + }, + { + __ROW_PATH__: [7], + int: 2, + '"int" * "float"': 7, + __INDEX__: [1], + }, + { + __ROW_PATH__: [9], + int: 4, + '"int" * "float"': 9, + __INDEX__: [0], + }, + { + __ROW_PATH__: [14.25], + int: 3, + '"int" * "float"': 14.25, + __INDEX__: [2], + }, + { + __ROW_PATH__: [21], + int: 4, + '"int" * "float"': 21, + __INDEX__: [3], + }, ]); view.delete(); table.delete(); }); - it("should update on dependent columns, divide", async function() { + it("should update on dependent columns, divide", async function () { const table = await perspective.table(pivot_data); const view = await table.view({ columns: ['"int" / "float"', "int"], row_pivots: ['"int" / "float"'], - expressions: ['"int" / "float"'] + expressions: ['"int" / "float"'], }); table.update([{int: 4, __INDEX__: 0}]); let json = await view.to_json({ - index: true + index: true, }); expect(json).toEqual([ - {__ROW_PATH__: [], int: 13, '"int" / "float"': 3.742690058479532, __INDEX__: [0, 3, 2, 1]}, - {__ROW_PATH__: [0.5714285714285714], int: 2, '"int" / "float"': 0.5714285714285714, __INDEX__: [1]}, - {__ROW_PATH__: [0.631578947368421], int: 3, '"int" / "float"': 0.631578947368421, __INDEX__: [2]}, - {__ROW_PATH__: [0.7619047619047619], int: 4, '"int" / "float"': 0.7619047619047619, __INDEX__: [3]}, - {__ROW_PATH__: [1.7777777777777777], int: 4, '"int" / "float"': 1.7777777777777777, __INDEX__: [0]} + { + __ROW_PATH__: [], + int: 13, + '"int" / "float"': 3.742690058479532, + __INDEX__: [0, 3, 2, 1], + }, + { + __ROW_PATH__: [0.5714285714285714], + int: 2, + '"int" / "float"': 0.5714285714285714, + __INDEX__: [1], + }, + { + __ROW_PATH__: [0.631578947368421], + int: 3, + '"int" / "float"': 0.631578947368421, + __INDEX__: [2], + }, + { + __ROW_PATH__: [0.7619047619047619], + int: 4, + '"int" / "float"': 0.7619047619047619, + __INDEX__: [3], + }, + { + __ROW_PATH__: [1.7777777777777777], + int: 4, + '"int" / "float"': 1.7777777777777777, + __INDEX__: [0], + }, ]); view.delete(); @@ -1015,12 +1236,12 @@ module.exports = perspective => { }); }); - describe("Partial update with null", function() { - it("Update over null should recalculate", async function() { + describe("Partial update with null", function () { + it("Update over null should recalculate", async function () { const table = await perspective.table(pivot_data, {index: "int"}); const view = await table.view({ columns: ['"int" + "float"', "int", "float"], - expressions: ['"int" + "float"'] + expressions: ['"int" + "float"'], }); table.update([{int: 2, float: 3.5}]); @@ -1030,19 +1251,19 @@ module.exports = perspective => { expect(result).toEqual({ '"int" + "float"': [3.25, 5.5, 7.75, 9.25], int: [1, 2, 3, 4], - float: [2.25, 3.5, 4.75, 5.25] + float: [2.25, 3.5, 4.75, 5.25], }); view.delete(); table.delete(); }); - it("Update with null should unset", async function() { + it("Update with null should unset", async function () { const table = await perspective.table(pivot_data, {index: "int"}); const view = await table.view({ columns: ['"int" + "float"', "int", "float"], - expressions: ['"int" + "float"'] + expressions: ['"int" + "float"'], }); table.update([{int: 2, float: null}]); @@ -1052,26 +1273,26 @@ module.exports = perspective => { expect(result).toEqual({ '"int" + "float"': [3.25, null, 7.75, 9.25], int: [1, 2, 3, 4], - float: [2.25, null, 4.75, 5.25] + float: [2.25, null, 4.75, 5.25], }); view.delete(); table.delete(); }); - it("Undefined should be a no-op on expression columns", async function() { + it("Undefined should be a no-op on expression columns", async function () { const table = await perspective.table( [ {int: 1, float: 2.25, string: "a", datetime: new Date()}, {int: 2, float: 3.5, string: "b", datetime: new Date()}, {int: 3, float: 4.75, string: "c", datetime: new Date()}, - {int: 4, float: 5.25, string: "d", datetime: new Date()} + {int: 4, float: 5.25, string: "d", datetime: new Date()}, ], {index: "int"} ); const view = await table.view({ columns: ['"int" + "float"', "int", "float"], - expressions: ['"int" + "float"'] + expressions: ['"int" + "float"'], }); table.update([{int: 2, float: undefined}]); @@ -1081,7 +1302,7 @@ module.exports = perspective => { expect(result).toEqual({ '"int" + "float"': [3.25, 5.5, 7.75, 9.25], int: [1, 2, 3, 4], - float: [2.25, 3.5, 4.75, 5.25] + float: [2.25, 3.5, 4.75, 5.25], }); view.delete(); @@ -1094,13 +1315,13 @@ module.exports = perspective => { const table = await perspective.table( { x: [1, 2, 3, 4], - y: ["A", "B", "C", "D"] + y: ["A", "B", "C", "D"], }, {index: "x"} ); const view = await table.view({ - expressions: ['lower("y")'] + expressions: ['lower("y")'], }); const before = await view.to_columns(); diff --git a/packages/perspective/test/js/filters.js b/packages/perspective/test/js/filters.js index ed788257d0..b3a1a8dfe2 100644 --- a/packages/perspective/test/js/filters.js +++ b/packages/perspective/test/js/filters.js @@ -16,14 +16,14 @@ var data = [ {w: now, x: 1, y: "a", z: true}, {w: now, x: 2, y: "b", z: false}, {w: now, x: 3, y: "c", z: true}, - {w: yesterday, x: 4, y: "d", z: false} + {w: yesterday, x: 4, y: "d", z: false}, ]; var rdata = [ {w: +now, x: 1, y: "a", z: true}, {w: +now, x: 2, y: "b", z: false}, {w: +now, x: 3, y: "c", z: true}, - {w: +yesterday, x: 4, y: "d", z: false} + {w: +yesterday, x: 4, y: "d", z: false}, ]; // starting from 09/01/2018 to 12/01/2018 @@ -31,14 +31,14 @@ var date_range_data = [ {w: new Date(1535778060000), x: 1, y: "a", z: true}, // Sat Sep 01 2018 01:01:00 GMT-0400 {w: new Date(1538370060000), x: 2, y: "b", z: false}, // Mon Oct 01 2018 01:01:00 GMT-0400 {w: new Date(1541048460000), x: 3, y: "c", z: true}, // Thu Nov 01 2018 01:01:00 GMT-0400 - {w: new Date(1543644060000), x: 4, y: "d", z: false} // Sat Dec 01 2018 01:01:00 GMT-0500 + {w: new Date(1543644060000), x: 4, y: "d", z: false}, // Sat Dec 01 2018 01:01:00 GMT-0500 ]; var r_date_range_data = [ {w: +new Date(1535778060000), x: 1, y: "a", z: true}, {w: +new Date(1538370060000), x: 2, y: "b", z: false}, {w: +new Date(1541048460000), x: 3, y: "c", z: true}, - {w: +new Date(1543644060000), x: 4, y: "d", z: false} + {w: +new Date(1543644060000), x: 4, y: "d", z: false}, ]; const datetime_data = [ @@ -48,7 +48,7 @@ const datetime_data = [ {x: new Date(2020, 2, 8, 2, 0, 1)}, // 2020/03/8 02:00:01 GMT-0500 {x: new Date(2020, 9, 1, 15, 11, 55)}, // 2020/10/01 15:30:55 GMT-0400 {x: new Date(2020, 10, 1, 19, 29, 55)}, // 2020/11/01 19:30:55 GMT-0400 - {x: new Date(2020, 11, 31, 7, 42, 55)} // 2020/12/31 07:30:55 GMT-0500 + {x: new Date(2020, 11, 31, 7, 42, 55)}, // 2020/12/31 07:30:55 GMT-0500 ]; const datetime_data_local = [ @@ -58,21 +58,21 @@ const datetime_data_local = [ {x: new Date(2020, 2, 8, 2, 0, 1).toLocaleString()}, // 2020/03/8 02:00:01 GMT-0500 {x: new Date(2020, 9, 1, 15, 11, 55).toLocaleString()}, // 2020/10/01 15:30:55 GMT-0400 {x: new Date(2020, 10, 1, 19, 29, 55).toLocaleString()}, // 2020/11/01 19:30:55 GMT-0400 - {x: new Date(2020, 11, 31, 7, 42, 55).toLocaleString()} // 2020/12/31 07:30:55 GMT-0500 + {x: new Date(2020, 11, 31, 7, 42, 55).toLocaleString()}, // 2020/12/31 07:30:55 GMT-0500 ]; -module.exports = perspective => { - describe("Filters", function() { - describe("GT & LT", function() { - it("filters on long strings", async function() { +module.exports = (perspective) => { + describe("Filters", function () { + describe("GT & LT", function () { + it("filters on long strings", async function () { var table = await perspective.table([ {x: 1, y: "123456789012a", z: true}, {x: 2, y: "123456789012a", z: false}, {x: 3, y: "123456789012b", z: true}, - {x: 4, y: "123456789012b", z: false} + {x: 4, y: "123456789012b", z: false}, ]); var view = await table.view({ - filter: [["y", "contains", "123456789012a"]] + filter: [["y", "contains", "123456789012a"]], }); let json = await view.to_json(); expect(json.length).toEqual(2); @@ -80,10 +80,10 @@ module.exports = perspective => { table.delete(); }); - it("x > 2", async function() { + it("x > 2", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", ">", 2.0]] + filter: [["x", ">", 2.0]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(2)); @@ -91,10 +91,10 @@ module.exports = perspective => { table.delete(); }); - it("x < 3", async function() { + it("x < 3", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", "<", 3.0]] + filter: [["x", "<", 3.0]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 2)); @@ -102,10 +102,10 @@ module.exports = perspective => { table.delete(); }); - it("x > 4", async function() { + it("x > 4", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", ">", 4]] + filter: [["x", ">", 4]], }); let json = await view.to_json(); expect(json).toEqual([]); @@ -113,10 +113,10 @@ module.exports = perspective => { table.delete(); }); - it("x < 0", async function() { + it("x < 0", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", ">", 4]] + filter: [["x", ">", 4]], }); let json = await view.to_json(); expect(json).toEqual([]); @@ -124,10 +124,10 @@ module.exports = perspective => { table.delete(); }); - it("w > datetime as string", async function() { + it("w > datetime as string", async function () { var table = await perspective.table(date_range_data); var view = await table.view({ - filter: [["w", ">", "10/01/2018"]] + filter: [["w", ">", "10/01/2018"]], }); let json = await view.to_json(); expect(json).toEqual(r_date_range_data.slice(1, 4)); @@ -135,10 +135,10 @@ module.exports = perspective => { table.delete(); }); - it("w < datetime as string", async function() { + it("w < datetime as string", async function () { var table = await perspective.table(date_range_data); var view = await table.view({ - filter: [["w", "<", "10/01/2018"]] + filter: [["w", "<", "10/01/2018"]], }); let json = await view.to_json(); expect(json).toEqual([r_date_range_data[0]]); @@ -146,23 +146,23 @@ module.exports = perspective => { table.delete(); }); - describe("filtering on date column", function() { + describe("filtering on date column", function () { const schema = { - w: "date" + w: "date", }; const date_results = [ {w: +new Date(1535760000000)}, // Fri Aug 31 2018 20:00:00 GMT-0400 {w: +new Date(1538352000000)}, // Sun Sep 30 2018 20:00:00 GMT-0400 {w: +new Date(1541030400000)}, // Wed Oct 31 2018 20:00:00 GMT-0400 - {w: +new Date(1543622400000)} // Fri Nov 30 2018 19:00:00 GMT-0500 + {w: +new Date(1543622400000)}, // Fri Nov 30 2018 19:00:00 GMT-0500 ]; - it("w > date as string", async function() { + it("w > date as string", async function () { var table = await perspective.table(schema); table.update(date_results); var view = await table.view({ - filter: [["w", ">", "10/02/2018"]] + filter: [["w", ">", "10/02/2018"]], }); let json = await view.to_json(); expect(json).toEqual(date_results.slice(2, 4)); @@ -170,11 +170,11 @@ module.exports = perspective => { table.delete(); }); - it("w < date as string", async function() { + it("w < date as string", async function () { var table = await perspective.table(schema); table.update(date_results); var view = await table.view({ - filter: [["w", "<", "10/02/2018"]] + filter: [["w", "<", "10/02/2018"]], }); let json = await view.to_json(); expect(json).toEqual(date_results.slice(0, 2)); @@ -184,11 +184,11 @@ module.exports = perspective => { }); }); - describe("EQ", function() { - it("x == 1", async function() { + describe("EQ", function () { + it("x == 1", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", "==", 1]] + filter: [["x", "==", 1]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 1)); @@ -196,26 +196,26 @@ module.exports = perspective => { table.delete(); }); - it("empty, pivoted", async function() { + it("empty, pivoted", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - filter: [["x", "==", 100]] + filter: [["x", "==", 100]], }); let json = await view.to_json(); expect(json).toEqual([ // empty sum - {__ROW_PATH__: [], w: 0, x: null, y: 0, z: 0} + {__ROW_PATH__: [], w: 0, x: null, y: 0, z: 0}, ]); view.delete(); table.delete(); }); - it("x == 1, rolling updates", async function() { + it("x == 1, rolling updates", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["x"], - filter: [["x", "==", 1]] + filter: [["x", "==", 1]], }); let json = await view.to_json(); expect(json).toEqual([{x: 1}]); @@ -225,33 +225,33 @@ module.exports = perspective => { } expect(await view.to_columns()).toEqual({ - x: [1, 1, 1, 1, 1, 1] + x: [1, 1, 1, 1, 1, 1], }); table.update([{x: 2}]); expect(await view.to_columns()).toEqual({ - x: [1, 1, 1, 1, 1, 1] + x: [1, 1, 1, 1, 1, 1], }); view.delete(); table.delete(); }); - it.skip("x == 1 pivoted, rolling updates", async function() { + it.skip("x == 1 pivoted, rolling updates", async function () { var table = await perspective.table( { a: [1, 2, 3, 4], b: ["a", "b", "c", "d"], - c: ["A", "B", "C", "D"] + c: ["A", "B", "C", "D"], }, { - index: "c" + index: "c", } ); var view = await table.view({ row_pivots: ["a"], - columns: ["b", "c"] + columns: ["b", "c"], }); let out = await view.to_columns(); @@ -262,23 +262,23 @@ module.exports = perspective => { } expect(await view.to_columns()).toEqual({ - x: [1, 1, 1, 1, 1, 1] + x: [1, 1, 1, 1, 1, 1], }); table.update([{x: 2}]); expect(await view.to_columns()).toEqual({ - x: [1, 1, 1, 1, 1, 1] + x: [1, 1, 1, 1, 1, 1], }); view.delete(); table.delete(); }); - it("x == 5", async function() { + it("x == 5", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["x", "==", 5]] + filter: [["x", "==", 5]], }); let json = await view.to_json(); expect(json).toEqual([]); @@ -286,10 +286,10 @@ module.exports = perspective => { table.delete(); }); - it("y == 'a'", async function() { + it("y == 'a'", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["y", "==", "a"]] + filter: [["y", "==", "a"]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 1)); @@ -297,10 +297,10 @@ module.exports = perspective => { table.delete(); }); - it("y == 'e'", async function() { + it("y == 'e'", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["y", "==", "e"]] + filter: [["y", "==", "e"]], }); let json = await view.to_json(); expect(json).toEqual([]); @@ -308,10 +308,10 @@ module.exports = perspective => { table.delete(); }); - it("z == true", async function() { + it("z == true", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["z", "==", true]] + filter: [["z", "==", true]], }); let json = await view.to_json(); expect(json).toEqual([rdata[0], rdata[2]]); @@ -319,10 +319,10 @@ module.exports = perspective => { table.delete(); }); - it("z == false", async function() { + it("z == false", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["z", "==", false]] + filter: [["z", "==", false]], }); let json = await view.to_json(); expect(json).toEqual([rdata[1], rdata[3]]); @@ -330,10 +330,10 @@ module.exports = perspective => { table.delete(); }); - it("w == yesterday", async function() { + it("w == yesterday", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["w", "==", yesterday]] + filter: [["w", "==", yesterday]], }); let json = await view.to_json(); expect(json).toEqual([rdata[3]]); @@ -341,10 +341,10 @@ module.exports = perspective => { table.delete(); }); - it("w != yesterday", async function() { + it("w != yesterday", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["w", "!=", yesterday]] + filter: [["w", "!=", yesterday]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 3)); @@ -352,17 +352,17 @@ module.exports = perspective => { table.delete(); }); - it("w == datetime as Date() object", async function() { + it("w == datetime as Date() object", async function () { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({ - x: "datetime" + x: "datetime", }); const view = await table.view({ - filter: [["x", "==", datetime_data[0]["x"]]] + filter: [["x", "==", datetime_data[0]["x"]]], }); expect(await view.num_rows()).toBe(1); let data = await view.to_json(); - data = data.map(d => { + data = data.map((d) => { d.x = new Date(d.x); return d; }); @@ -371,17 +371,17 @@ module.exports = perspective => { await table.delete(); }); - it("w == datetime as US locale string", async function() { + it("w == datetime as US locale string", async function () { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({ - x: "datetime" + x: "datetime", }); const view = await table.view({ - filter: [["x", "==", datetime_data_local[0]["x"]]] + filter: [["x", "==", datetime_data_local[0]["x"]]], }); expect(await view.num_rows()).toBe(1); let data = await view.to_json(); - data = data.map(d => { + data = data.map((d) => { d.x = new Date(d.x); return d; }); @@ -391,11 +391,11 @@ module.exports = perspective => { }); }); - describe("in", function() { - it("y in ['a', 'b']", async function() { + describe("in", function () { + it("y in ['a', 'b']", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["y", "in", ["a", "b"]]] + filter: [["y", "in", ["a", "b"]]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 2)); @@ -404,11 +404,11 @@ module.exports = perspective => { }); }); - describe("not in", function() { - it("y not in ['d']", async function() { + describe("not in", function () { + it("y not in ['d']", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["y", "not in", ["d"]]] + filter: [["y", "not in", ["d"]]], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 3)); @@ -417,11 +417,11 @@ module.exports = perspective => { }); }); - describe("contains", function() { - it("y contains 'a'", async function() { + describe("contains", function () { + it("y contains 'a'", async function () { var table = await perspective.table(data); var view = await table.view({ - filter: [["y", "contains", "a"]] + filter: [["y", "contains", "a"]], }); let json = await view.to_json(); expect(rdata.slice(0, 1)).toEqual(json); @@ -430,14 +430,14 @@ module.exports = perspective => { }); }); - describe("multiple", function() { - it("x > 1 & x < 4", async function() { + describe("multiple", function () { + it("x > 1 & x < 4", async function () { var table = await perspective.table(data); var view = await table.view({ filter: [ ["x", ">", 1], - ["x", "<", 4] - ] + ["x", "<", 4], + ], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(1, 3)); @@ -445,15 +445,15 @@ module.exports = perspective => { table.delete(); }); - it("y contains 'a' OR y contains 'b'", async function() { + it("y contains 'a' OR y contains 'b'", async function () { var table = await perspective.table(data); // when `filter_op` is provided, perspective returns data differently. In this case, returned data should satisfy either/or of the filter conditions. var view = await table.view({ filter_op: "or", filter: [ ["y", "contains", "a"], - ["y", "contains", "b"] - ] + ["y", "contains", "b"], + ], }); let json = await view.to_json(); expect(json).toEqual(rdata.slice(0, 2)); @@ -462,8 +462,8 @@ module.exports = perspective => { }); }); - describe("is null", function() { - it("returns the correct null cells for string column", async function() { + describe("is null", function () { + it("returns the correct null cells for string column", async function () { const table = await perspective.table([ {x: 1, y: null}, {x: 2, y: null}, @@ -471,14 +471,14 @@ module.exports = perspective => { {x: 4, y: "x"}, {x: 1, y: "y"}, {x: 2, y: "x"}, - {x: 3, y: "y"} + {x: 3, y: "y"}, ]); const view = await table.view({ - filter: [["y", "is null"]] + filter: [["y", "is null"]], }); const answer = [ {x: 1, y: null}, - {x: 2, y: null} + {x: 2, y: null}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -486,7 +486,7 @@ module.exports = perspective => { table.delete(); }); - it("returns the correct null cells for integer column", async function() { + it("returns the correct null cells for integer column", async function () { const table = await perspective.table([ {x: 1, y: null}, {x: 2, y: null}, @@ -494,14 +494,14 @@ module.exports = perspective => { {x: 4, y: 2}, {x: 1, y: 3}, {x: 2, y: 4}, - {x: 3, y: 5} + {x: 3, y: 5}, ]); const view = await table.view({ - filter: [["y", "is null"]] + filter: [["y", "is null"]], }); const answer = [ {x: 1, y: null}, - {x: 2, y: null} + {x: 2, y: null}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -509,7 +509,7 @@ module.exports = perspective => { table.delete(); }); - it("returns the correct null cells for datetime column", async function() { + it("returns the correct null cells for datetime column", async function () { const table = await perspective.table([ {x: 1, y: null}, {x: 2, y: null}, @@ -517,14 +517,14 @@ module.exports = perspective => { {x: 4, y: "1/1/2019"}, {x: 1, y: "1/1/2019"}, {x: 2, y: "1/1/2019"}, - {x: 3, y: "1/1/2019"} + {x: 3, y: "1/1/2019"}, ]); const view = await table.view({ - filter: [["y", "is null"]] + filter: [["y", "is null"]], }); const answer = [ {x: 1, y: null}, - {x: 2, y: null} + {x: 2, y: null}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -533,22 +533,22 @@ module.exports = perspective => { }); }); - describe("nulls", function() { - it("x > 2", async function() { + describe("nulls", function () { + it("x > 2", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ - filter: [["x", ">", 2]] + filter: [["x", ">", 2]], }); var answer = [ {x: 3, y: 1}, - {x: 4, y: 2} + {x: 4, y: 2}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -556,17 +556,17 @@ module.exports = perspective => { table.delete(); }); - it("x < 3", async function() { + it("x < 3", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ - filter: [["x", "<", 3]] + filter: [["x", "<", 3]], }); var answer = [{x: 2, y: 1}]; let result = await view.to_json(); @@ -575,7 +575,7 @@ module.exports = perspective => { table.delete(); }); - it("x > 2", async function() { + it("x > 2", async function () { var table = await perspective.table({x: "float", y: "integer"}); table.update([ {x: 3.5, y: 1}, @@ -583,14 +583,14 @@ module.exports = perspective => { {x: null, y: 1}, {x: null, y: 1}, {x: 4.5, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ - filter: [["x", ">", 2.5]] + filter: [["x", ">", 2.5]], }); var answer = [ {x: 3.5, y: 1}, - {x: 4.5, y: 2} + {x: 4.5, y: 2}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -598,7 +598,7 @@ module.exports = perspective => { table.delete(); }); - it("x > null should be an invalid filter", async function() { + it("x > null should be an invalid filter", async function () { var table = await perspective.table({x: "float", y: "integer"}); const dataSet = [ {x: 3.5, y: 1}, @@ -606,11 +606,11 @@ module.exports = perspective => { {x: null, y: 1}, {x: null, y: 1}, {x: 4.5, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]; table.update(dataSet); var view = await table.view({ - filter: [["x", ">", null]] + filter: [["x", ">", null]], }); var answer = dataSet; let result = await view.to_json(); @@ -620,74 +620,94 @@ module.exports = perspective => { }); }); - describe("is_valid_filter", function() { - it("x == 2", async function() { + describe("is_valid_filter", function () { + it("x == 2", async function () { let table = await perspective.table(data); let isValid = await table.is_valid_filter(["x", "==", 2]); expect(isValid).toBeTruthy(); table.delete(); }); - it("x < null", async function() { + it("x < null", async function () { let table = await perspective.table(data); let isValid = await table.is_valid_filter(["x", "<", null]); expect(isValid).toBeFalsy(); table.delete(); }); - it("x > undefined", async function() { + it("x > undefined", async function () { let table = await perspective.table(data); - let isValid = await table.is_valid_filter(["x", ">", undefined]); + let isValid = await table.is_valid_filter([ + "x", + ">", + undefined, + ]); expect(isValid).toBeFalsy(); table.delete(); }); - it('x == ""', async function() { + it('x == ""', async function () { let table = await perspective.table(data); let isValid = await table.is_valid_filter(["x", "==", ""]); expect(isValid).toBeTruthy(); table.delete(); }); - it("valid date", async function() { + it("valid date", async function () { const schema = { x: "string", - y: "date" + y: "date", }; let table = await perspective.table(schema); - let isValid = await table.is_valid_filter(["y", "==", "01-01-1970"]); + let isValid = await table.is_valid_filter([ + "y", + "==", + "01-01-1970", + ]); expect(isValid).toBeTruthy(); table.delete(); }); - it("invalid date", async function() { + it("invalid date", async function () { const schema = { x: "string", - y: "date" + y: "date", }; let table = await perspective.table(schema); let isValid = await table.is_valid_filter(["y", "<", "1234"]); expect(isValid).toBeFalsy(); table.delete(); }); - it("valid datetime", async function() { + it("valid datetime", async function () { const schema = { x: "string", - y: "datetime" + y: "datetime", }; let table = await perspective.table(schema); - let isValid = await table.is_valid_filter(["y", "==", "2019-11-02 11:11:11.111"]); + let isValid = await table.is_valid_filter([ + "y", + "==", + "2019-11-02 11:11:11.111", + ]); expect(isValid).toBeTruthy(); table.delete(); }); - it("invalid datetime", async function() { + it("invalid datetime", async function () { const schema = { x: "string", - y: "datetime" + y: "datetime", }; let table = await perspective.table(schema); - let isValid = await table.is_valid_filter(["y", ">", "2019-11-02 11:11:11:111"]); + let isValid = await table.is_valid_filter([ + "y", + ">", + "2019-11-02 11:11:11:111", + ]); expect(isValid).toBeFalsy(); table.delete(); }); - it("ignores schema check if column is not in schema", async function() { + it("ignores schema check if column is not in schema", async function () { let table = await perspective.table(data); - let isValid = await table.is_valid_filter(["not a valid column", "==", 2]); + let isValid = await table.is_valid_filter([ + "not a valid column", + "==", + 2, + ]); expect(isValid).toBeTruthy(); table.delete(); }); diff --git a/packages/perspective/test/js/get_min_max.spec.js b/packages/perspective/test/js/get_min_max.spec.js index f762a4dda8..bb790796b7 100644 --- a/packages/perspective/test/js/get_min_max.spec.js +++ b/packages/perspective/test/js/get_min_max.spec.js @@ -10,15 +10,52 @@ const perspective = require("../../dist/cjs/perspective.node.js"); const data = { - w: [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, -1.5, -3.5, -1.5, -4.5, -9.5, -5.5, -8.5, -7.5], + w: [ + 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, -1.5, -3.5, -1.5, -4.5, -9.5, + -5.5, -8.5, -7.5, + ], x: [1, 2, 3, 4, 4, 3, 2, 1, 3, 4, 2, 1, 4, 3, 1, 2], - y: ["a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d"], - z: [true, false, true, false, true, false, true, false, true, true, true, true, false, false, false, false] + y: [ + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + ], + z: [ + true, + false, + true, + false, + true, + false, + true, + false, + true, + true, + true, + true, + false, + false, + false, + false, + ], }; -describe("get_min_max", function() { - describe("0 sided", function() { - it("float column", async function() { +describe("get_min_max", function () { + describe("0 sided", function () { + it("float column", async function () { var table = await perspective.table(data); var view = await table.view({}); const range = await view.get_min_max("w"); @@ -27,7 +64,7 @@ describe("get_min_max", function() { table.delete(); }); - it("int column", async function() { + it("int column", async function () { var table = await perspective.table(data); var view = await table.view({}); const range = await view.get_min_max("x"); @@ -36,7 +73,7 @@ describe("get_min_max", function() { table.delete(); }); - it("string column", async function() { + it("string column", async function () { var table = await perspective.table(data); var view = await table.view({}); const range = await view.get_min_max("y"); @@ -46,11 +83,11 @@ describe("get_min_max", function() { }); }); - describe("1 sided", function() { - it("float col", async function() { + describe("1 sided", function () { + it("float col", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.get_min_max("w"); expect(cols).toEqual([-4, 1]); @@ -58,10 +95,10 @@ describe("get_min_max", function() { table.delete(); }); - it("int col", async function() { + it("int col", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.get_min_max("x"); expect(cols).toEqual([8, 12]); @@ -69,10 +106,10 @@ describe("get_min_max", function() { table.delete(); }); - it("string col", async function() { + it("string col", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.get_min_max("y"); expect(cols).toEqual([4, 4]); @@ -81,12 +118,12 @@ describe("get_min_max", function() { }); }); - describe("2 sided", function() { - it("float col", async function() { + describe("2 sided", function () { + it("float col", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("w"); expect(cols).toEqual([-9.5, 9.5]); @@ -94,11 +131,11 @@ describe("get_min_max", function() { table.delete(); }); - it("int column", async function() { + it("int column", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("x"); expect(cols).toEqual([1, 8]); @@ -106,11 +143,11 @@ describe("get_min_max", function() { table.delete(); }); - it("string column", async function() { + it("string column", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("y"); expect(cols).toEqual([1, 3]); @@ -119,11 +156,11 @@ describe("get_min_max", function() { }); }); - describe("column only", function() { - it("float col", async function() { + describe("column only", function () { + it("float col", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("w"); expect(cols).toEqual([-9.5, 8.5]); @@ -131,10 +168,10 @@ describe("get_min_max", function() { table.delete(); }); - it("int col", async function() { + it("int col", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("x"); expect(cols).toEqual([1, 4]); @@ -142,10 +179,10 @@ describe("get_min_max", function() { table.delete(); }); - it("string col", async function() { + it("string col", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.get_min_max("y"); expect(cols).toEqual(["a", "d"]); diff --git a/packages/perspective/test/js/internal.js b/packages/perspective/test/js/internal.js index ca4ddad0fd..d5b5269581 100644 --- a/packages/perspective/test/js/internal.js +++ b/packages/perspective/test/js/internal.js @@ -8,19 +8,23 @@ */ const test_null_arrow = require("./test_arrows.js").test_null_arrow; -const arrow_psp_internal_schema = [9, 10, 1, 2, 3, 4, 11, 19, 19, 12, 12, 12, 2]; +const arrow_psp_internal_schema = [ + 9, 10, 1, 2, 3, 4, 11, 19, 19, 12, 12, 12, 2, +]; module.exports = (perspective, mode) => { - describe("Internal API", function() { - it("is actually using the correct runtime", async function() { + describe("Internal API", function () { + it("is actually using the correct runtime", async function () { // Get the internal module; if (perspective.sync_module) { perspective = perspective.sync_module(); } - expect(perspective.__module__.wasmJSMethod).toEqual(mode === "ASMJS" ? "asmjs" : "native-wasm"); + expect(perspective.__module__.wasmJSMethod).toEqual( + mode === "ASMJS" ? "asmjs" : "native-wasm" + ); }); - it("Arrow schema types are mapped correctly", async function() { + it("Arrow schema types are mapped correctly", async function () { // This only works for non parallel if (perspective.sync_module) { perspective = perspective.sync_module(); diff --git a/packages/perspective/test/js/leaks.spec.js b/packages/perspective/test/js/leaks.spec.js index 7f12edce52..497ec8c5c7 100644 --- a/packages/perspective/test/js/leaks.spec.js +++ b/packages/perspective/test/js/leaks.spec.js @@ -11,7 +11,12 @@ const perspective = require("../../dist/cjs/perspective.node.js"); const fs = require("fs"); const path = require("path"); -const arr = fs.readFileSync(path.join(__dirname, "../../../../node_modules/superstore-arrow/superstore.arrow")).buffer; +const arr = fs.readFileSync( + path.join( + __dirname, + "../../../../node_modules/superstore-arrow/superstore.arrow" + ) +).buffer; /** * Run a function in a loop, comparing before-and-after wasm heap for leaks. @@ -44,7 +49,9 @@ function generate_expressions() { const expressions = ["concat('abcd', \"c\", 'efg')"]; for (const op of ["+", "-", "*", "/", "^", "%"]) { - expressions.push(`("a" ${op} "b") + ${Math.floor(Math.random() * 100)}`); + expressions.push( + `("a" ${op} "b") + ${Math.floor(Math.random() * 100)}` + ); } for (const fn of ["sqrt", "log10", "deg2rad"]) { @@ -62,13 +69,13 @@ function generate_expressions() { return expressions; } -describe("leaks", function() { - describe("view", function() { - describe("1-sided", function() { +describe("leaks", function () { + describe("view", function () { + describe("1-sided", function () { it("to_json does not leak", async () => { const table = await perspective.table(arr.slice()); const view = await table.view({row_pivots: ["State"]}); - await leak_test(async function() { + await leak_test(async function () { let json = await view.to_json(); expect(json.length).toEqual(50); }); @@ -78,16 +85,19 @@ describe("leaks", function() { }); }); - describe("table", function() { + describe("table", function () { it("update does not leak", async () => { - const table = await perspective.table({x: "integer", y: "string"}, {index: "x"}); + const table = await perspective.table( + {x: "integer", y: "string"}, + {index: "x"} + ); let count = 0; const view = await table.view(); - view.on_update(function() { + view.on_update(function () { count += 1; }); - await leak_test(async function() { + await leak_test(async function () { await table.update([{x: 1, y: "TestTestTest"}]); expect(await table.size()).toEqual(1); }); @@ -98,20 +108,24 @@ describe("leaks", function() { }); }); - describe("expression columns", function() { + describe("expression columns", function () { it("0 sided does not leak", async () => { const table = await perspective.table({ a: [1, 2, 3, 4], b: [1.5, 2.5, 3.5, 4.5], c: ["a", "b", "c", "d"], - d: [new Date(), new Date(), new Date(), new Date()] + d: [new Date(), new Date(), new Date(), new Date()], }); const expressions = generate_expressions(); await leak_test(async () => { const view = await table.view({ - expressions: [expressions[Math.floor(Math.random() * expressions.length)]] + expressions: [ + expressions[ + Math.floor(Math.random() * expressions.length) + ], + ], }); const expression_schema = await view.expression_schema(); expect(Object.keys(expression_schema).length).toEqual(1); @@ -126,7 +140,7 @@ describe("leaks", function() { a: [1, 2, 3, 4], b: [1.5, 2.5, 3.5, 4.5], c: ["a", "b", "c", "d"], - d: [new Date(), new Date(), new Date(), new Date()] + d: [new Date(), new Date(), new Date(), new Date()], }); const columns = ["a", "b", "c", "d"]; @@ -134,8 +148,14 @@ describe("leaks", function() { await leak_test(async () => { const view = await table.view({ - row_pivots: [columns[Math.floor(Math.random() * columns.length)]], - expressions: [expressions[Math.floor(Math.random() * expressions.length)]] + row_pivots: [ + columns[Math.floor(Math.random() * columns.length)], + ], + expressions: [ + expressions[ + Math.floor(Math.random() * expressions.length) + ], + ], }); const expression_schema = await view.expression_schema(); expect(Object.keys(expression_schema).length).toEqual(1); @@ -150,7 +170,7 @@ describe("leaks", function() { a: [1, 2, 3, 4], b: [1.5, 2.5, 3.5, 4.5], c: ["a", "b", "c", "d"], - d: [new Date(), new Date(), new Date(), new Date()] + d: [new Date(), new Date(), new Date(), new Date()], }); const columns = ["a", "b", "c", "d"]; @@ -158,9 +178,17 @@ describe("leaks", function() { await leak_test(async () => { const view = await table.view({ - row_pivots: [columns[Math.floor(Math.random() * columns.length)]], - column_pivots: [columns[Math.floor(Math.random() * columns.length)]], - expressions: [expressions[Math.floor(Math.random() * expressions.length)]] + row_pivots: [ + columns[Math.floor(Math.random() * columns.length)], + ], + column_pivots: [ + columns[Math.floor(Math.random() * columns.length)], + ], + expressions: [ + expressions[ + Math.floor(Math.random() * expressions.length) + ], + ], }); const expression_schema = await view.expression_schema(); expect(Object.keys(expression_schema).length).toEqual(1); diff --git a/packages/perspective/test/js/multiple.js b/packages/perspective/test/js/multiple.js index 1e7bb227cc..14d1406d11 100644 --- a/packages/perspective/test/js/multiple.js +++ b/packages/perspective/test/js/multiple.js @@ -14,18 +14,73 @@ const arrow = fs.readFileSync(path.join(__dirname, "..", "arrow", "test.arrow")) */ var arrow_result = [ - {f32: 1.5, f64: 1.5, i64: 1, i32: 1, i16: 1, i8: 1, bool: true, char: "a", dict: "a", datetime: +new Date("2018-01-25")}, - {f32: 2.5, f64: 2.5, i64: 2, i32: 2, i16: 2, i8: 2, bool: false, char: "b", dict: "b", datetime: +new Date("2018-01-26")}, - {f32: 3.5, f64: 3.5, i64: 3, i32: 3, i16: 3, i8: 3, bool: true, char: "c", dict: "c", datetime: +new Date("2018-01-27")}, - {f32: 4.5, f64: 4.5, i64: 4, i32: 4, i16: 4, i8: 4, bool: false, char: "d", dict: "d", datetime: +new Date("2018-01-28")}, - {f32: 5.5, f64: 5.5, i64: 5, i32: 5, i16: 5, i8: 5, bool: true, char: "d", dict: "d", datetime: +new Date("2018-01-29")} + { + f32: 1.5, + f64: 1.5, + i64: 1, + i32: 1, + i16: 1, + i8: 1, + bool: true, + char: "a", + dict: "a", + datetime: +new Date("2018-01-25"), + }, + { + f32: 2.5, + f64: 2.5, + i64: 2, + i32: 2, + i16: 2, + i8: 2, + bool: false, + char: "b", + dict: "b", + datetime: +new Date("2018-01-26"), + }, + { + f32: 3.5, + f64: 3.5, + i64: 3, + i32: 3, + i16: 3, + i8: 3, + bool: true, + char: "c", + dict: "c", + datetime: +new Date("2018-01-27"), + }, + { + f32: 4.5, + f64: 4.5, + i64: 4, + i32: 4, + i16: 4, + i8: 4, + bool: false, + char: "d", + dict: "d", + datetime: +new Date("2018-01-28"), + }, + { + f32: 5.5, + f64: 5.5, + i64: 5, + i32: 5, + i16: 5, + i8: 5, + bool: true, + char: "d", + dict: "d", + datetime: +new Date("2018-01-29"), + }, ]; const _ = require("underscore"); -module.exports = perspective => { - describe("Multiple Perspectives", function() { - it("Constructs table using data generated by to_arrow()", async function() { +module.exports = (perspective) => { + describe("Multiple Perspectives", function () { + it("Constructs table using data generated by to_arrow()", async function () { let table = await perspective.table(arrow_result); let view = await table.view(); let result = await view.to_arrow(); @@ -42,18 +97,18 @@ module.exports = perspective => { table2.delete(); }); - it("Constructs table using data in random column order generated by to_arrow()", async function() { + it("Constructs table using data in random column order generated by to_arrow()", async function () { let table = await perspective.table(arrow_result); let columns = _.shuffle(await table.columns()); let view = await table.view({ - columns: columns + columns: columns, }); let result = await view.to_arrow(); let table2 = await perspective.table(result); let columns2 = _.shuffle(await table2.columns()); let view2 = await table2.view({ - columns: columns2 + columns: columns2, }); let result2 = await view2.to_json(); diff --git a/packages/perspective/test/js/perspective.spec.js b/packages/perspective/test/js/perspective.spec.js index 2842e676c0..2f1ab2b901 100644 --- a/packages/perspective/test/js/perspective.spec.js +++ b/packages/perspective/test/js/perspective.spec.js @@ -10,7 +10,7 @@ const node_perspective = require("../../dist/cjs/perspective.node.js"); const RUNTIMES = { - NODE: node_perspective + NODE: node_perspective, }; const clear_tests = require("./clear.js"); @@ -28,9 +28,11 @@ const expression_tests = require("./expressions.js"); const delete_tests = require("./delete.js"); const port_tests = require("./ports.js"); -describe("perspective.js", function() { - Object.keys(RUNTIMES).forEach(function(mode) { - (typeof WebAssembly === "undefined" && mode === "WASM" ? xdescribe : describe)(mode, function() { +describe("perspective.js", function () { + Object.keys(RUNTIMES).forEach(function (mode) { + (typeof WebAssembly === "undefined" && mode === "WASM" + ? xdescribe + : describe)(mode, function () { clear_tests(RUNTIMES[mode]); constructor_tests(RUNTIMES[mode]); pivot_tests(RUNTIMES[mode]); diff --git a/packages/perspective/test/js/pivot_nulls.js b/packages/perspective/test/js/pivot_nulls.js index 4ed40b0399..c4e4748b34 100644 --- a/packages/perspective/test/js/pivot_nulls.js +++ b/packages/perspective/test/js/pivot_nulls.js @@ -7,22 +7,25 @@ * */ -module.exports = perspective => { - describe("Pivotting with nulls", function() { - describe("last aggregate", function() { - it("preserves null when it is the last element in a leaf", async function() { - const DATA = {a: ["a", "a", "a", "b", "b", "b", "c", "c", "c"], b: [1, 2, null, 3, 4, 5, null, null, null]}; +module.exports = (perspective) => { + describe("Pivotting with nulls", function () { + describe("last aggregate", function () { + it("preserves null when it is the last element in a leaf", async function () { + const DATA = { + a: ["a", "a", "a", "b", "b", "b", "c", "c", "c"], + b: [1, 2, null, 3, 4, 5, null, null, null], + }; var table = await perspective.table(DATA); var view = await table.view({ row_pivots: ["a"], columns: ["b"], - aggregates: {b: "last"} + aggregates: {b: "last"}, }); var answer = [ {__ROW_PATH__: [], b: null}, {__ROW_PATH__: ["a"], b: null}, {__ROW_PATH__: ["b"], b: 5}, - {__ROW_PATH__: ["c"], b: null} + {__ROW_PATH__: ["c"], b: null}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -30,17 +33,74 @@ module.exports = perspective => { table.delete(); }); - it("preserves null when it is the last element in a leaf under 2 levels", async function() { + it("preserves null when it is the last element in a leaf under 2 levels", async function () { const DATA = { - a: ["a", "a", "a", "b", "b", "b", "c", "c", "c", "a", "a", "a", "b", "b", "b", "c", "c", "c"], - b: [1, 2, null, 3, 4, 5, null, null, null, 1, 2, null, null, null, null, 3, 4, 5], - c: ["x", "x", "x", "x", "x", "x", "x", "x", "x", "y", "y", "y", "y", "y", "y", "y", "y", "y"] + a: [ + "a", + "a", + "a", + "b", + "b", + "b", + "c", + "c", + "c", + "a", + "a", + "a", + "b", + "b", + "b", + "c", + "c", + "c", + ], + b: [ + 1, + 2, + null, + 3, + 4, + 5, + null, + null, + null, + 1, + 2, + null, + null, + null, + null, + 3, + 4, + 5, + ], + c: [ + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + ], }; var table = await perspective.table(DATA); var view = await table.view({ row_pivots: ["c", "a"], columns: ["b"], - aggregates: {b: "last"} + aggregates: {b: "last"}, }); var answer = [ {__ROW_PATH__: [], b: 5}, @@ -51,7 +111,7 @@ module.exports = perspective => { {__ROW_PATH__: ["y"], b: 5}, {__ROW_PATH__: ["y", "a"], b: null}, {__ROW_PATH__: ["y", "b"], b: null}, - {__ROW_PATH__: ["y", "c"], b: 5} + {__ROW_PATH__: ["y", "c"], b: 5}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -59,17 +119,74 @@ module.exports = perspective => { table.delete(); }); - it("preserves null when it is the last element in a leaf under 2 levels when grand total is null", async function() { + it("preserves null when it is the last element in a leaf under 2 levels when grand total is null", async function () { const DATA = { - a: ["a", "a", "a", "b", "b", "b", "c", "c", "c", "a", "a", "a", "b", "b", "b", "c", "c", "c"], - b: [1, 2, null, null, null, null, 3, 4, 5, 1, 2, null, 3, 4, 5, null, null, null], - c: ["x", "x", "x", "x", "x", "x", "x", "x", "x", "y", "y", "y", "y", "y", "y", "y", "y", "y"] + a: [ + "a", + "a", + "a", + "b", + "b", + "b", + "c", + "c", + "c", + "a", + "a", + "a", + "b", + "b", + "b", + "c", + "c", + "c", + ], + b: [ + 1, + 2, + null, + null, + null, + null, + 3, + 4, + 5, + 1, + 2, + null, + 3, + 4, + 5, + null, + null, + null, + ], + c: [ + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "x", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + "y", + ], }; var table = await perspective.table(DATA); var view = await table.view({ row_pivots: ["c", "a"], columns: ["b"], - aggregates: {b: "last"} + aggregates: {b: "last"}, }); var answer = [ {__ROW_PATH__: [], b: null}, @@ -80,7 +197,7 @@ module.exports = perspective => { {__ROW_PATH__: ["y"], b: null}, {__ROW_PATH__: ["y", "a"], b: null}, {__ROW_PATH__: ["y", "b"], b: 5}, - {__ROW_PATH__: ["y", "c"], b: null} + {__ROW_PATH__: ["y", "c"], b: null}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -89,26 +206,26 @@ module.exports = perspective => { }); }); - it("shows one pivot for the nulls on initial load", async function() { + it("shows one pivot for the nulls on initial load", async function () { const dataWithNulls = [ {name: "Homer", value: 1}, {name: null, value: 1}, {name: null, value: 1}, - {name: "Krusty", value: 1} + {name: "Krusty", value: 1}, ]; var table = await perspective.table(dataWithNulls); var view = await table.view({ row_pivots: ["name"], - aggregates: {name: "distinct count"} + aggregates: {name: "distinct count"}, }); const answer = [ {__ROW_PATH__: [], name: 3, value: 4}, {__ROW_PATH__: [null], name: 1, value: 2}, {__ROW_PATH__: ["Homer"], name: 1, value: 1}, - {__ROW_PATH__: ["Krusty"], name: 1, value: 1} + {__ROW_PATH__: ["Krusty"], name: 1, value: 1}, ]; let results = await view.to_json(); @@ -117,14 +234,14 @@ module.exports = perspective => { table.delete(); }); - it("shows one pivot for the nulls after updating with a null", async function() { + it("shows one pivot for the nulls after updating with a null", async function () { const dataWithNull1 = [ {name: "Homer", value: 1}, - {name: null, value: 1} + {name: null, value: 1}, ]; const dataWithNull2 = [ {name: null, value: 1}, - {name: "Krusty", value: 1} + {name: "Krusty", value: 1}, ]; var table = await perspective.table(dataWithNull1); @@ -132,14 +249,14 @@ module.exports = perspective => { var view = await table.view({ row_pivots: ["name"], - aggregates: {name: "distinct count"} + aggregates: {name: "distinct count"}, }); const answer = [ {__ROW_PATH__: [], name: 3, value: 4}, {__ROW_PATH__: [null], name: 1, value: 2}, {__ROW_PATH__: ["Homer"], name: 1, value: 1}, - {__ROW_PATH__: ["Krusty"], name: 1, value: 1} + {__ROW_PATH__: ["Krusty"], name: 1, value: 1}, ]; let results = await view.to_json(); @@ -148,25 +265,25 @@ module.exports = perspective => { table.delete(); }); - it("aggregates that return NaN render correctly", async function() { + it("aggregates that return NaN render correctly", async function () { const dataWithNull1 = [ {name: "Homer", value: 3}, {name: "Homer", value: 1}, {name: "Marge", value: null}, - {name: "Marge", value: null} + {name: "Marge", value: null}, ]; var table = await perspective.table(dataWithNull1); var view = await table.view({ row_pivots: ["name"], - aggregates: {value: "avg"} + aggregates: {value: "avg"}, }); const answer = [ {__ROW_PATH__: [], name: 4, value: 2}, {__ROW_PATH__: ["Homer"], name: 2, value: 2}, - {__ROW_PATH__: ["Marge"], name: 2, value: null} + {__ROW_PATH__: ["Marge"], name: 2, value: null}, ]; let results = await view.to_json(); @@ -175,8 +292,17 @@ module.exports = perspective => { table.delete(); }); - it("aggregates nulls correctly", async function() { - const data = [{x: "AAAAAAAAAAAAAA"}, {x: "AAAAAAAAAAAAAA"}, {x: "AAAAAAAAAAAAAA"}, {x: null}, {x: null}, {x: "BBBBBBBBBBBBBB"}, {x: "BBBBBBBBBBBBBB"}, {x: "BBBBBBBBBBBBBB"}]; + it("aggregates nulls correctly", async function () { + const data = [ + {x: "AAAAAAAAAAAAAA"}, + {x: "AAAAAAAAAAAAAA"}, + {x: "AAAAAAAAAAAAAA"}, + {x: null}, + {x: null}, + {x: "BBBBBBBBBBBBBB"}, + {x: "BBBBBBBBBBBBBB"}, + {x: "BBBBBBBBBBBBBB"}, + ]; const tbl = await perspective.table(data); const view = await tbl.view({row_pivots: ["x"]}); @@ -184,20 +310,20 @@ module.exports = perspective => { expect(result).toEqual([ { __ROW_PATH__: [], - x: 8 + x: 8, }, { __ROW_PATH__: [null], - x: 2 + x: 2, }, { __ROW_PATH__: ["AAAAAAAAAAAAAA"], - x: 3 + x: 3, }, { __ROW_PATH__: ["BBBBBBBBBBBBBB"], - x: 3 - } + x: 3, + }, ]); }); }); diff --git a/packages/perspective/test/js/pivots.js b/packages/perspective/test/js/pivots.js index c1f55141ac..242e38a7eb 100644 --- a/packages/perspective/test/js/pivots.js +++ b/packages/perspective/test/js/pivots.js @@ -11,63 +11,73 @@ var data = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var meta = { x: "integer", y: "string", - z: "boolean" + z: "boolean", }; var data2 = [ {x: 1, y: 1, z: true}, {x: 2, y: 1, z: false}, {x: 3, y: 2, z: true}, - {x: 4, y: 2, z: false} + {x: 4, y: 2, z: false}, ]; var data_7 = { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }; var data_8 = { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [new Date(1555126035065), new Date(1555126035065), new Date(1555026035065), new Date(1555026035065)] + z: [ + new Date(1555126035065), + new Date(1555126035065), + new Date(1555026035065), + new Date(1555026035065), + ], }; const float_data = { x: [ - 0.99098243, - 0.36677191, - 0.58926465, - 0.95701263, - 0.96904283, - 0.50398721, - 0.67424934, - 0.32015379, - 0.14877031, - 0.16285932, - 0.00597484, - 0.90579728, - 0.01338397, - 0.66893083, - 0.79209796, - 0.41033256, - 0.92328448, - 0.20791236, - 0.14874502, - 0.1727802 + 0.99098243, 0.36677191, 0.58926465, 0.95701263, 0.96904283, 0.50398721, + 0.67424934, 0.32015379, 0.14877031, 0.16285932, 0.00597484, 0.90579728, + 0.01338397, 0.66893083, 0.79209796, 0.41033256, 0.92328448, 0.20791236, + 0.14874502, 0.1727802, + ], + y: [ + "a", + "b", + "c", + "d", + "e", + "a", + "b", + "c", + "d", + "e", + "a", + "b", + "c", + "d", + "e", + "a", + "b", + "c", + "d", + "e", ], - y: ["a", "b", "c", "d", "e", "a", "b", "c", "d", "e", "a", "b", "c", "d", "e", "a", "b", "c", "d", "e"] }; -const variance = nums => { +const variance = (nums) => { let k = 0, m = 0, s = 0; @@ -82,22 +92,22 @@ const variance = nums => { return s / nums.length; }; -const std = nums => { +const std = (nums) => { return Math.sqrt(variance(nums)); }; -module.exports = perspective => { - describe("Aggregate", function() { - it("old `aggregate` syntax is backwards compatible", async function() { +module.exports = (perspective) => { + describe("Aggregate", function () { + it("old `aggregate` syntax is backwards compatible", async function () { var table = await perspective.table(data); var view = await table.view({ aggregate: [{column: "x", op: "sum"}], - row_pivots: ["z"] + row_pivots: ["z"], }); var answer = [ {__ROW_PATH__: [], x: 10}, {__ROW_PATH__: [false], x: 6}, - {__ROW_PATH__: [true], x: 4} + {__ROW_PATH__: [true], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -105,22 +115,22 @@ module.exports = perspective => { table.delete(); }); - it("Aggregates are processed in the order of the columns array", async function() { + it("Aggregates are processed in the order of the columns array", async function () { const table = await perspective.table(data); const view = await table.view({ row_pivots: ["z"], columns: ["y", "z"], aggregates: { z: "last", - y: "last" - } + y: "last", + }, }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "y", "z"]); const answer = [ {__ROW_PATH__: [], y: "c", z: true}, {__ROW_PATH__: [false], y: "d", z: false}, - {__ROW_PATH__: [true], y: "c", z: true} + {__ROW_PATH__: [true], y: "c", z: true}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -128,21 +138,21 @@ module.exports = perspective => { table.delete(); }); - it("Aggregates are not in columns are ignored", async function() { + it("Aggregates are not in columns are ignored", async function () { const table = await perspective.table(data); const view = await table.view({ row_pivots: ["z"], columns: ["y", "z"], aggregates: { - x: "count" - } + x: "count", + }, }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "y", "z"]); const answer = [ {__ROW_PATH__: [], y: 4, z: 4}, {__ROW_PATH__: [false], y: 2, z: 2}, - {__ROW_PATH__: [true], y: 2, z: 2} + {__ROW_PATH__: [true], y: 2, z: 2}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -150,16 +160,16 @@ module.exports = perspective => { table.delete(); }); - it("['z'], sum", async function() { + it("['z'], sum", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], - columns: ["x"] + columns: ["x"], }); var answer = [ {__ROW_PATH__: [], x: 10}, {__ROW_PATH__: [false], x: 6}, - {__ROW_PATH__: [true], x: 4} + {__ROW_PATH__: [true], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -167,17 +177,17 @@ module.exports = perspective => { table.delete(); }); - it("['z'], weighted mean", async function() { + it("['z'], weighted mean", async function () { var table = await perspective.table(data2); var view = await table.view({ row_pivots: ["z"], aggregates: {x: ["weighted mean", "y"]}, - columns: ["x"] + columns: ["x"], }); var answer = [ {__ROW_PATH__: [], x: 2.8333333333333335}, {__ROW_PATH__: [false], x: 3.3333333333333335}, - {__ROW_PATH__: [true], x: 2.3333333333333335} + {__ROW_PATH__: [true], x: 2.3333333333333335}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -185,23 +195,23 @@ module.exports = perspective => { table.delete(); }); - it("['z'], weighted mean on a table created from schema should return valid values after update", async function() { + it("['z'], weighted mean on a table created from schema should return valid values after update", async function () { const table = await perspective.table({ x: "integer", y: "integer", - z: "boolean" + z: "boolean", }); const view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: ["weighted mean", "y"]} + aggregates: {x: ["weighted mean", "y"]}, }); const answer = [ {__ROW_PATH__: [], x: 2.8333333333333335}, {__ROW_PATH__: [false], x: 3.3333333333333335}, - {__ROW_PATH__: [true], x: 2.3333333333333335} + {__ROW_PATH__: [true], x: 2.3333333333333335}, ]; table.update(data2); @@ -212,17 +222,17 @@ module.exports = perspective => { table.delete(); }); - it("['z'], mean", async function() { + it("['z'], mean", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 2.5}, {__ROW_PATH__: [false], x: 3}, - {__ROW_PATH__: [true], x: 2} + {__ROW_PATH__: [true], x: 2}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -230,21 +240,21 @@ module.exports = perspective => { table.delete(); }); - it("['z'], mean on a table created from schema should return valid values after update", async function() { + it("['z'], mean on a table created from schema should return valid values after update", async function () { const table = await perspective.table({ x: "integer", y: "string", - z: "boolean" + z: "boolean", }); const view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); const answer = [ {__ROW_PATH__: [], x: 2.5}, {__ROW_PATH__: [false], x: 3}, - {__ROW_PATH__: [true], x: 2} + {__ROW_PATH__: [true], x: 2}, ]; table.update(data); @@ -255,17 +265,17 @@ module.exports = perspective => { table.delete(); }); - it("['z'], first by index", async function() { + it("['z'], first by index", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "first by index"} + aggregates: {x: "first by index"}, }); var answer = [ {__ROW_PATH__: [], x: 1}, {__ROW_PATH__: [false], x: 2}, - {__ROW_PATH__: [true], x: 1} + {__ROW_PATH__: [true], x: 1}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -273,17 +283,17 @@ module.exports = perspective => { table.delete(); }); - it("['z'], join", async function() { + it("['z'], join", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "join"} + aggregates: {x: "join"}, }); var answer = [ {__ROW_PATH__: [], x: "1, 2, 3, 4"}, {__ROW_PATH__: [false], x: "2, 4"}, - {__ROW_PATH__: [true], x: "1, 3"} + {__ROW_PATH__: [true], x: "1, 3"}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -291,19 +301,23 @@ module.exports = perspective => { table.delete(); }); - it("['z'], join exceeds max join size", async function() { + it("['z'], join exceeds max join size", async function () { let data2 = JSON.parse(JSON.stringify(data)); - data2.push({x: 5, y: "abcdefghijklmnopqrstuvwxyz".repeat(12), z: false}); + data2.push({ + x: 5, + y: "abcdefghijklmnopqrstuvwxyz".repeat(12), + z: false, + }); var table = await perspective.table(data2); var view = await table.view({ row_pivots: ["z"], columns: ["y"], - aggregates: {y: "join"} + aggregates: {y: "join"}, }); var answer = [ {__ROW_PATH__: [], y: "a"}, {__ROW_PATH__: [false], y: ""}, - {__ROW_PATH__: [true], y: "a, c"} + {__ROW_PATH__: [true], y: "a, c"}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -311,22 +325,22 @@ module.exports = perspective => { table.delete(); }); - it("['z'], first by index with appends", async function() { + it("['z'], first by index with appends", async function () { var table = await perspective.table(data, {index: "y"}); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "first by index"} + aggregates: {x: "first by index"}, }); const answer = [ {__ROW_PATH__: [], x: 1}, {__ROW_PATH__: [false], x: 2}, - {__ROW_PATH__: [true], x: 1} + {__ROW_PATH__: [true], x: 1}, ]; table.update({ x: [5], y: ["e"], - z: [true] + z: [true], }); const result = await view.to_json(); expect(result).toEqual(answer); @@ -334,22 +348,22 @@ module.exports = perspective => { table.delete(); }); - it("['z'], first by index with partial updates", async function() { + it("['z'], first by index with partial updates", async function () { var table = await perspective.table(data, {index: "y"}); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "first by index"} + aggregates: {x: "first by index"}, }); const answer = [ {__ROW_PATH__: [], x: 5}, {__ROW_PATH__: [false], x: 2}, - {__ROW_PATH__: [true], x: 5} + {__ROW_PATH__: [true], x: 5}, ]; table.update({ x: [5], y: ["a"], - z: [true] + z: [true], }); const result = await view.to_json(); expect(result).toEqual(answer); @@ -362,7 +376,7 @@ module.exports = perspective => { { x: ["a"], y: ["A"], - index: [1] + index: [1], }, {index: "index"} ); @@ -370,22 +384,22 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y"] + row_pivots: ["y"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 5; i++) { table.update({ x: ["a"], - index: [1] + index: [1], }); } @@ -395,7 +409,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: ["A"], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -411,7 +425,7 @@ module.exports = perspective => { { x: ["a"], y: [100], - index: [1] + index: [1], }, {index: "index"} ); @@ -419,23 +433,23 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "count", - y: "high" + y: "high", }, - row_pivots: ["x"] + row_pivots: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["a"]], x: [1, 1], y: [100, 100], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 5; i++) { table.update({ x: ["a"], y: [100], - index: [1] + index: [1], }); } @@ -445,7 +459,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: [101], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -461,7 +475,7 @@ module.exports = perspective => { { x: ["a"], y: [100], - index: [1] + index: [1], }, {index: "index"} ); @@ -469,23 +483,23 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "count", - y: "low" + y: "low", }, - row_pivots: ["x"] + row_pivots: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["a"]], x: [1, 1], y: [100, 100], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 5; i++) { table.update({ x: ["a"], y: [100], - index: [1] + index: [1], }); } @@ -495,7 +509,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: [101], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -505,7 +519,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: [99], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -521,7 +535,7 @@ module.exports = perspective => { { x: ["a"], y: [100], - index: [1] + index: [1], }, {index: "index"} ); @@ -529,23 +543,23 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "count", - y: "low" + y: "low", }, - row_pivots: ["x"] + row_pivots: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["a"]], x: [1, 1], y: [100, 100], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 100; i++) { table.update({ x: ["a"], y: [99], - index: [1] + index: [1], }); } @@ -554,7 +568,7 @@ module.exports = perspective => { table.update({ y: [101], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -568,7 +582,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: [100], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -584,7 +598,7 @@ module.exports = perspective => { { x: ["a"], y: ["A"], - index: [1] + index: [1], }, {index: "index"} ); @@ -592,22 +606,22 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y"] + row_pivots: ["y"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 100; i++) { table.update({ x: ["a"], - index: [1] + index: [1], }); } @@ -617,7 +631,7 @@ module.exports = perspective => { for (let i = 0; i < 100; i++) { table.update({ x: ["b"], - index: [1] + index: [1], }); } @@ -635,7 +649,7 @@ module.exports = perspective => { x: ["a"], y: ["A"], z: [1], - index: [1] + index: [1], }, {index: "index"} ); @@ -643,9 +657,9 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y", "z"] + row_pivots: ["y", "z"], }); expect(await view.to_columns()).toEqual({ @@ -653,13 +667,13 @@ module.exports = perspective => { x: ["a", "a", "a"], y: [1, 1, 1], z: [1, 1, 1], - index: [1, 1, 1] + index: [1, 1, 1], }); for (let i = 0; i < 100; i++) { table.update({ x: ["a"], - index: [1] + index: [1], }); } @@ -669,7 +683,7 @@ module.exports = perspective => { for (let i = 0; i < 100; i++) { table.update({ x: ["b"], - index: [1] + index: [1], }); } @@ -682,16 +696,28 @@ module.exports = perspective => { x: [i.toString()], y: [i.toString()], z: [i], - index: [i] + index: [i], }); } expect(await view.to_columns()).toEqual({ - __ROW_PATH__: [[], ["2"], ["2", 2], ["3"], ["3", 3], ["4"], ["4", 4], ["5"], ["5", 5], ["A"], ["A", 1]], + __ROW_PATH__: [ + [], + ["2"], + ["2", 2], + ["3"], + ["3", 3], + ["4"], + ["4", 4], + ["5"], + ["5", 5], + ["A"], + ["A", 1], + ], x: ["5", "2", "2", "3", "3", "4", "4", "5", "5", "b", "b"], y: [5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], z: [15, 2, 2, 3, 3, 4, 4, 5, 5, 1, 1], - index: [15, 2, 2, 3, 3, 4, 4, 5, 5, 1, 1] + index: [15, 2, 2, 3, 3, 4, 4, 5, 5, 1, 1], }); await view.delete(); @@ -704,7 +730,7 @@ module.exports = perspective => { { x: ["a"], y: [null], - index: [1] + index: [1], }, {index: "index"} ); @@ -712,37 +738,37 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["x"] + row_pivots: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["a"]], x: ["a", "a"], y: [1, 1], // null is still a value for count - index: [1, 1] + index: [1, 1], }); const view2 = await table.view({ aggregates: { x: "last", - y: "last" + y: "last", }, - row_pivots: ["x"] + row_pivots: ["x"], }); expect(await view2.to_columns()).toEqual({ __ROW_PATH__: [[], ["a"]], x: ["a", "a"], y: [null, null], - index: [1, 1] + index: [1, 1], }); for (let i = 0; i < 100; i++) { table.update({ x: ["a"], - index: [1] + index: [1], }); } @@ -753,7 +779,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: ["B"], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -765,7 +791,7 @@ module.exports = perspective => { // and invalid table.update({ y: [null], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -783,7 +809,7 @@ module.exports = perspective => { { x: ["a"], y: ["A"], - index: [1] + index: [1], }, {index: "index"} ); @@ -791,23 +817,23 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, row_pivots: ["y"], - column_pivots: ["x"] + column_pivots: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], "a|x": ["a", "a"], "a|y": [1, 1], - "a|index": [1, 1] + "a|index": [1, 1], }); for (let i = 0; i < 5; i++) { table.update({ x: ["a"], - index: [1] + index: [1], }); } @@ -817,7 +843,7 @@ module.exports = perspective => { table.update({ x: ["b"], y: ["A"], - index: [1] + index: [1], }); result = await view.to_columns(); @@ -833,7 +859,7 @@ module.exports = perspective => { { x: ["a"], y: ["A"], - index: [1] + index: [1], }, {index: "index"} ); @@ -841,21 +867,21 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y"] + row_pivots: ["y"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["b"], - index: [1] + index: [1], }); // x should flip, y should remain the same @@ -863,12 +889,12 @@ module.exports = perspective => { __ROW_PATH__: [[], ["A"]], x: ["b", "b"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["c"], - index: [1] + index: [1], }); // and again @@ -876,13 +902,13 @@ module.exports = perspective => { __ROW_PATH__: [[], ["A"]], x: ["c", "c"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["d"], y: ["B"], - index: [2] + index: [2], }); // another row @@ -890,7 +916,7 @@ module.exports = perspective => { __ROW_PATH__: [[], ["A"], ["B"]], x: ["d", "c", "d"], y: [2, 1, 1], - index: [3, 1, 2] + index: [3, 1, 2], }); await view.delete(); @@ -902,7 +928,7 @@ module.exports = perspective => { { x: ["a"], y: ["A"], - index: [1] + index: [1], }, {index: "index"} ); @@ -910,22 +936,22 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, row_pivots: ["y"], - filter: [["x", "==", "a"]] + filter: [["x", "==", "a"]], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["b"], - index: [1] + index: [1], }); // x should flip, y should remain the same @@ -933,12 +959,12 @@ module.exports = perspective => { __ROW_PATH__: [[]], x: [""], y: [0], - index: [0] + index: [0], }); table.update({ x: ["a"], - index: [1] + index: [1], }); // and again @@ -946,13 +972,13 @@ module.exports = perspective => { __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["a"], y: ["B"], - index: [2] + index: [2], }); // another row @@ -960,7 +986,7 @@ module.exports = perspective => { __ROW_PATH__: [[], ["A"], ["B"]], x: ["a", "a", "a"], y: [2, 1, 1], - index: [3, 1, 2] + index: [3, 1, 2], }); await view.delete(); @@ -971,39 +997,39 @@ module.exports = perspective => { const table = await perspective.table({ x: ["a"], y: ["A"], - index: [1] + index: [1], }); const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y"] + row_pivots: ["y"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - index: [1, 1] + index: [1, 1], }); table.update({ x: ["b"], - index: [1] + index: [1], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], [null], ["A"]], x: ["b", "b", "a"], y: [2, 1, 1], - index: [2, 1, 1] + index: [2, 1, 1], }); table.update({ x: ["c"], - index: [1] + index: [1], }); // and again @@ -1011,13 +1037,13 @@ module.exports = perspective => { __ROW_PATH__: [[], [null], ["A"]], x: ["c", "c", "a"], y: [3, 2, 1], - index: [3, 2, 1] + index: [3, 2, 1], }); table.update({ x: ["d"], y: ["B"], - index: [2] + index: [2], }); // another row @@ -1025,24 +1051,24 @@ module.exports = perspective => { __ROW_PATH__: [[], [null], ["A"], ["B"]], x: ["d", "c", "a", "d"], y: [4, 2, 1, 1], - index: [5, 2, 1, 2] + index: [5, 2, 1, 2], }); await view.delete(); await table.delete(); }); - it("['z'], last by index", async function() { + it("['z'], last by index", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "last by index"} + aggregates: {x: "last by index"}, }); var answer = [ {__ROW_PATH__: [], x: 4}, {__ROW_PATH__: [false], x: 4}, - {__ROW_PATH__: [true], x: 3} + {__ROW_PATH__: [true], x: 3}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1050,22 +1076,22 @@ module.exports = perspective => { table.delete(); }); - it("['z'], last by index with appends", async function() { + it("['z'], last by index with appends", async function () { const table = await perspective.table(data); const view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "last by index"} + aggregates: {x: "last by index"}, }); const answer = [ {__ROW_PATH__: [], x: 5}, {__ROW_PATH__: [false], x: 4}, - {__ROW_PATH__: [true], x: 5} + {__ROW_PATH__: [true], x: 5}, ]; table.update({ x: [5], y: ["e"], - z: [true] + z: [true], }); const result = await view.to_json(); expect(result).toEqual(answer); @@ -1073,22 +1099,22 @@ module.exports = perspective => { table.delete(); }); - it("['z'], last by index with partial updates", async function() { + it("['z'], last by index with partial updates", async function () { const table = await perspective.table(data, {index: "y"}); const view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "last by index"} + aggregates: {x: "last by index"}, }); const answer = [ {__ROW_PATH__: [], x: 4}, {__ROW_PATH__: [false], x: 4}, - {__ROW_PATH__: [true], x: 5} + {__ROW_PATH__: [true], x: 5}, ]; table.update({ x: [5], y: ["c"], - z: [true] + z: [true], }); const result = await view.to_json(); expect(result).toEqual(answer); @@ -1096,29 +1122,29 @@ module.exports = perspective => { table.delete(); }); - it("['z'], last", async function() { + it("['z'], last", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], columns: ["x"], - aggregates: {x: "last"} + aggregates: {x: "last"}, }); var answer = [ {__ROW_PATH__: [], x: 3}, {__ROW_PATH__: [false], x: 4}, - {__ROW_PATH__: [true], x: 3} + {__ROW_PATH__: [true], x: 3}, ]; let result = await view.to_json(); expect(result).toEqual(answer); table.update([ {x: 1, y: "c", z: true}, - {x: 2, y: "d", z: false} + {x: 2, y: "d", z: false}, ]); var answerAfterUpdate = [ {__ROW_PATH__: [], x: 1}, {__ROW_PATH__: [false], x: 2}, - {__ROW_PATH__: [true], x: 1} + {__ROW_PATH__: [true], x: 1}, ]; let result2 = await view.to_json(); expect(result2).toEqual(answerAfterUpdate); @@ -1126,12 +1152,12 @@ module.exports = perspective => { table.delete(); }); - it("unique", async function() { + it("unique", async function () { const table = await perspective.table( { x: [100, 200, 100, 200], y: [1, 2, 3, 4], - z: ["a", "a", "a", "b"] + z: ["a", "a", "a", "b"], }, {index: "y"} ); @@ -1141,56 +1167,59 @@ module.exports = perspective => { aggregates: { x: "unique", y: "unique", - z: "unique" - } + z: "unique", + }, }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], [100], [200]], x: [null, 100, 200], y: [null, null, null], - z: [null, "a", null] + z: [null, "a", null], }); table.update({ y: [4], - z: ["a"] + z: ["a"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], [100], [200]], x: [null, 100, 200], y: [null, null, null], - z: ["a", "a", "a"] + z: ["a", "a", "a"], }); table.update({ y: [5], - z: ["x"] + z: ["x"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], [null], [100], [200]], x: [null, null, 100, 200], y: [null, 5, null, null], - z: [null, "x", "a", "a"] + z: [null, "x", "a", "a"], }); await view.delete(); await table.delete(); }); - it("variance", async function() { + it("variance", async function () { const table = await perspective.table(float_data); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); const result = await view.to_columns(); - const expected = [0.33597085443953206, 0.35043269693156814, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363].map(x => Math.pow(x, 2)); + const expected = [ + 0.33597085443953206, 0.35043269693156814, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected[i], 6); @@ -1200,20 +1229,27 @@ module.exports = perspective => { await table.delete(); }); - it("variance multi-pivot", async function() { + it("variance multi-pivot", async function () { const table = await perspective.table({ - x: [0.62817744, 0.16903811, 0.77902867, 0.92330087, 0.10583306, 0.59794354], + x: [ + 0.62817744, 0.16903811, 0.77902867, 0.92330087, 0.10583306, + 0.59794354, + ], y: ["a", "a", "b", "b", "c", "c"], - z: [1, 1, 1, 1, 1, 1] + z: [1, 1, 1, 1, 1, 1], }); const view = await table.view({ row_pivots: ["y", "z"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); const result = await view.to_columns(); - const expected = [0.3002988555851961, 0.22956966499999998, 0.22956966499999998, 0.07213610000000004, 0.07213610000000004, 0.24605524, 0.24605524].map(x => Math.pow(x, 2)); + const expected = [ + 0.3002988555851961, 0.22956966499999998, 0.22956966499999998, + 0.07213610000000004, 0.07213610000000004, 0.24605524, + 0.24605524, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected[i], 6); @@ -1223,17 +1259,20 @@ module.exports = perspective => { await table.delete(); }); - it("variance append", async function() { + it("variance append", async function () { const table = await perspective.table(float_data); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); let result = await view.to_columns(); - const expected = [0.33597085443953206, 0.35043269693156814, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363].map(x => Math.pow(x, 2)); + const expected = [ + 0.33597085443953206, 0.35043269693156814, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected[i], 6); @@ -1242,7 +1281,10 @@ module.exports = perspective => { table.update([{x: 0.64294039, y: "a"}]); result = await view.to_columns(); - const expected2 = [0.32935140956170517, 0.32031993493462224, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363].map(x => Math.pow(x, 2)); + const expected2 = [ + 0.32935140956170517, 0.32031993493462224, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected2[i], 6); } @@ -1251,12 +1293,15 @@ module.exports = perspective => { await table.delete(); }); - it("variance partial update", async function() { + it("variance partial update", async function () { const table = await perspective.table( { - x: [0.99098243, 0.36677191, 0.58926465, 0.95701263, 0.96904283, 0.50398721], + x: [ + 0.99098243, 0.36677191, 0.58926465, 0.95701263, + 0.96904283, 0.50398721, + ], y: ["a", "a", "b", "b", "c", "c"], - z: [1, 2, 3, 4, 5, 6] + z: [1, 2, 3, 4, 5, 6], }, {index: "z"} ); @@ -1264,12 +1309,15 @@ module.exports = perspective => { const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); let result = await view.to_columns(); - const expected = [0.25153179517283897, 0.31210526, 0.18387399000000004, 0.23252781].map(x => Math.pow(x, 2)); + const expected = [ + 0.25153179517283897, 0.31210526, 0.18387399000000004, + 0.23252781, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected[i], 6); @@ -1278,7 +1326,9 @@ module.exports = perspective => { table.update([{x: 0.284169685, z: 3}]); result = await view.to_columns(); - const expected2 = [0.3007643174035643, 0.31210526, 0.33642147250000004, 0.23252781].map(x => Math.pow(x, 2)); + const expected2 = [ + 0.3007643174035643, 0.31210526, 0.33642147250000004, 0.23252781, + ].map((x) => Math.pow(x, 2)); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected2[i], 6); @@ -1288,7 +1338,7 @@ module.exports = perspective => { await table.delete(); }); - it("variance of range", async function() { + it("variance of range", async function () { const float_data_copy = JSON.parse(JSON.stringify(float_data)); float_data_copy["y"] = Array(float_data_copy["x"].length).fill("a"); @@ -1296,12 +1346,14 @@ module.exports = perspective => { const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); const result = await view.to_columns(); - const expected = [0.33597085443953206, 0.33597085443953206].map(x => Math.pow(x, 2)); + const expected = [0.33597085443953206, 0.33597085443953206].map( + (x) => Math.pow(x, 2) + ); for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected[i], 6); @@ -1311,15 +1363,15 @@ module.exports = perspective => { await table.delete(); }); - it("variance of size 1", async function() { + it("variance of size 1", async function () { const table = await perspective.table({ x: [0.61801758], - y: ["a"] + y: ["a"], }); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); const result = await view.to_columns(); @@ -1329,36 +1381,41 @@ module.exports = perspective => { await table.delete(); }); - it("variance of size 2", async function() { + it("variance of size 2", async function () { const table = await perspective.table({ x: [0.61801758, 0.11283123], - y: ["a", "a"] + y: ["a", "a"], }); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "var"} + aggregates: {x: "var"}, }); const result = await view.to_columns(); - expect(result.x).toEqual([0.06380331205658062, 0.06380331205658062]); + expect(result.x).toEqual([ + 0.06380331205658062, 0.06380331205658062, + ]); await view.delete(); await table.delete(); }); - it("standard deviation", async function() { + it("standard deviation", async function () { const table = await perspective.table(float_data); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); const result = await view.to_columns(); // using np.std() - const expected = [0.33597085443953206, 0.35043269693156814, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363]; + const expected = [ + 0.33597085443953206, 0.35043269693156814, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { @@ -1369,22 +1426,29 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation multi-pivot", async function() { + it("standard deviation multi-pivot", async function () { const table = await perspective.table({ - x: [0.62817744, 0.16903811, 0.77902867, 0.92330087, 0.10583306, 0.59794354], + x: [ + 0.62817744, 0.16903811, 0.77902867, 0.92330087, 0.10583306, + 0.59794354, + ], y: ["a", "a", "b", "b", "c", "c"], - z: [1, 1, 1, 1, 1, 1] + z: [1, 1, 1, 1, 1, 1], }); const view = await table.view({ row_pivots: ["y", "z"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); const result = await view.to_columns(); // using np.std() - const expected = [0.3002988555851961, 0.22956966499999998, 0.22956966499999998, 0.07213610000000004, 0.07213610000000004, 0.24605524, 0.24605524]; + const expected = [ + 0.3002988555851961, 0.22956966499999998, 0.22956966499999998, + 0.07213610000000004, 0.07213610000000004, 0.24605524, + 0.24605524, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { @@ -1395,18 +1459,21 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation append", async function() { + it("standard deviation append", async function () { const table = await perspective.table(float_data); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); let result = await view.to_columns(); // using np.std() - const expected = [0.33597085443953206, 0.35043269693156814, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363]; + const expected = [ + 0.33597085443953206, 0.35043269693156814, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { @@ -1416,7 +1483,10 @@ module.exports = perspective => { table.update([{x: 0.64294039, y: "a"}]); result = await view.to_columns(); - const expected2 = [0.32935140956170517, 0.32031993493462224, 0.22510197203101087, 0.20827220910070432, 0.3473746254711007, 0.3618418050868363]; + const expected2 = [ + 0.32935140956170517, 0.32031993493462224, 0.22510197203101087, + 0.20827220910070432, 0.3473746254711007, 0.3618418050868363, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { expect(result.x[i]).toBeCloseTo(expected2[i], 6); @@ -1426,12 +1496,15 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation partial update", async function() { + it("standard deviation partial update", async function () { const table = await perspective.table( { - x: [0.99098243, 0.36677191, 0.58926465, 0.95701263, 0.96904283, 0.50398721], + x: [ + 0.99098243, 0.36677191, 0.58926465, 0.95701263, + 0.96904283, 0.50398721, + ], y: ["a", "a", "b", "b", "c", "c"], - z: [1, 2, 3, 4, 5, 6] + z: [1, 2, 3, 4, 5, 6], }, {index: "z"} ); @@ -1439,13 +1512,16 @@ module.exports = perspective => { const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); let result = await view.to_columns(); // using np.std() - const expected = [0.25153179517283897, 0.31210526, 0.18387399000000004, 0.23252781]; + const expected = [ + 0.25153179517283897, 0.31210526, 0.18387399000000004, + 0.23252781, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { @@ -1455,7 +1531,9 @@ module.exports = perspective => { table.update([{x: 0.284169685, z: 3}]); result = await view.to_columns(); - const expected2 = [0.3007643174035643, 0.31210526, 0.33642147250000004, 0.23252781]; + const expected2 = [ + 0.3007643174035643, 0.31210526, 0.33642147250000004, 0.23252781, + ]; // Check we are within 6 digits of the result from np.std() for (let i = 0; i < result.x.length; i++) { @@ -1466,7 +1544,7 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation of range", async function() { + it("standard deviation of range", async function () { const float_data_copy = JSON.parse(JSON.stringify(float_data)); float_data_copy["y"] = Array(float_data_copy["x"].length).fill("a"); @@ -1474,7 +1552,7 @@ module.exports = perspective => { const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); const result = await view.to_columns(); @@ -1491,15 +1569,15 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation of size 1", async function() { + it("standard deviation of size 1", async function () { const table = await perspective.table({ x: [0.61801758], - y: ["a"] + y: ["a"], }); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); const result = await view.to_columns(); @@ -1509,15 +1587,15 @@ module.exports = perspective => { await table.delete(); }); - it("standard deviation of size 2", async function() { + it("standard deviation of size 2", async function () { const table = await perspective.table({ x: [0.61801758, 0.11283123], - y: ["a", "a"] + y: ["a", "a"], }); const view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "stddev"} + aggregates: {x: "stddev"}, }); const result = await view.to_columns(); @@ -1528,25 +1606,25 @@ module.exports = perspective => { }); }); - describe("Aggregates with nulls", function() { - it("mean", async function() { + describe("Aggregates with nulls", function () { + it("mean", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 3}, {__ROW_PATH__: [1], x: 2.5}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1554,7 +1632,7 @@ module.exports = perspective => { table.delete(); }); - it("mean with 0", async function() { + it("mean with 0", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 3, y: 1}, @@ -1562,17 +1640,17 @@ module.exports = perspective => { {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 2.5}, {__ROW_PATH__: [1], x: 2}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1580,7 +1658,7 @@ module.exports = perspective => { table.delete(); }); - it("mean with 0.0 (floats)", async function() { + it("mean with 0.0 (floats)", async function () { var table = await perspective.table({x: "float", y: "integer"}); table.update([ {x: 3, y: 1}, @@ -1589,17 +1667,17 @@ module.exports = perspective => { {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 2.5}, {__ROW_PATH__: [1], x: 2}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1607,23 +1685,23 @@ module.exports = perspective => { table.delete(); }); - it("sum", async function() { + it("sum", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: 4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], - columns: ["x"] + columns: ["x"], }); var answer = [ {__ROW_PATH__: [], x: 9}, {__ROW_PATH__: [1], x: 5}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1631,24 +1709,24 @@ module.exports = perspective => { table.delete(); }); - it("abs sum", async function() { + it("abs sum", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: null, y: 1}, {x: null, y: 1}, {x: -4, y: 2}, - {x: null, y: 2} + {x: null, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "abs sum"} + aggregates: {x: "abs sum"}, }); var answer = [ {__ROW_PATH__: [], x: 1}, {__ROW_PATH__: [1], x: 5}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1656,26 +1734,26 @@ module.exports = perspective => { table.delete(); }); - it("mean after update", async function() { + it("mean after update", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: null, y: 1}, - {x: null, y: 2} + {x: null, y: 2}, ]); table.update([ {x: 2, y: 1}, {x: null, y: 1}, - {x: 4, y: 2} + {x: 4, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 3}, {__ROW_PATH__: [1], x: 2.5}, - {__ROW_PATH__: [2], x: 4} + {__ROW_PATH__: [2], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1683,29 +1761,29 @@ module.exports = perspective => { table.delete(); }); - it("mean at aggregate level", async function() { + it("mean at aggregate level", async function () { var table = await perspective.table([ {x: 4, y: 1, z: "a"}, {x: null, y: 1, z: "a"}, - {x: null, y: 2, z: "a"} + {x: null, y: 2, z: "a"}, ]); table.update([ {x: 1, y: 1, z: "b"}, {x: 1, y: 1, z: "b"}, {x: null, y: 1, z: "b"}, {x: 4, y: 2, z: "b"}, - {x: null, y: 2, z: "b"} + {x: null, y: 2, z: "b"}, ]); table.update([ {x: 2, y: 2, z: "c"}, {x: 3, y: 2, z: "c"}, {x: null, y: 2, z: "c"}, - {x: 7, y: 2, z: "c"} + {x: 7, y: 2, z: "c"}, ]); var view = await table.view({ row_pivots: ["y", "z"], columns: ["x"], - aggregates: {x: "mean"} + aggregates: {x: "mean"}, }); var answer = [ {__ROW_PATH__: [], x: 3.142857142857143}, @@ -1715,7 +1793,7 @@ module.exports = perspective => { {__ROW_PATH__: [2], x: 4}, {__ROW_PATH__: [2, "a"], x: null}, {__ROW_PATH__: [2, "b"], x: 4}, - {__ROW_PATH__: [2, "c"], x: 4} + {__ROW_PATH__: [2, "c"], x: 4}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1723,18 +1801,22 @@ module.exports = perspective => { table.delete(); }); - it("null in pivot column", async function() { - var table = await perspective.table([{x: null}, {x: "x"}, {x: "y"}]); + it("null in pivot column", async function () { + var table = await perspective.table([ + {x: null}, + {x: "x"}, + {x: "y"}, + ]); var view = await table.view({ row_pivots: ["x"], columns: ["x"], - aggregates: {x: "distinct count"} + aggregates: {x: "distinct count"}, }); var answer = [ {__ROW_PATH__: [], x: 3}, {__ROW_PATH__: [null], x: 1}, {__ROW_PATH__: ["x"], x: 1}, - {__ROW_PATH__: ["y"], x: 1} + {__ROW_PATH__: ["y"], x: 1}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1742,20 +1824,20 @@ module.exports = perspective => { table.delete(); }); - it("weighted mean", async function() { + it("weighted mean", async function () { var table = await perspective.table([ {a: "a", x: 1, y: 200}, {a: "a", x: 2, y: 100}, - {a: "a", x: 3, y: null} + {a: "a", x: 3, y: null}, ]); var view = await table.view({ row_pivots: ["a"], aggregates: {y: ["weighted mean", "x"]}, - columns: ["y"] + columns: ["y"], }); var answer = [ {__ROW_PATH__: [], y: (1 * 200 + 2 * 100) / (1 + 2)}, - {__ROW_PATH__: ["a"], y: (1 * 200 + 2 * 100) / (1 + 2)} + {__ROW_PATH__: ["a"], y: (1 * 200 + 2 * 100) / (1 + 2)}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -1764,25 +1846,25 @@ module.exports = perspective => { }); }); - describe("Aggregates with negatives", function() { - it("sum abs", async function() { + describe("Aggregates with negatives", function () { + it("sum abs", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: 1, y: 1}, {x: -1, y: 1}, {x: -2, y: 2}, - {x: -3, y: 2} + {x: -3, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "sum abs"} + aggregates: {x: "sum abs"}, }); var answer = [ {__ROW_PATH__: [], x: 12}, {__ROW_PATH__: [1], x: 7}, - {__ROW_PATH__: [2], x: 5} + {__ROW_PATH__: [2], x: 5}, ]; let result = await view.to_json(); expect(answer).toEqual(result); @@ -1790,24 +1872,24 @@ module.exports = perspective => { table.delete(); }); - it("abs sum", async function() { + it("abs sum", async function () { var table = await perspective.table([ {x: 3, y: 1}, {x: 2, y: 1}, {x: -1, y: 1}, {x: -1, y: 1}, {x: -2, y: 2}, - {x: -3, y: 2} + {x: -3, y: 2}, ]); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "abs sum"} + aggregates: {x: "abs sum"}, }); var answer = [ {__ROW_PATH__: [], x: 2}, {__ROW_PATH__: [1], x: 3}, - {__ROW_PATH__: [2], x: 5} + {__ROW_PATH__: [2], x: 5}, ]; let result = await view.to_json(); expect(answer).toEqual(result); @@ -1816,19 +1898,19 @@ module.exports = perspective => { }); }); - describe("Row pivot", function() { - it("['x']", async function() { + describe("Row pivot", function () { + it("['x']", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["x"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); var answer = [ {__ROW_PATH__: [], x: 10, y: 4, z: 2}, {__ROW_PATH__: [1], x: 1, y: 1, z: 1}, {__ROW_PATH__: [2], x: 2, y: 1, z: 1}, {__ROW_PATH__: [3], x: 3, y: 1, z: 1}, - {__ROW_PATH__: [4], x: 4, y: 1, z: 1} + {__ROW_PATH__: [4], x: 4, y: 1, z: 1}, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -1836,23 +1918,23 @@ module.exports = perspective => { table.delete(); }); - it("['x'] test update pkey column", async function() { + it("['x'] test update pkey column", async function () { const schema = { id: "integer", name: "string", chg: "float", - pos: "integer" + pos: "integer", }; const rec1 = [ {id: 1, name: "John", pos: 100, chg: 1}, {id: 2, name: "Mary", pos: 200, chg: 2}, - {id: 3, name: "Tom", pos: 300, chg: 3} + {id: 3, name: "Tom", pos: 300, chg: 3}, ]; const table = await perspective.table(schema, {index: "id"}); table.update(rec1); let view = await table.view({ row_pivots: ["id"], - columns: ["pos"] + columns: ["pos"], }); let rec2 = [{id: 1, chg: 3}]; table.update(rec2); @@ -1861,34 +1943,34 @@ module.exports = perspective => { {__ROW_PATH__: [], pos: 600}, {__ROW_PATH__: [1], pos: 100}, {__ROW_PATH__: [2], pos: 200}, - {__ROW_PATH__: [3], pos: 300} + {__ROW_PATH__: [3], pos: 300}, ]; expect(result2).toEqual(answer); view.delete(); table.delete(); }); - describe("pivoting on column containing null values", function() { - it("shows one pivot for the nulls on initial load", async function() { + describe("pivoting on column containing null values", function () { + it("shows one pivot for the nulls on initial load", async function () { const dataWithNulls = [ {name: "Homer", value: 1}, {name: null, value: 1}, {name: null, value: 1}, - {name: "Krusty", value: 1} + {name: "Krusty", value: 1}, ]; var table = await perspective.table(dataWithNulls); var view = await table.view({ row_pivots: ["name"], - aggregates: {name: "distinct count"} + aggregates: {name: "distinct count"}, }); const answer = [ {__ROW_PATH__: [], name: 3, value: 4}, {__ROW_PATH__: [null], name: 1, value: 2}, {__ROW_PATH__: ["Homer"], name: 1, value: 1}, - {__ROW_PATH__: ["Krusty"], name: 1, value: 1} + {__ROW_PATH__: ["Krusty"], name: 1, value: 1}, ]; let results = await view.to_json(); @@ -1897,14 +1979,14 @@ module.exports = perspective => { table.delete(); }); - it("shows one pivot for the nulls after updating with a null", async function() { + it("shows one pivot for the nulls after updating with a null", async function () { const dataWithNull1 = [ {name: "Homer", value: 1}, - {name: null, value: 1} + {name: null, value: 1}, ]; const dataWithNull2 = [ {name: null, value: 1}, - {name: "Krusty", value: 1} + {name: "Krusty", value: 1}, ]; var table = await perspective.table(dataWithNull1); @@ -1912,14 +1994,14 @@ module.exports = perspective => { var view = await table.view({ row_pivots: ["name"], - aggregates: {name: "distinct count"} + aggregates: {name: "distinct count"}, }); const answer = [ {__ROW_PATH__: [], name: 3, value: 4}, {__ROW_PATH__: [null], name: 1, value: 2}, {__ROW_PATH__: ["Homer"], name: 1, value: 1}, - {__ROW_PATH__: ["Krusty"], name: 1, value: 1} + {__ROW_PATH__: ["Krusty"], name: 1, value: 1}, ]; let results = await view.to_json(); @@ -1929,10 +2011,10 @@ module.exports = perspective => { }); }); - it("['x'] has a schema", async function() { + it("['x'] has a schema", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["x"] + row_pivots: ["x"], }); let result2 = await view.schema(); expect(result2).toEqual({x: "integer", y: "integer", z: "integer"}); @@ -1940,12 +2022,12 @@ module.exports = perspective => { table.delete(); }); - it("['x'] translates type `string` to `integer` when pivoted by row", async function() { + it("['x'] translates type `string` to `integer` when pivoted by row", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["x"], columns: ["y"], - aggregates: {y: "distinct count"} + aggregates: {y: "distinct count"}, }); let result2 = await view.schema(); expect(result2).toEqual({y: "integer"}); @@ -1953,12 +2035,12 @@ module.exports = perspective => { table.delete(); }); - it("['x'] translates type `integer` to `float` when pivoted by row", async function() { + it("['x'] translates type `integer` to `float` when pivoted by row", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], columns: ["x"], - aggregates: {x: "avg"} + aggregates: {x: "avg"}, }); let result2 = await view.schema(); expect(result2).toEqual({x: "float"}); @@ -1966,12 +2048,12 @@ module.exports = perspective => { table.delete(); }); - it("['x'] does not translate type when only pivoted by column", async function() { + it("['x'] does not translate type when only pivoted by column", async function () { var table = await perspective.table(data); var view = await table.view({ column_pivots: ["y"], columns: ["x"], - aggregates: {x: "avg"} + aggregates: {x: "avg"}, }); let result2 = await view.schema(); expect(result2).toEqual({x: "integer"}); @@ -1979,10 +2061,10 @@ module.exports = perspective => { table.delete(); }); - it("['x'] has the correct # of rows", async function() { + it("['x'] has the correct # of rows", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["x"] + row_pivots: ["x"], }); let result2 = await view.num_rows(); expect(result2).toEqual(5); @@ -1990,10 +2072,10 @@ module.exports = perspective => { table.delete(); }); - it("['x'] has the correct # of columns", async function() { + it("['x'] has the correct # of columns", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["x"] + row_pivots: ["x"], }); let result2 = await view.num_columns(); expect(result2).toEqual(3); @@ -2001,7 +2083,7 @@ module.exports = perspective => { table.delete(); }); - it("Row Pivot by date column results in correct headers", async function() { + it("Row Pivot by date column results in correct headers", async function () { var table = await perspective.table({ a: [ new Date("2020/01/15"), @@ -2015,12 +2097,12 @@ module.exports = perspective => { new Date("2020/09/15"), new Date("2020/10/15"), new Date("2020/11/15"), - new Date("2020/12/15") + new Date("2020/12/15"), ], - b: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + b: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], }); var view = await table.view({ - row_pivots: ["a"] + row_pivots: ["a"], }); const results = await view.to_columns(); expect(results).toEqual({ @@ -2037,10 +2119,10 @@ module.exports = perspective => { [1600128000000], [1602720000000], [1605398400000], - [1607990400000] + [1607990400000], ], a: [12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - b: [78, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + b: [78, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], }); const dates = results["__ROW_PATH__"]; @@ -2057,7 +2139,7 @@ module.exports = perspective => { new Date("2020/09/15"), new Date("2020/10/15"), new Date("2020/11/15"), - new Date("2020/12/15") + new Date("2020/12/15"), ]; for (const d of dates) { @@ -2072,16 +2154,16 @@ module.exports = perspective => { table.delete(); }); - it("['z']", async function() { + it("['z']", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["z"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); var answer = [ {__ROW_PATH__: [], x: 10, y: 4, z: 2}, {__ROW_PATH__: [false], x: 6, y: 2, z: 1}, - {__ROW_PATH__: [true], x: 4, y: 2, z: 1} + {__ROW_PATH__: [true], x: 4, y: 2, z: 1}, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2089,11 +2171,11 @@ module.exports = perspective => { table.delete(); }); - it("['x', 'z']", async function() { + it("['x', 'z']", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["x", "z"], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); var answer = [ {__ROW_PATH__: [], x: 10, y: 4, z: 2}, @@ -2104,7 +2186,7 @@ module.exports = perspective => { {__ROW_PATH__: [3], x: 3, y: 1, z: 1}, {__ROW_PATH__: [3, true], x: 3, y: 1, z: 1}, {__ROW_PATH__: [4], x: 4, y: 1, z: 1}, - {__ROW_PATH__: [4, false], x: 4, y: 1, z: 1} + {__ROW_PATH__: [4, false], x: 4, y: 1, z: 1}, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2112,10 +2194,10 @@ module.exports = perspective => { table.delete(); }); - it("['x', 'z'] windowed", async function() { + it("['x', 'z'] windowed", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["x", "z"] + row_pivots: ["x", "z"], }); var answer = [ {__ROW_PATH__: [1, true], x: 1, y: 1, z: 1}, @@ -2124,7 +2206,7 @@ module.exports = perspective => { {__ROW_PATH__: [3], x: 3, y: 1, z: 1}, {__ROW_PATH__: [3, true], x: 3, y: 1, z: 1}, {__ROW_PATH__: [4], x: 4, y: 1, z: 1}, - {__ROW_PATH__: [4, false], x: 4, y: 1, z: 1} + {__ROW_PATH__: [4, false], x: 4, y: 1, z: 1}, ]; let result2 = await view.to_json({start_row: 2}); expect(result2).toEqual(answer); @@ -2132,19 +2214,19 @@ module.exports = perspective => { table.delete(); }); - it("['x', 'z'], pivot_depth = 1", async function() { + it("['x', 'z'], pivot_depth = 1", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["x", "z"], row_pivot_depth: 1, - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); var answer = [ {__ROW_PATH__: [], x: 10, y: 4, z: 2}, {__ROW_PATH__: [1], x: 1, y: 1, z: 1}, {__ROW_PATH__: [2], x: 2, y: 1, z: 1}, {__ROW_PATH__: [3], x: 3, y: 1, z: 1}, - {__ROW_PATH__: [4], x: 4, y: 1, z: 1} + {__ROW_PATH__: [4], x: 4, y: 1, z: 1}, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2153,11 +2235,11 @@ module.exports = perspective => { }); }); - describe("Column pivot", function() { - it("['y'] only, schema", async function() { + describe("Column pivot", function () { + it("['y'] only, schema", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["y"] + column_pivots: ["y"], }); let result2 = await view.schema(); expect(result2).toEqual(meta); @@ -2165,43 +2247,79 @@ module.exports = perspective => { table.delete(); }); - it("['z'] only, datetime column", async function() { + it("['z'] only, datetime column", async function () { var table = await perspective.table(data_8); var view = await table.view({ column_pivots: ["z"], - columns: ["x", "y"] + columns: ["x", "y"], }); let result2 = await view.to_columns(); expect(result2).toEqual({ "4/11/2019, 11:40:35 PM|x": [null, null, 3, 4], "4/11/2019, 11:40:35 PM|y": [null, null, "c", "d"], "4/13/2019, 3:27:15 AM|x": [1, 2, null, null], - "4/13/2019, 3:27:15 AM|y": ["a", "b", null, null] + "4/13/2019, 3:27:15 AM|y": ["a", "b", null, null], }); view.delete(); table.delete(); }); - it("['x'] only, column-oriented input", async function() { + it("['x'] only, column-oriented input", async function () { var table = await perspective.table(data_7); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); let result2 = await view.to_json(); expect(result2).toEqual([ - {"true|w": 1.5, "true|x": 1, "true|y": "a", "true|z": true, "false|w": null, "false|x": null, "false|y": null, "false|z": null}, - {"true|w": null, "true|x": null, "true|y": null, "true|z": null, "false|w": 2.5, "false|x": 2, "false|y": "b", "false|z": false}, - {"true|w": 3.5, "true|x": 3, "true|y": "c", "true|z": true, "false|w": null, "false|x": null, "false|y": null, "false|z": null}, - {"true|w": null, "true|x": null, "true|y": null, "true|z": null, "false|w": 4.5, "false|x": 4, "false|y": "d", "false|z": false} + { + "true|w": 1.5, + "true|x": 1, + "true|y": "a", + "true|z": true, + "false|w": null, + "false|x": null, + "false|y": null, + "false|z": null, + }, + { + "true|w": null, + "true|x": null, + "true|y": null, + "true|z": null, + "false|w": 2.5, + "false|x": 2, + "false|y": "b", + "false|z": false, + }, + { + "true|w": 3.5, + "true|x": 3, + "true|y": "c", + "true|z": true, + "false|w": null, + "false|x": null, + "false|y": null, + "false|z": null, + }, + { + "true|w": null, + "true|x": null, + "true|y": null, + "true|z": null, + "false|w": 4.5, + "false|x": 4, + "false|y": "d", + "false|z": false, + }, ]); view.delete(); table.delete(); }); - it("['z'] only, column-oriented output", async function() { + it("['z'] only, column-oriented output", async function () { var table = await perspective.table(data_7); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); let result2 = await view.to_columns(); expect(result2).toEqual({ @@ -2212,23 +2330,75 @@ module.exports = perspective => { "false|w": [null, 2.5, null, 4.5], "false|x": [null, 2, null, 4], "false|y": [null, "b", null, "d"], - "false|z": [null, false, null, false] + "false|z": [null, false, null, false], }); view.delete(); table.delete(); }); - it("['y'] only sorted by ['x'] desc", async function() { + it("['y'] only sorted by ['x'] desc", async function () { var table = await perspective.table(data); var view = await table.view({ column_pivots: ["y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); var answer = [ - {"a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": 4, "d|y": "d", "d|z": false}, - {"a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": 3, "c|y": "c", "c|z": true, "d|x": null, "d|y": null, "d|z": null}, - {"a|x": null, "a|y": null, "a|z": null, "b|x": 2, "b|y": "b", "b|z": false, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {"a|x": 1, "a|y": "a", "a|z": true, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null} + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": 4, + "d|y": "d", + "d|z": false, + }, + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": 3, + "c|y": "c", + "c|z": true, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": 2, + "b|y": "b", + "b|z": false, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + "a|x": 1, + "a|y": "a", + "a|z": true, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2236,16 +2406,68 @@ module.exports = perspective => { table.delete(); }); - it("['y'] only", async function() { + it("['y'] only", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["y"] + column_pivots: ["y"], }); var answer = [ - {"a|x": 1, "a|y": "a", "a|z": true, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {"a|x": null, "a|y": null, "a|z": null, "b|x": 2, "b|y": "b", "b|z": false, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {"a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": 3, "c|y": "c", "c|z": true, "d|x": null, "d|y": null, "d|z": null}, - {"a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": 4, "d|y": "d", "d|z": false} + { + "a|x": 1, + "a|y": "a", + "a|z": true, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": 2, + "b|y": "b", + "b|z": false, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": 3, + "c|y": "c", + "c|z": true, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": 4, + "d|y": "d", + "d|z": false, + }, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2253,18 +2475,88 @@ module.exports = perspective => { table.delete(); }); - it("['x'] by ['y']", async function() { + it("['x'] by ['y']", async function () { var table = await perspective.table(data); var view = await table.view({ column_pivots: ["y"], - row_pivots: ["x"] + row_pivots: ["x"], }); var answer = [ - {__ROW_PATH__: [], "a|x": 1, "a|y": 1, "a|z": 1, "b|x": 2, "b|y": 1, "b|z": 1, "c|x": 3, "c|y": 1, "c|z": 1, "d|x": 4, "d|y": 1, "d|z": 1}, - {__ROW_PATH__: [1], "a|x": 1, "a|y": 1, "a|z": 1, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {__ROW_PATH__: [2], "a|x": null, "a|y": null, "a|z": null, "b|x": 2, "b|y": 1, "b|z": 1, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {__ROW_PATH__: [3], "a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": 3, "c|y": 1, "c|z": 1, "d|x": null, "d|y": null, "d|z": null}, - {__ROW_PATH__: [4], "a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": 4, "d|y": 1, "d|z": 1} + { + __ROW_PATH__: [], + "a|x": 1, + "a|y": 1, + "a|z": 1, + "b|x": 2, + "b|y": 1, + "b|z": 1, + "c|x": 3, + "c|y": 1, + "c|z": 1, + "d|x": 4, + "d|y": 1, + "d|z": 1, + }, + { + __ROW_PATH__: [1], + "a|x": 1, + "a|y": 1, + "a|z": 1, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + __ROW_PATH__: [2], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": 2, + "b|y": 1, + "b|z": 1, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + __ROW_PATH__: [3], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": 3, + "c|y": 1, + "c|z": 1, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + __ROW_PATH__: [4], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": 4, + "d|y": 1, + "d|z": 1, + }, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2272,26 +2564,46 @@ module.exports = perspective => { table.delete(); }); - it("['x', 'z']", async function() { + it("['x', 'z']", async function () { var table = await perspective.table(data); var view = await table.view({ column_pivots: ["x", "z"], - columns: ["y"] + columns: ["y"], }); let result2 = await view.to_json(); expect(result2).toEqual([ - {"1|true|y": "a", "2|false|y": null, "3|true|y": null, "4|false|y": null}, - {"1|true|y": null, "2|false|y": "b", "3|true|y": null, "4|false|y": null}, - {"1|true|y": null, "2|false|y": null, "3|true|y": "c", "4|false|y": null}, - {"1|true|y": null, "2|false|y": null, "3|true|y": null, "4|false|y": "d"} + { + "1|true|y": "a", + "2|false|y": null, + "3|true|y": null, + "4|false|y": null, + }, + { + "1|true|y": null, + "2|false|y": "b", + "3|true|y": null, + "4|false|y": null, + }, + { + "1|true|y": null, + "2|false|y": null, + "3|true|y": "c", + "4|false|y": null, + }, + { + "1|true|y": null, + "2|false|y": null, + "3|true|y": null, + "4|false|y": "d", + }, ]); view.delete(); table.delete(); }); }); - describe("Expand/Collapse", function() { - it("Collapse a row in a 2x2 pivot", async function() { + describe("Expand/Collapse", function () { + it("Collapse a row in a 2x2 pivot", async function () { var table = await perspective.table([ {x: 7, y: "A", z: true, a: "AA", b: "BB", c: "CC"}, {x: 2, y: "A", z: false, a: "AA", b: "CC", c: "CC"}, @@ -2304,13 +2616,13 @@ module.exports = perspective => { {x: 9, y: "C", z: true, a: "BB", b: "BB", c: "CC"}, {x: 10, y: "C", z: false, a: "BB", b: "CC", c: "CC"}, {x: 11, y: "C", z: true, a: "BB", b: "BB", c: "DD"}, - {x: 12, y: "C", z: false, a: "BB", b: "CC", c: "DD"} + {x: 12, y: "C", z: false, a: "BB", b: "CC", c: "DD"}, ]); var view = await table.view({ column_pivots: ["z", "b"], row_pivots: ["y", "a"], columns: ["x"], - aggregates: {x: "last"} + aggregates: {x: "last"}, }); let answer = [ @@ -2319,7 +2631,7 @@ module.exports = perspective => { {__ROW_PATH__: ["A", "AA"], "false|CC|x": 4, "true|BB|x": 5}, {__ROW_PATH__: ["B"], "false|CC|x": 6, "true|BB|x": 3}, {__ROW_PATH__: ["C"], "false|CC|x": 12, "true|BB|x": 11}, - {__ROW_PATH__: ["C", "BB"], "false|CC|x": 12, "true|BB|x": 11} + {__ROW_PATH__: ["C", "BB"], "false|CC|x": 12, "true|BB|x": 11}, ]; view.collapse(3); let result2 = await view.to_json(); @@ -2329,8 +2641,8 @@ module.exports = perspective => { }); }); - describe("Column pivot w/sort", function() { - it("['y'] by ['z'], sorted by 'x'", async function() { + describe("Column pivot w/sort", function () { + it("['y'] by ['z'], sorted by 'x'", async function () { var table = await perspective.table([ {x: 7, y: "A", z: true}, {x: 2, y: "A", z: false}, @@ -2343,20 +2655,52 @@ module.exports = perspective => { {x: 9, y: "C", z: true}, {x: 10, y: "C", z: false}, {x: 11, y: "C", z: true}, - {x: 12, y: "C", z: false} + {x: 12, y: "C", z: false}, ]); var view = await table.view({ column_pivots: ["z"], row_pivots: ["y"], sort: [["x", "desc"]], - aggregates: {y: "distinct count", z: "distinct count"} + aggregates: {y: "distinct count", z: "distinct count"}, }); let answer = [ - {__ROW_PATH__: [], "false|x": 42, "false|y": 3, "false|z": 1, "true|x": 36, "true|y": 3, "true|z": 1}, - {__ROW_PATH__: ["C"], "false|x": 22, "false|y": 1, "false|z": 1, "true|x": 20, "true|y": 1, "true|z": 1}, - {__ROW_PATH__: ["A"], "false|x": 6, "false|y": 1, "false|z": 1, "true|x": 12, "true|y": 1, "true|z": 1}, - {__ROW_PATH__: ["B"], "false|x": 14, "false|y": 1, "false|z": 1, "true|x": 4, "true|y": 1, "true|z": 1} + { + __ROW_PATH__: [], + "false|x": 42, + "false|y": 3, + "false|z": 1, + "true|x": 36, + "true|y": 3, + "true|z": 1, + }, + { + __ROW_PATH__: ["C"], + "false|x": 22, + "false|y": 1, + "false|z": 1, + "true|x": 20, + "true|y": 1, + "true|z": 1, + }, + { + __ROW_PATH__: ["A"], + "false|x": 6, + "false|y": 1, + "false|z": 1, + "true|x": 12, + "true|y": 1, + "true|z": 1, + }, + { + __ROW_PATH__: ["B"], + "false|x": 14, + "false|y": 1, + "false|z": 1, + "true|x": 4, + "true|y": 1, + "true|z": 1, + }, ]; let result2 = await view.to_json(); expect(result2).toEqual(answer); @@ -2364,7 +2708,7 @@ module.exports = perspective => { table.delete(); }); - it("['z'] by ['y'], sorted by 'y'", async function() { + it("['z'] by ['y'], sorted by 'y'", async function () { var table = await perspective.table([ {x: 7, y: "A", z: true}, {x: 2, y: "A", z: false}, @@ -2377,28 +2721,36 @@ module.exports = perspective => { {x: 9, y: "C", z: true}, {x: 10, y: "C", z: false}, {x: 11, y: "C", z: true}, - {x: 12, y: "C", z: false} + {x: 12, y: "C", z: false}, ]); var view = await table.view({ column_pivots: ["y"], row_pivots: ["z"], sort: [["y", "col desc"]], columns: ["x", "y"], - aggregates: {x: "sum", y: "any"} + aggregates: {x: "sum", y: "any"}, }); let result2 = await view.to_columns(); - expect(Object.keys(result2)).toEqual(["__ROW_PATH__", "C|x", "C|y", "B|x", "B|y", "A|x", "A|y"]); + expect(Object.keys(result2)).toEqual([ + "__ROW_PATH__", + "C|x", + "C|y", + "B|x", + "B|y", + "A|x", + "A|y", + ]); view.delete(); table.delete(); }); - it("['y'] by ['x'] sorted by ['x'] desc has the correct # of columns", async function() { + it("['y'] by ['x'] sorted by ['x'] desc has the correct # of columns", async function () { var table = await perspective.table(data); var view = await table.view({ column_pivots: ["y"], row_pivots: ["x"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); let num_cols = await view.num_columns(); expect(num_cols).toEqual(12); @@ -2407,12 +2759,12 @@ module.exports = perspective => { }); }); - describe("Pivot table operations", function() { - it("Should not expand past number of row pivots", async function() { + describe("Pivot table operations", function () { + it("Should not expand past number of row pivots", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["x"], - column_pivots: ["y"] + column_pivots: ["y"], }); var expanded_idx = await view.expand(2); // invalid expands return the index @@ -2420,8 +2772,8 @@ module.exports = perspective => { }); }); - describe("Column paths", function() { - it("Should return all columns, 0-sided view from schema", async function() { + describe("Column paths", function () { + it("Should return all columns, 0-sided view from schema", async function () { const table = await perspective.table(meta); const view = await table.view(); const paths = await view.column_paths(); @@ -2430,10 +2782,10 @@ module.exports = perspective => { table.delete(); }); - it("Should return all columns in specified order, 0-sided view from schema", async function() { + it("Should return all columns in specified order, 0-sided view from schema", async function () { const table = await perspective.table(meta); const view = await table.view({ - columns: ["z", "y", "x"] + columns: ["z", "y", "x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["z", "y", "x"]); @@ -2441,10 +2793,10 @@ module.exports = perspective => { table.delete(); }); - it("Should return specified visible columns, 0-sided view from schema", async function() { + it("Should return specified visible columns, 0-sided view from schema", async function () { const table = await perspective.table(meta); const view = await table.view({ - columns: ["x"] + columns: ["x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["x"]); @@ -2452,7 +2804,7 @@ module.exports = perspective => { table.delete(); }); - it("Should return all columns, 0-sided view", async function() { + it("Should return all columns, 0-sided view", async function () { const table = await perspective.table(data); const view = await table.view(); const paths = await view.column_paths(); @@ -2461,10 +2813,10 @@ module.exports = perspective => { table.delete(); }); - it("Should return all columns in specified order, 0-sided view", async function() { + it("Should return all columns in specified order, 0-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ - columns: ["z", "y", "x"] + columns: ["z", "y", "x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["z", "y", "x"]); @@ -2472,10 +2824,10 @@ module.exports = perspective => { table.delete(); }); - it("Should return specified visible columns, 0-sided view", async function() { + it("Should return specified visible columns, 0-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ - columns: ["x"] + columns: ["x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["x"]); @@ -2483,10 +2835,10 @@ module.exports = perspective => { table.delete(); }); - it("Should return all columns with __ROW_PATH__, 1-sided view", async function() { + it("Should return all columns with __ROW_PATH__, 1-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ - row_pivots: ["x"] + row_pivots: ["x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "x", "y", "z"]); @@ -2494,12 +2846,12 @@ module.exports = perspective => { table.delete(); }); - it("Should return numerical column names in the correct order, 1-sided view", async function() { + it("Should return numerical column names in the correct order, 1-sided view", async function () { const table = await perspective.table({ - "2345": [0, 1, 2, 3], - "1.23456789": [0, 1, 2, 3], - "1234": [1, 2, 3, 4], - x: [5, 6, 7, 8] + 2345: [0, 1, 2, 3], + 1.23456789: [0, 1, 2, 3], + 1234: [1, 2, 3, 4], + x: [5, 6, 7, 8], }); // Previously, we iterated through the aggregates map using the @@ -2512,20 +2864,26 @@ module.exports = perspective => { columns: ["2345", "1234", "x", "1.23456789"], aggregates: { x: "sum", - "1234": "sum" - } + 1234: "sum", + }, }); const paths = await view.column_paths(); - expect(paths).toEqual(["__ROW_PATH__", "2345", "1234", "x", "1.23456789"]); + expect(paths).toEqual([ + "__ROW_PATH__", + "2345", + "1234", + "x", + "1.23456789", + ]); view.delete(); table.delete(); }); - it("Should return all columns in specified order, 1-sided view", async function() { + it("Should return all columns in specified order, 1-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ row_pivots: ["x"], - columns: ["z", "y", "x"] + columns: ["z", "y", "x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "z", "y", "x"]); @@ -2533,11 +2891,11 @@ module.exports = perspective => { table.delete(); }); - it("Should return specified visible columns with __ROW_PATH__, 1-sided view", async function() { + it("Should return specified visible columns with __ROW_PATH__, 1-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ columns: ["x"], - row_pivots: ["x"] + row_pivots: ["x"], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "x"]); @@ -2545,37 +2903,65 @@ module.exports = perspective => { table.delete(); }); - it("Should return all columns with __ROW_PATH__, 2-sided view", async function() { + it("Should return all columns with __ROW_PATH__, 2-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ row_pivots: ["x"], - column_pivots: ["y"] + column_pivots: ["y"], }); const paths = await view.column_paths(); - expect(paths).toEqual(["__ROW_PATH__", "a|x", "a|y", "a|z", "b|x", "b|y", "b|z", "c|x", "c|y", "c|z", "d|x", "d|y", "d|z"]); + expect(paths).toEqual([ + "__ROW_PATH__", + "a|x", + "a|y", + "a|z", + "b|x", + "b|y", + "b|z", + "c|x", + "c|y", + "c|z", + "d|x", + "d|y", + "d|z", + ]); view.delete(); table.delete(); }); - it("Should return specified visible columns with __ROW_PATH__, 2-sided view", async function() { + it("Should return specified visible columns with __ROW_PATH__, 2-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ columns: ["z", "y", "x"], row_pivots: ["x"], - column_pivots: ["y"] + column_pivots: ["y"], }); const paths = await view.column_paths(); - expect(paths).toEqual(["__ROW_PATH__", "a|z", "a|y", "a|x", "b|z", "b|y", "b|x", "c|z", "c|y", "c|x", "d|z", "d|y", "d|x"]); + expect(paths).toEqual([ + "__ROW_PATH__", + "a|z", + "a|y", + "a|x", + "b|z", + "b|y", + "b|x", + "c|z", + "c|y", + "c|x", + "d|z", + "d|y", + "d|x", + ]); view.delete(); table.delete(); }); - it("Should return specified visible columns with __ROW_PATH__, 2-sided view", async function() { + it("Should return specified visible columns with __ROW_PATH__, 2-sided view", async function () { const table = await perspective.table(data); const view = await table.view({ columns: ["x"], row_pivots: ["x"], - column_pivots: ["y"] + column_pivots: ["y"], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "a|x", "b|x", "c|x", "d|x"]); diff --git a/packages/perspective/test/js/ports.js b/packages/perspective/test/js/ports.js index 9b1131433f..13f770bae5 100644 --- a/packages/perspective/test/js/ports.js +++ b/packages/perspective/test/js/ports.js @@ -12,19 +12,19 @@ const data = { w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }; -const get_random_int = function(min, max) { +const get_random_int = function (min, max) { // liberally copied from stack min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; }; -module.exports = perspective => { - describe("ports", function() { - it("Should create port IDs in incremental order", async function() { +module.exports = (perspective) => { + describe("ports", function () { + it("Should create port IDs in incremental order", async function () { const table = await perspective.table(data); const port_ids = []; for (let i = 0; i < 10; i++) { @@ -34,7 +34,7 @@ module.exports = perspective => { table.delete(); }); - it("Should create port IDs in incremental order and allow updates on each port", async function() { + it("Should create port IDs in incremental order and allow updates on each port", async function () { const table = await perspective.table(data); const port_ids = []; @@ -50,7 +50,7 @@ module.exports = perspective => { w: [1.5], x: [port_id], y: ["d"], - z: [true] + z: [true], }, {port_id} ); @@ -62,10 +62,43 @@ module.exports = perspective => { expect(await table.size()).toEqual(14); const expected = { - w: [1.5, 2.5, 3.5, 4.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], + w: [ + 1.5, 2.5, 3.5, 4.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, + 1.5, 1.5, + ], x: [1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - y: ["a", "b", "c", "d", "d", "d", "d", "d", "d", "d", "d", "d", "d", "d"], - z: [true, false, true, false, true, true, true, true, true, true, true, true, true, true] + y: [ + "a", + "b", + "c", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + ], + z: [ + true, + false, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + ], }; expect(output).toEqual(expected); @@ -74,7 +107,7 @@ module.exports = perspective => { table.delete(); }); - it("Should create port IDs in incremental order and allow updates on each port, indexed", async function() { + it("Should create port IDs in incremental order and allow updates on each port, indexed", async function () { const table = await perspective.table(data, {index: "w"}); const port_ids = []; @@ -90,7 +123,7 @@ module.exports = perspective => { w: [1.5], x: [1], y: ["a"], - z: [true] + z: [true], }, {port_id} ); @@ -106,7 +139,7 @@ module.exports = perspective => { table.delete(); }); - it("Should create port IDs in incremental order and allow random arbitrary updates on each port, indexed", async function() { + it("Should create port IDs in incremental order and allow random arbitrary updates on each port, indexed", async function () { const table = await perspective.table(data, {index: "w"}); const port_ids = []; @@ -122,7 +155,7 @@ module.exports = perspective => { w: [1.5], x: [1], y: ["a"], - z: [true] + z: [true], }, {port_id} ); @@ -140,7 +173,7 @@ module.exports = perspective => { w: [5.5], x: [5], y: ["e"], - z: [true] + z: [true], }; table.update(update_data, {port_id: 0}); @@ -150,7 +183,7 @@ module.exports = perspective => { w: [1.5, 2.5, 3.5, 4.5, 5.5], x: [1, 2, 3, 4, 5], y: ["a", "b", "c", "d", "e"], - z: [true, false, true, false, true] + z: [true, false, true, false, true], }); // and do it again but this time with null as pkey @@ -164,7 +197,7 @@ module.exports = perspective => { w: [null], x: [6], y: ["f"], - z: [true] + z: [true], }; table.update(update_data2, {port_id: port2}); @@ -173,15 +206,15 @@ module.exports = perspective => { w: [null, 1.5, 2.5, 3.5, 4.5, 5.5], x: [6, 1, 2, 3, 4, 5], y: ["f", "a", "b", "c", "d", "e"], - z: [true, true, false, true, false, true] + z: [true, true, false, true, false, true], }); view.delete(); table.delete(); }); - describe("View notifications from different ports", function() { - it("All views should be notified by appends on all ports", async function() { + describe("View notifications from different ports", function () { + it("All views should be notified by appends on all ports", async function () { const table = await perspective.table(data); const port_ids = []; @@ -194,12 +227,12 @@ module.exports = perspective => { const view = await table.view(); const view2 = await table.view({ - row_pivots: ["w"] + row_pivots: ["w"], }); const view3 = await table.view({ row_pivots: ["w"], - column_pivots: ["x"] + column_pivots: ["x"], }); for (const port_id of port_ids) { @@ -208,7 +241,7 @@ module.exports = perspective => { w: [1.5], x: [port_id], y: ["d"], - z: [true] + z: [true], }, {port_id} ); @@ -219,10 +252,43 @@ module.exports = perspective => { expect(await table.size()).toEqual(14); const expected = { - w: [1.5, 2.5, 3.5, 4.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], + w: [ + 1.5, 2.5, 3.5, 4.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, + 1.5, 1.5, 1.5, + ], x: [1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - y: ["a", "b", "c", "d", "d", "d", "d", "d", "d", "d", "d", "d", "d", "d"], - z: [true, false, true, false, true, true, true, true, true, true, true, true, true, true] + y: [ + "a", + "b", + "c", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + "d", + ], + z: [ + true, + false, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + ], }; expect(output).toEqual(expected); @@ -233,7 +299,7 @@ module.exports = perspective => { w: [27, 16.5, 2.5, 3.5, 4.5], x: [65, 56, 2, 3, 4], y: [14, 11, 1, 1, 1], - z: [14, 11, 1, 1, 1] + z: [14, 11, 1, 1, 1], }); const output3 = await view3.to_columns(); @@ -278,7 +344,7 @@ module.exports = perspective => { "10|w": [1.5, 1.5, null, null, null], "10|x": [10, 10, null, null, null], "10|y": [1, 1, null, null, null], - "10|z": [1, 1, null, null, null] + "10|z": [1, 1, null, null, null], }); view3.delete(); @@ -287,7 +353,7 @@ module.exports = perspective => { table.delete(); }); - it("All views should be notified by partial updates on all ports", async function() { + it("All views should be notified by partial updates on all ports", async function () { const table = await perspective.table(data, {index: "w"}); const port_ids = []; @@ -300,12 +366,12 @@ module.exports = perspective => { const view = await table.view(); const view2 = await table.view({ - row_pivots: ["w"] + row_pivots: ["w"], }); const view3 = await table.view({ row_pivots: ["w"], - column_pivots: ["x"] + column_pivots: ["x"], }); for (const port_id of port_ids) { @@ -314,7 +380,7 @@ module.exports = perspective => { w: [1.5], x: [port_id], y: ["d"], - z: [false] + z: [false], }, {port_id} ); @@ -328,7 +394,7 @@ module.exports = perspective => { w: [1.5, 2.5, 3.5, 4.5], x: [10, 2, 3, 4], y: ["d", "b", "c", "d"], - z: [false, false, true, false] + z: [false, false, true, false], }; expect(output).toEqual(expected); @@ -339,7 +405,7 @@ module.exports = perspective => { w: [12, 1.5, 2.5, 3.5, 4.5], x: [19, 10, 2, 3, 4], y: [4, 1, 1, 1, 1], - z: [4, 1, 1, 1, 1] + z: [4, 1, 1, 1, 1], }); const output3 = await view3.to_columns(); @@ -360,7 +426,7 @@ module.exports = perspective => { "10|w": [1.5, 1.5, null, null, null], "10|x": [10, 10, null, null, null], "10|y": [1, 1, null, null, null], - "10|z": [1, 1, null, null, null] + "10|z": [1, 1, null, null, null], }); view3.delete(); @@ -370,8 +436,8 @@ module.exports = perspective => { }); }); - describe("on_update notifications from different ports", function() { - it("All calls to on_update should contain the port ID", async function(done) { + describe("on_update notifications from different ports", function () { + it("All calls to on_update should contain the port ID", async function (done) { const table = await perspective.table(data); const port_ids = []; @@ -385,7 +451,7 @@ module.exports = perspective => { // because no updates were called in port 0, start at 1 let last_port_id = 1; - view.on_update(function(updated) { + view.on_update(function (updated) { expect(updated.port_id).toEqual(last_port_id); if (last_port_id == 10) { view.delete(); @@ -402,14 +468,14 @@ module.exports = perspective => { w: [1.5], x: [1], y: ["a"], - z: [true] + z: [true], }, {port_id} ); } }); - it("Only the port that was updated should be notified in on_update", async function(done) { + it("Only the port that was updated should be notified in on_update", async function (done) { const table = await perspective.table(data); const port_ids = []; @@ -423,7 +489,7 @@ module.exports = perspective => { const port_id = get_random_int(1, 9); - view.on_update(function(updated) { + view.on_update(function (updated) { expect(updated.port_id).toEqual(port_id); view.delete(); table.delete(); @@ -435,13 +501,13 @@ module.exports = perspective => { w: [1.5], x: [1], y: ["a"], - z: [true] + z: [true], }, {port_id} ); }); - it("All ports should be notified in creation order, regardless of what order the update is called.", async function(done) { + it("All ports should be notified in creation order, regardless of what order the update is called.", async function (done) { const table = await perspective.table(data); const port_ids = []; @@ -456,7 +522,7 @@ module.exports = perspective => { let last_port_id = 0; let num_updates = 0; - view.on_update(function(updated) { + view.on_update(function (updated) { expect(updated.port_id).toEqual(last_port_id); if (last_port_id == 10 && num_updates === 10) { view.delete(); @@ -468,7 +534,9 @@ module.exports = perspective => { } }); - const update_order = _.shuffle([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const update_order = _.shuffle([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + ]); for (const port_id of update_order) { table.update( @@ -476,16 +544,18 @@ module.exports = perspective => { w: [1.5], x: [1], y: ["a"], - z: [true] + z: [true], }, {port_id} ); } }); - it("On update callbacks should be able to ignore updates from certain ports.", async function(done) { + it("On update callbacks should be able to ignore updates from certain ports.", async function (done) { const table = await perspective.table(data); - const update_table = await perspective.table(await table.schema()); + const update_table = await perspective.table( + await table.schema() + ); const port_ids = []; for (let i = 0; i < 10; i++) { @@ -500,7 +570,7 @@ module.exports = perspective => { let num_updates = 0; view.on_update( - async function(updated) { + async function (updated) { expect(updated.port_id).toEqual(last_port_id); if (![0, 5, 7, 8, 9].includes(updated.port_id)) { @@ -515,7 +585,7 @@ module.exports = perspective => { w: [1.5, 1.5, 1.5, 1.5, 1.5, 1.5], x: [1, 2, 3, 4, 6, 10], y: ["a", "a", "a", "a", "a", "a"], - z: [true, true, true, true, true, true] + z: [true, true, true, true, true, true], }); update_view.delete(); update_table.delete(); @@ -530,7 +600,9 @@ module.exports = perspective => { {mode: "row"} ); - const update_order = _.shuffle([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const update_order = _.shuffle([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + ]); for (const port_id of update_order) { table.update( @@ -538,7 +610,7 @@ module.exports = perspective => { w: [1.5], x: [port_id], y: ["a"], - z: [true] + z: [true], }, {port_id} ); @@ -546,8 +618,8 @@ module.exports = perspective => { }); }); - describe("deltas", function() { - it("Deltas should be unique to each port", async function(done) { + describe("deltas", function () { + it("Deltas should be unique to each port", async function (done) { const table = await perspective.table(data); const port_ids = []; @@ -562,7 +634,7 @@ module.exports = perspective => { let update_count = 0; view.on_update( - async function(updated) { + async function (updated) { const _t = await perspective.table(updated.delta); const _v = await _t.view(); const result = await _v.to_columns(); @@ -572,14 +644,14 @@ module.exports = perspective => { w: [100], x: [first_port], y: ["first port"], - z: [false] + z: [false], }); } else if (updated.port_id == second_port) { expect(result).toEqual({ w: [200], x: [second_port], y: ["second port"], - z: [true] + z: [true], }); } @@ -608,7 +680,7 @@ module.exports = perspective => { w: [100], x: [first_port], y: ["first port"], - z: [false] + z: [false], }, {port_id: first_port} ); @@ -618,15 +690,15 @@ module.exports = perspective => { w: [200], x: [second_port], y: ["second port"], - z: [true] + z: [true], }, {port_id: second_port} ); }); }); - describe("Cross-table port operations", function() { - it("Should allow a client-server round trip without loops", async function(done) { + describe("Cross-table port operations", function () { + it("Should allow a client-server round trip without loops", async function (done) { const client_table = await perspective.table(data); const server_table = await perspective.table(data); const client_port = await client_table.make_port(); @@ -645,9 +717,11 @@ module.exports = perspective => { let CLIENT_TO_SERVER_UPDATES = 0; client_view.on_update( - async updated => { + async (updated) => { if (updated.port_id === client_port) { - server_table.update(updated.delta, {port_id: server_port}); + server_table.update(updated.delta, { + port_id: server_port, + }); CLIENT_TO_SERVER_UPDATES++; } else { // Should not pass forward that update, and break @@ -661,7 +735,7 @@ module.exports = perspective => { const server_view = await server_table.view(); server_view.on_update( - async updated => { + async (updated) => { if (updated.port_id !== server_port) { client_table.update(updated.delta); } else { @@ -684,7 +758,7 @@ module.exports = perspective => { client_table.update(data, {port_id: client_port}); }); - it("Should allow a client-server round trip without loops and server updates", async function(done) { + it("Should allow a client-server round trip without loops and server updates", async function (done) { const client_table = await perspective.table(data); const server_table = await perspective.table(data); const client_port = await client_table.make_port(); @@ -703,14 +777,18 @@ module.exports = perspective => { let CLIENT_TO_SERVER_UPDATES = 0; client_view.on_update( - async updated => { + async (updated) => { if (updated.port_id === client_port) { - server_table.update(updated.delta, {port_id: server_port}); + server_table.update(updated.delta, { + port_id: server_port, + }); CLIENT_TO_SERVER_UPDATES++; } else { // Should not pass forward that update, and break // the chain. - expect(CLIENT_TO_SERVER_UPDATES).toBeLessThanOrEqual(2); + expect( + CLIENT_TO_SERVER_UPDATES + ).toBeLessThanOrEqual(2); } }, {mode: "row"} @@ -719,7 +797,7 @@ module.exports = perspective => { const server_view = await server_table.view(); server_view.on_update( - async updated => { + async (updated) => { if (updated.port_id !== server_port) { client_table.update(updated.delta); } else { @@ -748,10 +826,16 @@ module.exports = perspective => { client_table.update(data, {port_id: client_port}); }); - it("Should allow multi client-server round trips without loops", async function(done) { - const client_table = await perspective.table(data, {index: "w"}); - const client_table2 = await perspective.table(data, {index: "w"}); - const server_table = await perspective.table(data, {index: "w"}); + it("Should allow multi client-server round trips without loops", async function (done) { + const client_table = await perspective.table(data, { + index: "w", + }); + const client_table2 = await perspective.table(data, { + index: "w", + }); + const server_table = await perspective.table(data, { + index: "w", + }); const client_port = await client_table.make_port(); const client_port2 = await client_table2.make_port(); @@ -775,10 +859,12 @@ module.exports = perspective => { const client_view2 = await client_table2.view(); client_view.on_update( - async updated => { + async (updated) => { // client1 and server handlers are as minimal as can be if (updated.port_id === client_port) { - server_table.update(updated.delta, {port_id: server_port}); + server_table.update(updated.delta, { + port_id: server_port, + }); } }, {mode: "row"} @@ -786,14 +872,14 @@ module.exports = perspective => { client_view2.on_update( // eslint-disable-next-line no-unused-vars - async updated => { + async (updated) => { // client2 only reads updates from other clients const results = await client_view2.to_columns(); expect(results).toEqual({ w: [1.5, 2.5, 3.5, 4.5], x: [1, 2, 300, 4], y: ["a", "b", "ccc", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }); expect(await client_view.to_columns()).toEqual(results); expect(await server_view.to_columns()).toEqual(results); @@ -812,7 +898,7 @@ module.exports = perspective => { // Simulate multiple connections to the server server_view.on_update( - async updated => { + async (updated) => { if (updated.port_id !== server_port) { client_table.update(updated.delta); } @@ -821,7 +907,7 @@ module.exports = perspective => { ); server_view.on_update( - async updated => { + async (updated) => { if (updated.port_id !== server_port2) { client_table2.update(updated.delta); } @@ -837,8 +923,8 @@ module.exports = perspective => { { w: 3.5, x: 300, - y: "ccc" - } + y: "ccc", + }, ], {port_id: client_port} ); diff --git a/packages/perspective/test/js/remote.spec.js b/packages/perspective/test/js/remote.spec.js index f524bc4a38..bbf0c2083f 100644 --- a/packages/perspective/test/js/remote.spec.js +++ b/packages/perspective/test/js/remote.spec.js @@ -12,7 +12,7 @@ const perspective = require("../../dist/cjs/perspective.node.js"); let server; let port; -describe("WebSocketManager", function() { +describe("WebSocketManager", function () { beforeAll(() => { server = new perspective.WebSocketServer({port: 0}); port = server._server.address().port; @@ -47,8 +47,10 @@ describe("WebSocketManager", function() { const client = perspective.websocket(`ws://localhost:${port}`); const client_table = client.open_table("test"); - client_table.view({columns: ["z"]}).catch(error => { - expect(error.message).toBe("Abort(): Invalid column 'z' found in View columns.\n"); + client_table.view({columns: ["z"]}).catch((error) => { + expect(error.message).toBe( + "Abort(): Invalid column 'z' found in View columns.\n" + ); }); const client_view = await client_table.view(); @@ -97,8 +99,10 @@ describe("WebSocketManager", function() { const client_1_table = client_1.open_table("test"); const client_2_table = client_2.open_table("test"); - client_1_table.view({columns: ["z"]}).catch(error => { - expect(error.message).toBe("Abort(): Invalid column 'z' found in View columns.\n"); + client_1_table.view({columns: ["z"]}).catch((error) => { + expect(error.message).toBe( + "Abort(): Invalid column 'z' found in View columns.\n" + ); }); const client_1_view = await client_1_table.view(); @@ -116,7 +120,7 @@ describe("WebSocketManager", function() { server.eject_table("test"); }); - it("sends updates to client on subscribe", async done => { + it("sends updates to client on subscribe", async (done) => { const data = [{x: 1}]; const table = await perspective.table(data); server.host_table("test", table); @@ -127,7 +131,7 @@ describe("WebSocketManager", function() { const client_view = await client_table.view(); // eslint-disable-next-line no-unused-vars const on_update = () => { - client_view.to_json().then(async updated_data => { + client_view.to_json().then(async (updated_data) => { server.eject_table("test"); expect(updated_data).toEqual([{x: 1}, {x: 2}]); await client.terminate(); @@ -136,7 +140,7 @@ describe("WebSocketManager", function() { }; client_view.on_update(on_update); - client_view.to_json().then(client_data => { + client_view.to_json().then((client_data) => { expect(client_data).toEqual(data); table.update([{x: 2}]); }); @@ -186,7 +190,7 @@ describe("WebSocketManager", function() { server.eject_table("test"); }); - it("Calls `update` and sends arraybuffers using `on_update`", async done => { + it("Calls `update` and sends arraybuffers using `on_update`", async (done) => { const data = [{x: 1}]; const table = await perspective.table(data); const view = await table.view(); @@ -194,7 +198,7 @@ describe("WebSocketManager", function() { let update_port; - const updater = async updated => { + const updater = async (updated) => { expect(updated.port_id).toEqual(update_port); expect(updated.delta instanceof ArrayBuffer).toEqual(true); expect(updated.delta.byteLength).toBeGreaterThan(0); diff --git a/packages/perspective/test/js/removes.spec.js b/packages/perspective/test/js/removes.spec.js index 07c0d13c54..d6729195cd 100644 --- a/packages/perspective/test/js/removes.spec.js +++ b/packages/perspective/test/js/removes.spec.js @@ -37,7 +37,7 @@ const _query = async (should_cache, table, config = {}, callback) => { const SCHEMA = { str: "string", int: "integer", - float: "float" + float: "float", }; describe("Removes", () => { @@ -50,12 +50,12 @@ describe("Removes", () => { describe(`View cached? ${cond}, index: ${idx}`, () => { it("Output consistent with filter", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; table.update(data); @@ -63,16 +63,18 @@ describe("Removes", () => { await query( table, { - filter: [["str", "!=", "2"]] + filter: [["str", "!=", "2"]], }, - async getter => { + async (getter) => { const pkey = 3; - table.remove([string_pkey ? pkey.toString() : pkey]); + table.remove([ + string_pkey ? pkey.toString() : pkey, + ]); expect(await getter()).toEqual({ str: ["1", "4", "5", "6"], int: [1, 4, 5, 6], - float: [0.5, 2, 2.5, 3] + float: [0.5, 2, 2.5, 3], }); } ); @@ -82,12 +84,12 @@ describe("Removes", () => { it("Output consistent with filter, empty string", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; data.str.push(""); @@ -99,23 +101,25 @@ describe("Removes", () => { await query( table, { - filter: [["str", "!=", "2"]] + filter: [["str", "!=", "2"]], }, - async getter => { + async (getter) => { const pkey = 3; - table.remove([string_pkey ? pkey.toString() : pkey]); + table.remove([ + string_pkey ? pkey.toString() : pkey, + ]); if (string_pkey) { expect(await getter()).toEqual({ str: ["", "1", "4", "5", "6"], int: [7, 1, 4, 5, 6], - float: [3.5, 0.5, 2, 2.5, 3] + float: [3.5, 0.5, 2, 2.5, 3], }); } else { expect(await getter()).toEqual({ str: ["1", "4", "5", "6", ""], int: [1, 4, 5, 6, 7], - float: [0.5, 2, 2.5, 3, 3.5] + float: [0.5, 2, 2.5, 3, 3.5], }); } } @@ -126,12 +130,12 @@ describe("Removes", () => { it("Output consistent with filter, null", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; data.str.push(null); @@ -143,23 +147,25 @@ describe("Removes", () => { await query( table, { - filter: [["str", "!=", "2"]] + filter: [["str", "!=", "2"]], }, - async getter => { + async (getter) => { const pkey = 3; - table.remove([string_pkey ? pkey.toString() : pkey]); + table.remove([ + string_pkey ? pkey.toString() : pkey, + ]); if (string_pkey) { expect(await getter()).toEqual({ str: [null, "1", "4", "5", "6"], int: [7, 1, 4, 5, 6], - float: [3.5, 0.5, 2, 2.5, 3] + float: [3.5, 0.5, 2, 2.5, 3], }); } else { expect(await getter()).toEqual({ str: ["1", "4", "5", "6", null], int: [1, 4, 5, 6, 7], - float: [0.5, 2, 2.5, 3, 3.5] + float: [0.5, 2, 2.5, 3, 3.5], }); } } @@ -170,39 +176,41 @@ describe("Removes", () => { it("Output consistent with filter, remove before view construction, sorted", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; table.update(data); - table.remove([1, 3, 5].map(x => (string_pkey ? x.toString() : x))); + table.remove( + [1, 3, 5].map((x) => (string_pkey ? x.toString() : x)) + ); await query( table, { - sort: [["str", "desc"]] + sort: [["str", "desc"]], }, - async getter => { + async (getter) => { expect(await getter()).toEqual({ str: ["6", "4", "2"], int: [6, 4, 2], - float: [3, 2, 1] + float: [3, 2, 1], }); table.update({ str: ["7", "9", "8"], int: [7, 9, 8], - float: [3.5, 4.5, 4] + float: [3.5, 4.5, 4], }); expect(await getter()).toEqual({ str: ["9", "8", "7", "6", "4", "2"], int: [9, 8, 7, 6, 4, 2], - float: [4.5, 4, 3.5, 3, 2, 1] + float: [4.5, 4, 3.5, 3, 2, 1], }); } ); @@ -212,12 +220,12 @@ describe("Removes", () => { it("Output consistent with filter, remove and add identical", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; table.update(data); @@ -225,28 +233,30 @@ describe("Removes", () => { await query( table, { - filter: [["str", "!=", "2"]] + filter: [["str", "!=", "2"]], }, - async getter => { + async (getter) => { const pkey = 3; - table.remove([string_pkey ? pkey.toString() : pkey]); + table.remove([ + string_pkey ? pkey.toString() : pkey, + ]); expect(await getter()).toEqual({ str: ["1", "4", "5", "6"], int: [1, 4, 5, 6], - float: [0.5, 2, 2.5, 3] + float: [0.5, 2, 2.5, 3], }); table.update({ str: ["7", "9", "8"], int: [7, 9, 8], - float: [3.5, 4.5, 4] + float: [3.5, 4.5, 4], }); expect(await getter()).toEqual({ str: ["1", "4", "5", "6", "7", "8", "9"], int: [1, 4, 5, 6, 7, 8, 9], - float: [0.5, 2, 2.5, 3, 3.5, 4, 4.5] + float: [0.5, 2, 2.5, 3, 3.5, 4, 4.5], }); } ); @@ -256,36 +266,44 @@ describe("Removes", () => { it("Output consistent with filter, add null and remove other", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; table.update(data); - await query(table, {}, async getter => { + await query(table, {}, async (getter) => { expect(await getter()).toEqual(data); table.update({ str: [string_pkey ? null : "7"], int: [string_pkey ? 7 : null], - float: [3.5] + float: [3.5], }); if (string_pkey) { expect(await getter()).toEqual({ - str: [null, 1, 2, 3, 4, 5, 6].map(x => (x ? x.toString() : x)), + str: [null, 1, 2, 3, 4, 5, 6].map((x) => + x ? x.toString() : x + ), int: [7, 1, 2, 3, 4, 5, 6], - float: [7, 1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [7, 1, 2, 3, 4, 5, 6].map( + (x) => x * 0.5 + ), }); } else { expect(await getter()).toEqual({ - str: [7, 1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [7, 1, 2, 3, 4, 5, 6].map((x) => + x.toString() + ), int: [null, 1, 2, 3, 4, 5, 6], - float: [7, 1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [7, 1, 2, 3, 4, 5, 6].map( + (x) => x * 0.5 + ), }); } @@ -293,15 +311,19 @@ describe("Removes", () => { if (string_pkey) { expect(await getter()).toEqual({ - str: [null, 1, 2, 4, 5, 6].map(x => (x ? x.toString() : x)), + str: [null, 1, 2, 4, 5, 6].map((x) => + x ? x.toString() : x + ), int: [7, 1, 2, 4, 5, 6], - float: [7, 1, 2, 4, 5, 6].map(x => x * 0.5) + float: [7, 1, 2, 4, 5, 6].map((x) => x * 0.5), }); } else { expect(await getter()).toEqual({ - str: [7, 1, 2, 4, 5, 6].map(x => x.toString()), + str: [7, 1, 2, 4, 5, 6].map((x) => + x.toString() + ), int: [null, 1, 2, 4, 5, 6], - float: [7, 1, 2, 4, 5, 6].map(x => x * 0.5) + float: [7, 1, 2, 4, 5, 6].map((x) => x * 0.5), }); } }); @@ -311,13 +333,13 @@ describe("Removes", () => { it("Output consistent with filter, remove and add new dataset", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; table.update(data); @@ -325,30 +347,34 @@ describe("Removes", () => { await query( table, { - filter: [["int", "!=", 3]] + filter: [["int", "!=", 3]], }, - async getter => { - table.remove([1, 2, 3, 4, 5, 6].map(x => (string_pkey ? x.toString() : x))); + async (getter) => { + table.remove( + [1, 2, 3, 4, 5, 6].map((x) => + string_pkey ? x.toString() : x + ) + ); expect(await getter()).toEqual({}); table.update({ str: ["def", "abc", "deff"], int: [1, 2, 4], - float: [100.5, 200.5, 300.5] + float: [100.5, 200.5, 300.5], }); if (string_pkey) { expect(await getter()).toEqual({ str: ["abc", "def", "deff"], int: [2, 1, 4], - float: [200.5, 100.5, 300.5] + float: [200.5, 100.5, 300.5], }); } else { expect(await getter()).toEqual({ str: ["def", "abc", "deff"], int: [1, 2, 4], - float: [100.5, 200.5, 300.5] + float: [100.5, 200.5, 300.5], }); } } @@ -360,12 +386,12 @@ describe("Removes", () => { it.skip("Output consistent with filter, remove and add null", async () => { // FIXME const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; const splice_idx = Math.floor(Math.random() * 6); @@ -378,12 +404,16 @@ describe("Removes", () => { await query( table, { - filter: [["float", "==", 3.5]] + filter: [["float", "==", 3.5]], }, - async getter => { + async (getter) => { table.remove([null]); const v = await table.view(); - console.log("str?", string_pkey, await v.to_columns()); + console.log( + "str?", + string_pkey, + await v.to_columns() + ); await v.delete(); expect(await getter()).toEqual({}); @@ -391,13 +421,13 @@ describe("Removes", () => { table.update({ str: [string_pkey ? null : "7"], int: [string_pkey ? 7 : null], - float: [3.5] + float: [3.5], }); expect(await getter()).toEqual({ str: [string_pkey ? null : "7"], int: [string_pkey ? 7 : null], - float: [3.5] + float: [3.5], }); } ); @@ -407,12 +437,12 @@ describe("Removes", () => { it.skip("Output consistent with filter, update null", async () => { const table = await perspective.table(SCHEMA, { - index: idx + index: idx, }); const data = { - str: [1, 2, 3, 4, 5, 6].map(x => x.toString()), + str: [1, 2, 3, 4, 5, 6].map((x) => x.toString()), int: [1, 2, 3, 4, 5, 6], - float: [1, 2, 3, 4, 5, 6].map(x => x * 0.5) + float: [1, 2, 3, 4, 5, 6].map((x) => x * 0.5), }; const splice_idx = Math.floor(Math.random() * 6); @@ -422,16 +452,22 @@ describe("Removes", () => { table.update(data); - await query(table, {}, async getter => { + await query(table, {}, async (getter) => { const expected = { str: data.str.slice(), int: data.int.slice(), - float: data.float.slice() + float: data.float.slice(), }; expect(await getter()).toEqual(expected); - table.update([{str: string_pkey ? null : "7", int: string_pkey ? 7 : null, float: 4.5}]); + table.update([ + { + str: string_pkey ? null : "7", + int: string_pkey ? 7 : null, + float: 4.5, + }, + ]); expected.float[splice_idx] = 4.5; diff --git a/packages/perspective/test/js/sort.js b/packages/perspective/test/js/sort.js index e075309a84..ac162ab0d4 100644 --- a/packages/perspective/test/js/sort.js +++ b/packages/perspective/test/js/sort.js @@ -11,68 +11,77 @@ const data = { w: [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5], x: [1, 2, 3, 4, 4, 3, 2, 1], y: ["a", "b", "c", "d", "a", "b", "c", "d"], - z: [true, false, true, false, true, false, true, false] + z: [true, false, true, false, true, false, true, false], }; const data2 = { w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "b", "c", "d", "e", "f", "g", "h"] + y: ["a", "b", "c", "d", "e", "f", "g", "h"], }; const data3 = { w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "a", "a", "a", "b", "b", "b", "b"] + y: ["a", "a", "a", "a", "b", "b", "b", "b"], }; -module.exports = perspective => { - describe("Sorts", function() { +module.exports = (perspective) => { + describe("Sorts", function () { describe("With nulls", () => { - it("asc", async function() { + it("asc", async function () { var table = await perspective.table(data2); var view = await table.view({ columns: ["x", "w"], - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); expect(json).toEqual({ w: [null, null, null, null, 1.5, 2.5, 3.5, 4.5], - x: [3, 4, 4, 3, 2, 1, 1, 2] + x: [3, 4, 4, 3, 2, 1, 1, 2], }); view.delete(); table.delete(); }); - it("desc", async function() { + it("desc", async function () { var table = await perspective.table(data2); var view = await table.view({ columns: ["x", "w"], - sort: [["w", "desc"]] + sort: [["w", "desc"]], }); const json = await view.to_columns(); expect(json).toEqual({ w: [4.5, 3.5, 2.5, 1.5, null, null, null, null], - x: [2, 1, 1, 2, 3, 4, 4, 3] + x: [2, 1, 1, 2, 3, 4, 4, 3], }); view.delete(); table.delete(); }); - it("asc datetime", async function() { + it("asc datetime", async function () { var table = await perspective.table({ - w: [new Date(2020, 0, 1, 12, 30, 45), new Date(2020, 0, 1), null, null, null, null, new Date(2008, 0, 1, 12, 30, 45), new Date(2020, 12, 1, 12, 30, 45)], + w: [ + new Date(2020, 0, 1, 12, 30, 45), + new Date(2020, 0, 1), + null, + null, + null, + null, + new Date(2008, 0, 1, 12, 30, 45), + new Date(2020, 12, 1, 12, 30, 45), + ], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "b", "c", "d", "e", "f", "g", "h"] + y: ["a", "b", "c", "d", "e", "f", "g", "h"], }); var view = await table.view({ columns: ["x", "w"], - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); @@ -85,25 +94,34 @@ module.exports = perspective => { new Date(2008, 0, 1, 12, 30, 45).getTime(), new Date(2020, 0, 1).getTime(), new Date(2020, 0, 1, 12, 30, 45).getTime(), - new Date(2020, 12, 1, 12, 30, 45).getTime() + new Date(2020, 12, 1, 12, 30, 45).getTime(), ], - x: [3, 4, 4, 3, 2, 2, 1, 1] + x: [3, 4, 4, 3, 2, 2, 1, 1], }); view.delete(); table.delete(); }); - it("desc datetime", async function() { + it("desc datetime", async function () { var table = await perspective.table({ - w: [new Date(2020, 0, 1, 12, 30, 45), new Date(2020, 0, 1), null, null, null, null, new Date(2008, 0, 1, 12, 30, 45), new Date(2020, 12, 1, 12, 30, 45)], + w: [ + new Date(2020, 0, 1, 12, 30, 45), + new Date(2020, 0, 1), + null, + null, + null, + null, + new Date(2008, 0, 1, 12, 30, 45), + new Date(2020, 12, 1, 12, 30, 45), + ], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "b", "c", "d", "e", "f", "g", "h"] + y: ["a", "b", "c", "d", "e", "f", "g", "h"], }); var view = await table.view({ columns: ["x", "w"], - sort: [["w", "desc"]] + sort: [["w", "desc"]], }); const json = await view.to_columns(); @@ -116,9 +134,9 @@ module.exports = perspective => { null, null, null, - null + null, ], - x: [1, 1, 2, 2, 3, 4, 4, 3] + x: [1, 1, 2, 2, 3, 4, 4, 3], }); view.delete(); @@ -126,98 +144,138 @@ module.exports = perspective => { }); }); - describe("With aggregates", function() { - describe("aggregates, in a sorted column with nulls", function() { - it("sum", async function() { + describe("With aggregates", function () { + describe("aggregates, in a sorted column with nulls", function () { + it("sum", async function () { var table = await perspective.table(data2); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { w: "sum", - x: "unique" + x: "unique", }, - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); expect(json).toEqual({ - __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + __ROW_PATH__: [ + [], + ["c"], + ["d"], + ["e"], + ["f"], + ["g"], + ["h"], + ["a"], + ["b"], + ], w: [12, 0, 0, 0, 0, 1.5, 2.5, 3.5, 4.5], - x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + x: [null, 3, 4, 4, 3, 2, 1, 1, 2], }); view.delete(); table.delete(); }); - it("sum of floats", async function() { + it("sum of floats", async function () { var table = await perspective.table({ w: [3.25, 4.51, null, null, null, null, 1.57, 2.59], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "b", "c", "d", "e", "f", "g", "h"] + y: ["a", "b", "c", "d", "e", "f", "g", "h"], }); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { w: "sum", - x: "unique" + x: "unique", }, - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); expect(json).toEqual({ - __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + __ROW_PATH__: [ + [], + ["c"], + ["d"], + ["e"], + ["f"], + ["g"], + ["h"], + ["a"], + ["b"], + ], w: [11.92, 0, 0, 0, 0, 1.57, 2.59, 3.25, 4.51], - x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + x: [null, 3, 4, 4, 3, 2, 1, 1, 2], }); view.delete(); table.delete(); }); - it("unique", async function() { + it("unique", async function () { var table = await perspective.table(data2); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { w: "unique", - x: "unique" + x: "unique", }, - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); expect(json).toEqual({ - __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + __ROW_PATH__: [ + [], + ["c"], + ["d"], + ["e"], + ["f"], + ["g"], + ["h"], + ["a"], + ["b"], + ], w: [null, null, null, null, null, 1.5, 2.5, 3.5, 4.5], - x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + x: [null, 3, 4, 4, 3, 2, 1, 1, 2], }); view.delete(); table.delete(); }); - it("avg", async function() { + it("avg", async function () { var table = await perspective.table(data2); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { w: "avg", - x: "unique" + x: "unique", }, - sort: [["w", "asc"]] + sort: [["w", "asc"]], }); const json = await view.to_columns(); expect(json).toEqual({ - __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + __ROW_PATH__: [ + [], + ["c"], + ["d"], + ["e"], + ["f"], + ["g"], + ["h"], + ["a"], + ["b"], + ], w: [3, null, null, null, null, 1.5, 2.5, 3.5, 4.5], - x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + x: [null, 3, 4, 4, 3, 2, 1, 1, 2], }); // Broken result: @@ -232,97 +290,97 @@ module.exports = perspective => { }); describe("Multiple hidden sort", () => { - it("sum", async function() { + it("sum", async function () { var table = await perspective.table(data3); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { - w: "sum" + w: "sum", }, sort: [ ["x", "desc"], - ["w", "desc"] - ] + ["w", "desc"], + ], }); const json = await view.to_columns(); expect(json).toEqual({ __ROW_PATH__: [[], ["a"], ["b"]], w: [12, 8, 4], - x: [20, 10, 10] + x: [20, 10, 10], }); view.delete(); table.delete(); }); - it("sum of floats", async function() { + it("sum of floats", async function () { var table = await perspective.table({ w: [3.25, 4.51, null, null, null, null, 1.57, 2.59], x: [1, 2, 3, 4, 4, 3, 2, 1], - y: ["a", "a", "a", "a", "b", "b", "b", "b"] + y: ["a", "a", "a", "a", "b", "b", "b", "b"], }); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { - w: "sum" + w: "sum", }, sort: [ ["x", "desc"], - ["w", "desc"] - ] + ["w", "desc"], + ], }); const json = await view.to_columns(); expect(json).toEqual({ __ROW_PATH__: [[], ["a"], ["b"]], w: [11.92, 7.76, 4.16], - x: [20, 10, 10] + x: [20, 10, 10], }); view.delete(); table.delete(); }); - it("unique", async function() { + it("unique", async function () { var table = await perspective.table(data3); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { - w: "unique" + w: "unique", }, sort: [ ["x", "desc"], - ["w", "desc"] - ] + ["w", "desc"], + ], }); const json = await view.to_columns(); expect(json).toEqual({ __ROW_PATH__: [[], ["a"], ["b"]], w: [null, null, null], - x: [20, 10, 10] + x: [20, 10, 10], }); view.delete(); table.delete(); }); - it("avg", async function() { + it("avg", async function () { var table = await perspective.table(data3); var view = await table.view({ columns: ["x", "w"], row_pivots: ["y"], aggregates: { - w: "avg" + w: "avg", }, sort: [ ["x", "desc"], - ["w", "desc"] - ] + ["w", "desc"], + ], }); const json = await view.to_columns(); @@ -330,7 +388,7 @@ module.exports = perspective => { __ROW_PATH__: [[], ["a"], ["b"]], // 4 and 2 are the avg of the non-null rows w: [3, 4, 2], - x: [20, 10, 10] + x: [20, 10, 10], }); view.delete(); @@ -340,12 +398,12 @@ module.exports = perspective => { }); }); - describe("On hidden columns", function() { - it("Column path should not emit hidden sorts", async function() { + describe("On hidden columns", function () { + it("Column path should not emit hidden sorts", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w", "y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); const paths = await view.column_paths(); expect(paths).toEqual(["w", "y"]); @@ -353,13 +411,13 @@ module.exports = perspective => { table.delete(); }); - it("Column path should not emit hidden column sorts", async function() { + it("Column path should not emit hidden column sorts", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w"], row_pivots: ["y"], column_pivots: ["z"], - sort: [["x", "col desc"]] + sort: [["x", "col desc"]], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "false|w", "true|w"]); @@ -367,7 +425,7 @@ module.exports = perspective => { table.delete(); }); - it("Column path should not emit hidden regular and column sorts", async function() { + it("Column path should not emit hidden regular and column sorts", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w"], @@ -375,8 +433,8 @@ module.exports = perspective => { column_pivots: ["z"], sort: [ ["x", "col desc"], - ["y", "desc"] - ] + ["y", "desc"], + ], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "false|w", "true|w"]); @@ -384,11 +442,11 @@ module.exports = perspective => { table.delete(); }); - it("unpivoted", async function() { + it("unpivoted", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w", "y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); var answer = [ {w: 4.5, y: "d"}, @@ -398,7 +456,7 @@ module.exports = perspective => { {w: 2.5, y: "b"}, {w: 7.5, y: "c"}, {w: 1.5, y: "a"}, - {w: 8.5, y: "d"} + {w: 8.5, y: "d"}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -406,19 +464,19 @@ module.exports = perspective => { table.delete(); }); - it("row pivot ['y']", async function() { + it("row pivot ['y']", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w"], row_pivots: ["y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); var answer = [ {__ROW_PATH__: [], w: 40}, {__ROW_PATH__: ["a"], w: 7}, {__ROW_PATH__: ["b"], w: 9}, {__ROW_PATH__: ["c"], w: 11}, - {__ROW_PATH__: ["d"], w: 13} + {__ROW_PATH__: ["d"], w: 13}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -426,27 +484,27 @@ module.exports = perspective => { table.delete(); }); - it("row pivot and hidden sort ['y'] with aggregates specified", async function() { + it("row pivot and hidden sort ['y'] with aggregates specified", async function () { const table = await perspective.table({ x: [1, 2, 3, 4, 5], - y: ["a", "b", "b", "b", "c"] + y: ["a", "b", "b", "b", "c"], }); // Aggregate should be overriden if the sort column is hidden // AND also in row pivots const view = await table.view({ aggregates: { x: "sum", - y: "count" + y: "count", }, columns: ["x"], row_pivots: ["y"], - sort: [["y", "desc"]] + sort: [["y", "desc"]], }); const answer = [ {__ROW_PATH__: [], x: 15}, {__ROW_PATH__: ["c"], x: 5}, {__ROW_PATH__: ["b"], x: 9}, - {__ROW_PATH__: ["a"], x: 1} + {__ROW_PATH__: ["a"], x: 1}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -454,21 +512,21 @@ module.exports = perspective => { table.delete(); }); - it("row pivot and hidden sort ['y'] without aggregates specified", async function() { + it("row pivot and hidden sort ['y'] without aggregates specified", async function () { const table = await perspective.table({ x: [1, 2, 3, 4, 5], - y: ["a", "b", "b", "b", "c"] + y: ["a", "b", "b", "b", "c"], }); const view = await table.view({ columns: ["x"], row_pivots: ["y"], - sort: [["y", "desc"]] + sort: [["y", "desc"]], }); const answer = [ {__ROW_PATH__: [], x: 15}, {__ROW_PATH__: ["c"], x: 5}, {__ROW_PATH__: ["b"], x: 9}, - {__ROW_PATH__: ["a"], x: 1} + {__ROW_PATH__: ["a"], x: 1}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -476,12 +534,12 @@ module.exports = perspective => { table.delete(); }); - it("column pivot ['y']", async function() { + it("column pivot ['y']", async function () { const table = await perspective.table(data); const view = await table.view({ columns: ["w"], column_pivots: ["y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); const paths = await view.column_paths(); expect(paths).toEqual(["a|w", "b|w", "c|w", "d|w"]); @@ -493,7 +551,7 @@ module.exports = perspective => { {"a|w": null, "b|w": 2.5, "c|w": null, "d|w": null}, {"a|w": null, "b|w": null, "c|w": 7.5, "d|w": null}, {"a|w": 1.5, "b|w": null, "c|w": null, "d|w": null}, - {"a|w": null, "b|w": null, "c|w": null, "d|w": 8.5} + {"a|w": null, "b|w": null, "c|w": null, "d|w": 8.5}, ]; const result = await view.to_json(); expect(result).toEqual(answer); @@ -501,12 +559,12 @@ module.exports = perspective => { table.delete(); }); - it("column pivot ['y'], col desc sort", async function() { + it("column pivot ['y'], col desc sort", async function () { const table = await perspective.table(data); const view = await table.view({ columns: ["w"], column_pivots: ["y"], - sort: [["x", "col desc"]] + sort: [["x", "col desc"]], }); const paths = await view.column_paths(); expect(paths).toEqual(["d|w", "c|w", "b|w", "a|w"]); @@ -514,7 +572,7 @@ module.exports = perspective => { "d|w": [null, null, null, 4.5, null, null, null, 8.5], "c|w": [null, null, 3.5, null, null, null, 7.5, null], "b|w": [null, 2.5, null, null, null, 6.5, null, null], - "a|w": [1.5, null, null, null, 5.5, null, null, null] + "a|w": [1.5, null, null, null, 5.5, null, null, null], }; const result = await view.to_columns(); expect(result).toEqual(answer); @@ -522,15 +580,15 @@ module.exports = perspective => { table.delete(); }); - it("column pivot and hidden sort ['y']", async function() { + it("column pivot and hidden sort ['y']", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["a", "a", "a", "b"] + y: ["a", "a", "a", "b"], }); const view = await table.view({ columns: ["x"], column_pivots: ["y"], - sort: [["y", "desc"]] + sort: [["y", "desc"]], }); const paths = await view.column_paths(); @@ -540,21 +598,21 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ "a|x": [null, 1, 2, 3], - "b|x": [4, null, null, null] + "b|x": [4, null, null, null], }); view.delete(); table.delete(); }); - it("column pivot and hidden col sort ['y']", async function() { + it("column pivot and hidden col sort ['y']", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["a", "a", "a", "b"] + y: ["a", "a", "a", "b"], }); const view = await table.view({ columns: ["x"], column_pivots: ["y"], - sort: [["y", "col desc"]] + sort: [["y", "col desc"]], }); const paths = await view.column_paths(); @@ -563,16 +621,16 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ "b|x": [null, null, null, 4], - "a|x": [1, 2, 3, null] + "a|x": [1, 2, 3, null], }); view.delete(); table.delete(); }); - it("column pivot and hidden sort ['y'] with aggregates specified", async function() { + it("column pivot and hidden sort ['y'] with aggregates specified", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["a", "a", "a", "b"] + y: ["a", "a", "a", "b"], }); // Aggregate for hidden sort should be ignored in column-only, @@ -582,8 +640,8 @@ module.exports = perspective => { column_pivots: ["y"], sort: [["y", "desc"]], aggregates: { - y: "count" - } + y: "count", + }, }); const paths = await view.column_paths(); @@ -593,16 +651,16 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ "a|x": [null, 1, 2, 3], - "b|x": [4, null, null, null] + "b|x": [4, null, null, null], }); view.delete(); table.delete(); }); - it("column pivot and hidden col sort ['y'] with aggregates specified", async function() { + it("column pivot and hidden col sort ['y'] with aggregates specified", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["a", "a", "a", "b"] + y: ["a", "a", "a", "b"], }); // Aggregate for hidden sort should be ignored in column-only, @@ -612,8 +670,8 @@ module.exports = perspective => { column_pivots: ["y"], sort: [["y", "col desc"]], aggregates: { - y: "count" - } + y: "count", + }, }); const paths = await view.column_paths(); @@ -622,22 +680,22 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ "b|x": [null, null, null, 4], - "a|x": [1, 2, 3, null] + "a|x": [1, 2, 3, null], }); view.delete(); table.delete(); }); - it("column pivot ['y'] with overridden aggregates", async function() { + it("column pivot ['y'] with overridden aggregates", async function () { const table = await perspective.table({ x: [1, 2, 3, 4], - y: ["a", "a", "a", "b"] + y: ["a", "a", "a", "b"], }); const view = await table.view({ columns: ["x"], column_pivots: ["y"], aggregates: {y: "count"}, - sort: [["y", "col desc"]] + sort: [["y", "col desc"]], }); const paths = await view.column_paths(); @@ -647,20 +705,20 @@ module.exports = perspective => { let result = await view.to_columns(); expect(result).toEqual({ "b|x": [null, null, null, 4], - "a|x": [1, 2, 3, null] + "a|x": [1, 2, 3, null], }); view.delete(); table.delete(); }); - it("column pivot ['y'] with extra aggregates", async function() { + it("column pivot ['y'] with extra aggregates", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w"], column_pivots: ["y"], aggregates: {w: "sum", z: "last"}, - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); var answer = [ {"a|w": null, "b|w": null, "c|w": null, "d|w": 4.5}, @@ -670,7 +728,7 @@ module.exports = perspective => { {"a|w": null, "b|w": 2.5, "c|w": null, "d|w": null}, {"a|w": null, "b|w": null, "c|w": 7.5, "d|w": null}, {"a|w": 1.5, "b|w": null, "c|w": null, "d|w": null}, - {"a|w": null, "b|w": null, "c|w": null, "d|w": 8.5} + {"a|w": null, "b|w": null, "c|w": null, "d|w": 8.5}, ]; let result = await view.to_json(); expect(result).toEqual(answer); @@ -678,11 +736,11 @@ module.exports = perspective => { table.delete(); }); - it("row pivot ['x'], column pivot ['y'], both hidden and asc sorted", async function() { + it("row pivot ['x'], column pivot ['y'], both hidden and asc sorted", async function () { const table = await perspective.table({ x: ["a", "a", "b", "c"], y: ["x", "x", "y", "x"], - z: [1, 2, 3, 4] + z: [1, 2, 3, 4], }); const view = await table.view({ columns: ["z"], @@ -690,23 +748,27 @@ module.exports = perspective => { column_pivots: ["y"], sort: [ ["x", "asc"], - ["y", "col asc"] - ] + ["y", "col asc"], + ], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "x|z", "y|z"]); - const expected = {__ROW_PATH__: [[], ["a"], ["b"], ["c"]], "x|z": [7, 3, null, 4], "y|z": [3, null, 3, null]}; + const expected = { + __ROW_PATH__: [[], ["a"], ["b"], ["c"]], + "x|z": [7, 3, null, 4], + "y|z": [3, null, 3, null], + }; const result = await view.to_columns(); expect(result).toEqual(expected); view.delete(); table.delete(); }); - it("row pivot ['x'], column pivot ['y'], both hidden and desc sorted", async function() { + it("row pivot ['x'], column pivot ['y'], both hidden and desc sorted", async function () { const table = await perspective.table({ x: ["a", "a", "b", "c"], y: ["x", "x", "y", "x"], - z: [1, 2, 3, 4] + z: [1, 2, 3, 4], }); const view = await table.view({ columns: ["z"], @@ -714,24 +776,28 @@ module.exports = perspective => { column_pivots: ["y"], sort: [ ["x", "desc"], - ["y", "col desc"] - ] + ["y", "col desc"], + ], }); const paths = await view.column_paths(); expect(paths).toEqual(["__ROW_PATH__", "y|z", "x|z"]); - const expected = {__ROW_PATH__: [[], ["c"], ["b"], ["a"]], "y|z": [3, null, 3, null], "x|z": [7, 4, null, 3]}; + const expected = { + __ROW_PATH__: [[], ["c"], ["b"], ["a"]], + "y|z": [3, null, 3, null], + "x|z": [7, 4, null, 3], + }; const result = await view.to_columns(); expect(result).toEqual(expected); view.delete(); table.delete(); }); - it("column pivot ['y'] has correct # of columns", async function() { + it("column pivot ['y'] has correct # of columns", async function () { var table = await perspective.table(data); var view = await table.view({ columns: ["w"], column_pivots: ["y"], - sort: [["x", "desc"]] + sort: [["x", "desc"]], }); let result = await view.num_columns(); expect(result).toEqual(4); diff --git a/packages/perspective/test/js/sync_load.spec.js b/packages/perspective/test/js/sync_load.spec.js index d820198e4d..b2343eefec 100644 --- a/packages/perspective/test/js/sync_load.spec.js +++ b/packages/perspective/test/js/sync_load.spec.js @@ -7,10 +7,12 @@ * */ -describe("perspective.js module", function() { +describe("perspective.js module", function () { it("does not access the WASM module until it is ready", async () => { - const tbl = require("../../dist/cjs/perspective.node.js").table([{x: 1}]); - tbl.then(async table => { + const tbl = require("../../dist/cjs/perspective.node.js").table([ + {x: 1}, + ]); + tbl.then(async (table) => { const size = await table.size(); expect(size).toEqual(1); }); diff --git a/packages/perspective/test/js/test_arrows.js b/packages/perspective/test/js/test_arrows.js index 125d78c461..7db8dabc0b 100644 --- a/packages/perspective/test/js/test_arrows.js +++ b/packages/perspective/test/js/test_arrows.js @@ -24,26 +24,59 @@ const path = require("path"); */ function load_arrow(arrow_path) { const data = fs.readFileSync(arrow_path); - return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); + return data.buffer.slice( + data.byteOffset, + data.byteOffset + data.byteLength + ); } -const chunked_arrow = load_arrow(path.join(__dirname, "..", "arrow", "chunked.arrow")); -const test_null_arrow = load_arrow(path.join(__dirname, "..", "arrow", "test_null.arrow")); -const test_arrow = load_arrow(path.join(__dirname, "..", "arrow", "test.arrow")); -const partial_arrow = load_arrow(path.join(__dirname, "..", "arrow", "partial.arrow")); -const partial_missing_rows_arrow = load_arrow(path.join(__dirname, "..", "arrow", "partial_missing_rows.arrow")); -const int_float_str_arrow = load_arrow(path.join(__dirname, "..", "arrow", "int_float_str.arrow")); -const int_float_str_update_arrow = load_arrow(path.join(__dirname, "..", "arrow", "int_float_str_update.arrow")); -const int_float_str_file_arrow = load_arrow(path.join(__dirname, "..", "arrow", "int_float_str_file.arrow")); -const date32_arrow = load_arrow(path.join(__dirname, "..", "arrow", "date32.arrow")); -const date64_arrow = load_arrow(path.join(__dirname, "..", "arrow", "date64.arrow")); -const dict_arrow = load_arrow(path.join(__dirname, "..", "arrow", "dict.arrow")); -const dict_update_arrow = load_arrow(path.join(__dirname, "..", "arrow", "dict_update.arrow")); -const numbers_arrow = load_arrow(path.join(__dirname, "..", "arrow", "number_types.arrow")); -const all_types_arrow = load_arrow(path.join(__dirname, "..", "arrow", "all_types_small.arrow")); +const chunked_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "chunked.arrow") +); +const test_null_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "test_null.arrow") +); +const test_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "test.arrow") +); +const partial_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "partial.arrow") +); +const partial_missing_rows_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "partial_missing_rows.arrow") +); +const int_float_str_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "int_float_str.arrow") +); +const int_float_str_update_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "int_float_str_update.arrow") +); +const int_float_str_file_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "int_float_str_file.arrow") +); +const date32_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "date32.arrow") +); +const date64_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "date64.arrow") +); +const dict_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "dict.arrow") +); +const dict_update_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "dict_update.arrow") +); +const numbers_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "number_types.arrow") +); +const all_types_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "all_types_small.arrow") +); // uint8-64 x2, int8-64 x2, date, datetime, bool, string -const all_types_multi_arrow = load_arrow(path.join(__dirname, "..", "arrow", "all_types_small_multi.arrow")); +const all_types_multi_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "all_types_small_multi.arrow") +); module.exports = { chunked_arrow, @@ -60,5 +93,5 @@ module.exports = { dict_update_arrow, numbers_arrow, all_types_arrow, - all_types_multi_arrow + all_types_multi_arrow, }; diff --git a/packages/perspective/test/js/timezone/timezone.spec.js b/packages/perspective/test/js/timezone/timezone.spec.js index 24236fcaee..3cf95cab13 100644 --- a/packages/perspective/test/js/timezone/timezone.spec.js +++ b/packages/perspective/test/js/timezone/timezone.spec.js @@ -14,7 +14,7 @@ const date_data = [ {x: new Date(2020, 2, 8)}, // 2020/03/08 {x: new Date(2020, 9, 1)}, // 2020/10/01 {x: new Date(2020, 10, 1)}, // 2020/11/01 - {x: new Date(2020, 11, 31)} // 2020/12/31 + {x: new Date(2020, 11, 31)}, // 2020/12/31 ]; const date_data_local = [ @@ -22,7 +22,7 @@ const date_data_local = [ {x: new Date(2020, 2, 8).toLocaleDateString()}, // 2020/03/08 {x: new Date(2020, 9, 1).toLocaleDateString()}, // 2020/10/01 {x: new Date(2020, 10, 1).toLocaleDateString()}, // 2020/11/01 - {x: new Date(2020, 11, 31).toLocaleDateString()} // 2020/12/31 + {x: new Date(2020, 11, 31).toLocaleDateString()}, // 2020/12/31 ]; const datetime_data = [ @@ -32,7 +32,7 @@ const datetime_data = [ {x: new Date(2020, 2, 8, 2, 0, 1)}, // 2020/03/8 02:00:01 GMT-0500 {x: new Date(2020, 9, 1, 15, 11, 55)}, // 2020/10/01 15:30:55 GMT-0400 {x: new Date(2020, 10, 1, 19, 29, 55)}, // 2020/11/01 19:30:55 GMT-0400 - {x: new Date(2020, 11, 31, 7, 42, 55)} // 2020/12/31 07:30:55 GMT-0500 + {x: new Date(2020, 11, 31, 7, 42, 55)}, // 2020/12/31 07:30:55 GMT-0500 ]; const datetime_data_local = [ @@ -42,7 +42,7 @@ const datetime_data_local = [ {x: new Date(2020, 2, 8, 2, 0, 1).toLocaleString()}, // 2020/03/8 02:00:01 GMT-0500 {x: new Date(2020, 9, 1, 15, 11, 55).toLocaleString()}, // 2020/10/01 15:30:55 GMT-0400 {x: new Date(2020, 10, 1, 19, 29, 55).toLocaleString()}, // 2020/11/01 19:30:55 GMT-0400 - {x: new Date(2020, 11, 31, 7, 42, 55).toLocaleString()} // 2020/12/31 07:30:55 GMT-0500 + {x: new Date(2020, 11, 31, 7, 42, 55).toLocaleString()}, // 2020/12/31 07:30:55 GMT-0500 ]; /** @@ -56,8 +56,12 @@ const check_datetime = (output, expected) => { expect(output.length).toEqual(expected.length); for (let i = 0; i < output.length; i++) { let date = new Date(output[i]["x"]); - expect(date.toLocaleString()).toEqual(expected[i]["x"].toLocaleString()); - expect(date.getTimezoneOffset()).toEqual(expected[i]["x"].getTimezoneOffset()); + expect(date.toLocaleString()).toEqual( + expected[i]["x"].toLocaleString() + ); + expect(date.getTimezoneOffset()).toEqual( + expected[i]["x"].getTimezoneOffset() + ); } }; @@ -69,7 +73,10 @@ describe("Timezone Tests", () => { // offset is 240 during DST and 300 during non-DST, since we manually // set the timezone here just assert the timezone string - if (now.toString().includes("GMT-0400") || now.toString().includes("Eastern Daylight Time")) { + if ( + now.toString().includes("GMT-0400") || + now.toString().includes("Eastern Daylight Time") + ) { expect(new Date().getTimezoneOffset()).toBe(240); } else { expect(new Date().getTimezoneOffset()).toBe(300); @@ -94,7 +101,9 @@ describe("Timezone Tests", () => { it("== filters date correctly from Date()", async () => { const table = await perspective.table(date_data); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "==", date_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "==", date_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, [date_data[2]]); }); @@ -102,7 +111,9 @@ describe("Timezone Tests", () => { it("> filters date correctly from Date()", async () => { const table = await perspective.table(date_data); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", ">", date_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">", date_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(3)); }); @@ -110,7 +121,9 @@ describe("Timezone Tests", () => { it("< filters date correctly from Date()", async () => { const table = await perspective.table(date_data); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "<", date_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<", date_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(0, 2)); }); @@ -118,7 +131,9 @@ describe("Timezone Tests", () => { it(">= filters date correctly from Date()", async () => { const table = await perspective.table(date_data); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", ">=", date_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">=", date_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(2)); }); @@ -126,7 +141,9 @@ describe("Timezone Tests", () => { it("< filters date correctly from Date()", async () => { const table = await perspective.table(date_data); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "<=", date_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<=", date_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(0, 3)); }); @@ -147,7 +164,7 @@ describe("Timezone Tests", () => { // Converts automatically to ISO-formatted string const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); const view = await table.view(); @@ -167,18 +184,22 @@ describe("Timezone Tests", () => { for (let i = 0; i < data.length; i++) { let date = new Date(data[i]["x"]); - expect(date.toLocaleDateString()).toEqual(date_data_local[i]["x"]); + expect(date.toLocaleDateString()).toEqual( + date_data_local[i]["x"] + ); } }); it("== filters date correctly from ISO date string", async () => { const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "==", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "==", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, [date_data[2]]); }); @@ -186,11 +207,13 @@ describe("Timezone Tests", () => { it("> filters date correctly from ISO date string", async () => { const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", ">", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(3)); }); @@ -198,11 +221,13 @@ describe("Timezone Tests", () => { it("< filters date correctly from ISO date string", async () => { const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "<", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(0, 2)); }); @@ -210,11 +235,13 @@ describe("Timezone Tests", () => { it(">= filters date correctly from ISO date string", async () => { const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", ">=", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">=", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(2)); }); @@ -222,11 +249,13 @@ describe("Timezone Tests", () => { it("< filters date correctly from ISO date string", async () => { const deepcopy = JSON.parse(JSON.stringify(date_data)); const table = await perspective.table({ - x: "date" + x: "date", }); table.update(deepcopy); expect(await table.schema()).toEqual({x: "date"}); - const view = await table.view({filter: [["x", "<=", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<=", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, date_data.slice(0, 3)); }); @@ -248,7 +277,9 @@ describe("Timezone Tests", () => { it("== filters datetime correctly from Date()", async () => { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "==", datetime_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "==", datetime_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, [datetime_data[2]]); }); @@ -256,7 +287,9 @@ describe("Timezone Tests", () => { it("> filters datetime correctly from Date()", async () => { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", ">", datetime_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">", datetime_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(3)); }); @@ -264,7 +297,9 @@ describe("Timezone Tests", () => { it("< filters datetime correctly from Date()", async () => { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "<", datetime_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<", datetime_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(0, 2)); }); @@ -272,7 +307,9 @@ describe("Timezone Tests", () => { it(">= filters datetime correctly from Date()", async () => { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", ">=", datetime_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">=", datetime_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(2)); }); @@ -280,7 +317,9 @@ describe("Timezone Tests", () => { it("< filters datetime correctly from Date()", async () => { const table = await perspective.table(datetime_data); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "<=", datetime_data[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<=", datetime_data[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(0, 3)); }); @@ -309,7 +348,9 @@ describe("Timezone Tests", () => { for (let i = 0; i < data.length; i++) { let date = new Date(data[i]["x"]); - expect(date.toLocaleString()).toEqual(" " + datetime_data_local[i]["x"]); + expect(date.toLocaleString()).toEqual( + " " + datetime_data_local[i]["x"] + ); } }); @@ -317,7 +358,9 @@ describe("Timezone Tests", () => { const deepcopy = JSON.parse(JSON.stringify(datetime_data)); const table = await perspective.table(deepcopy); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "==", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "==", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, [datetime_data[2]]); }); @@ -326,7 +369,9 @@ describe("Timezone Tests", () => { const deepcopy = JSON.parse(JSON.stringify(datetime_data)); const table = await perspective.table(deepcopy); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", ">", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(3)); }); @@ -335,7 +380,9 @@ describe("Timezone Tests", () => { const deepcopy = JSON.parse(JSON.stringify(datetime_data)); const table = await perspective.table(deepcopy); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "<", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(0, 2)); }); @@ -344,7 +391,9 @@ describe("Timezone Tests", () => { const deepcopy = JSON.parse(JSON.stringify(datetime_data)); const table = await perspective.table(deepcopy); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", ">=", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", ">=", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(2)); }); @@ -353,7 +402,9 @@ describe("Timezone Tests", () => { const deepcopy = JSON.parse(JSON.stringify(datetime_data)); const table = await perspective.table(deepcopy); expect(await table.schema()).toEqual({x: "datetime"}); - const view = await table.view({filter: [["x", "<=", deepcopy[2]["x"]]]}); + const view = await table.view({ + filter: [["x", "<=", deepcopy[2]["x"]]], + }); let data = await view.to_json(); check_datetime(data, datetime_data.slice(0, 3)); }); diff --git a/packages/perspective/test/js/to_format.js b/packages/perspective/test/js/to_format.js index 09f6184e53..a48d079989 100644 --- a/packages/perspective/test/js/to_format.js +++ b/packages/perspective/test/js/to_format.js @@ -11,62 +11,97 @@ const int_float_string_data = [ {int: 1, float: 2.25, string: "a", datetime: new Date()}, {int: 2, float: 3.5, string: "b", datetime: new Date()}, {int: 3, float: 4.75, string: "c", datetime: new Date()}, - {int: 4, float: 5.25, string: "d", datetime: new Date()} + {int: 4, float: 5.25, string: "d", datetime: new Date()}, ]; const pivoted_output = [ - {__ROW_PATH__: [], int: 10, float: 15.75, string: 4, datetime: 4, __INDEX__: [3, 2, 1, 0]}, - {__ROW_PATH__: [1], int: 1, float: 2.25, string: 1, datetime: 1, __INDEX__: [0]}, - {__ROW_PATH__: [2], int: 2, float: 3.5, string: 1, datetime: 1, __INDEX__: [1]}, - {__ROW_PATH__: [3], int: 3, float: 4.75, string: 1, datetime: 1, __INDEX__: [2]}, - {__ROW_PATH__: [4], int: 4, float: 5.25, string: 1, datetime: 1, __INDEX__: [3]} + { + __ROW_PATH__: [], + int: 10, + float: 15.75, + string: 4, + datetime: 4, + __INDEX__: [3, 2, 1, 0], + }, + { + __ROW_PATH__: [1], + int: 1, + float: 2.25, + string: 1, + datetime: 1, + __INDEX__: [0], + }, + { + __ROW_PATH__: [2], + int: 2, + float: 3.5, + string: 1, + datetime: 1, + __INDEX__: [1], + }, + { + __ROW_PATH__: [3], + int: 3, + float: 4.75, + string: 1, + datetime: 1, + __INDEX__: [2], + }, + { + __ROW_PATH__: [4], + int: 4, + float: 5.25, + string: 1, + datetime: 1, + __INDEX__: [3], + }, ]; -module.exports = perspective => { - describe("data slice", function() { - it("should filter out invalid start rows", async function() { +module.exports = (perspective) => { + describe("data slice", function () { + it("should filter out invalid start rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ - start_row: 5 + start_row: 5, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should filter out invalid start columns", async function() { + it("should filter out invalid start columns", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ - start_col: 5 + start_col: 5, }); expect(json).toEqual([{}, {}, {}, {}]); view.delete(); table.delete(); }); - it("should filter out invalid start rows & columns", async function() { + it("should filter out invalid start rows & columns", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ start_row: 5, - start_col: 5 + start_col: 5, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should filter out invalid start rows based on view", async function() { + it("should filter out invalid start rows based on view", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - filter: [["float", ">", 3.5]] + filter: [["float", ">", 3.5]], }); // valid on view() but not this filtered view let json = await view.to_json({ - start_row: 3 + start_row: 3, }); expect(json).toEqual([]); @@ -75,14 +110,14 @@ module.exports = perspective => { table.delete(); }); - it("should filter out invalid start columns based on view", async function() { + it("should filter out invalid start columns based on view", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - columns: ["float", "int"] + columns: ["float", "int"], }); let json = await view.to_json({ - start_col: 2 + start_col: 2, }); expect(json).toEqual([{}, {}, {}, {}]); @@ -90,27 +125,27 @@ module.exports = perspective => { table.delete(); }); - it("should filter out invalid start rows & columns based on view", async function() { + it("should filter out invalid start rows & columns based on view", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ columns: ["float", "int"], - filter: [["float", ">", 3.5]] + filter: [["float", ">", 3.5]], }); let json = await view.to_json({ start_row: 5, - start_col: 5 + start_col: 5, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should respect start/end rows", async function() { + it("should respect start/end rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ start_row: 2, - end_row: 3 + end_row: 3, }); let comparator = int_float_string_data[2]; comparator.datetime = +comparator.datetime; @@ -119,15 +154,15 @@ module.exports = perspective => { table.delete(); }); - it("should respect end rows when larger than data size", async function() { + it("should respect end rows when larger than data size", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ start_row: 2, - end_row: 6 + end_row: 6, }); expect(json).toEqual( - int_float_string_data.slice(2).map(x => { + int_float_string_data.slice(2).map((x) => { x.datetime = +x.datetime; return x; }) @@ -136,27 +171,29 @@ module.exports = perspective => { table.delete(); }); - it("should respect start/end columns", async function() { + it("should respect start/end columns", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_columns({ start_col: 2, - end_col: 3 + end_col: 3, }); - let comparator = {string: int_float_string_data.map(d => d.string)}; + let comparator = { + string: int_float_string_data.map((d) => d.string), + }; expect(json).toEqual(comparator); view.delete(); table.delete(); }); - it("should floor float start rows", async function() { + it("should floor float start rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ - start_row: 1.5 + start_row: 1.5, }); expect(json).toEqual( - int_float_string_data.slice(1).map(x => { + int_float_string_data.slice(1).map((x) => { x.datetime = +x.datetime; return x; }) @@ -165,17 +202,17 @@ module.exports = perspective => { table.delete(); }); - it("should ceil float end rows", async function() { + it("should ceil float end rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ - end_row: 1.5 + end_row: 1.5, }); expect(json).toEqual( // deep copy JSON.parse(JSON.stringify(int_float_string_data)) .slice(0, 2) - .map(x => { + .map((x) => { x.datetime = +new Date(x.datetime); return x; }) @@ -184,12 +221,12 @@ module.exports = perspective => { table.delete(); }); - it("should floor/ceil float start/end rows", async function() { + it("should floor/ceil float start/end rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ start_row: 2.9, - end_row: 2.4 + end_row: 2.4, }); let comparator = int_float_string_data[2]; comparator.datetime = +comparator.datetime; @@ -198,15 +235,15 @@ module.exports = perspective => { table.delete(); }); - it("should ceil float end rows when larger than data size", async function() { + it("should ceil float end rows when larger than data size", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ start_row: 2, - end_row: 5.5 + end_row: 5.5, }); expect(json).toEqual( - int_float_string_data.slice(2).map(x => { + int_float_string_data.slice(2).map((x) => { x.datetime = +x.datetime; return x; }) @@ -215,23 +252,25 @@ module.exports = perspective => { table.delete(); }); - it("should floor/ceil float start/end columns", async function() { + it("should floor/ceil float start/end columns", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_columns({ start_col: 2.6, - end_col: 2.4 + end_col: 2.4, }); - let comparator = {string: int_float_string_data.map(d => d.string)}; + let comparator = { + string: int_float_string_data.map((d) => d.string), + }; expect(json).toEqual(comparator); view.delete(); table.delete(); }); - it("one-sided views should have row paths", async function() { + it("one-sided views should have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - row_pivots: ["int"] + row_pivots: ["int"], }); let json = await view.to_json(); for (let d of json) { @@ -241,10 +280,10 @@ module.exports = perspective => { table.delete(); }); - it("one-sided views with start_col > 0 should have row paths", async function() { + it("one-sided views with start_col > 0 should have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - row_pivots: ["int"] + row_pivots: ["int"], }); let json = await view.to_json({start_col: 1}); for (let d of json) { @@ -254,10 +293,10 @@ module.exports = perspective => { table.delete(); }); - it("one-sided column-only views should not have row paths", async function() { + it("one-sided column-only views should not have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - column_pivots: ["int"] + column_pivots: ["int"], }); let json = await view.to_json(); for (let d of json) { @@ -267,44 +306,46 @@ module.exports = perspective => { table.delete(); }); - it("column-only views should not have header rows", async function() { + it("column-only views should not have header rows", async function () { let table = await perspective.table([ {x: 1, y: "a"}, - {x: 2, y: "b"} + {x: 2, y: "b"}, ]); let view = await table.view({ - column_pivots: ["x"] + column_pivots: ["x"], }); let json = await view.to_json(); expect(json).toEqual([ {"1|x": 1, "1|y": "a", "2|x": null, "2|y": null}, - {"1|x": null, "1|y": null, "2|x": 2, "2|y": "b"} + {"1|x": null, "1|y": null, "2|x": 2, "2|y": "b"}, ]); view.delete(); table.delete(); }); - it("column-only views should return correct windows of data", async function() { + it("column-only views should return correct windows of data", async function () { let table = await perspective.table([ {x: 1, y: "a"}, - {x: 2, y: "b"} + {x: 2, y: "b"}, ]); let view = await table.view({ - column_pivots: ["x"] + column_pivots: ["x"], }); let json = await view.to_json({ - start_row: 1 + start_row: 1, }); - expect(json).toEqual([{"1|x": null, "1|y": null, "2|x": 2, "2|y": "b"}]); + expect(json).toEqual([ + {"1|x": null, "1|y": null, "2|x": 2, "2|y": "b"}, + ]); view.delete(); table.delete(); }); - it("two-sided views should have row paths", async function() { + it("two-sided views should have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], - column_pivots: ["string"] + column_pivots: ["string"], }); let json = await view.to_json(); for (let d of json) { @@ -314,11 +355,11 @@ module.exports = perspective => { table.delete(); }); - it("two-sided views with start_col > 0 should have row paths", async function() { + it("two-sided views with start_col > 0 should have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], - column_pivots: ["string"] + column_pivots: ["string"], }); let json = await view.to_json({start_col: 1}); for (let d of json) { @@ -328,12 +369,12 @@ module.exports = perspective => { table.delete(); }); - it("two-sided sorted views with start_col > 0 should have row paths", async function() { + it("two-sided sorted views with start_col > 0 should have row paths", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], column_pivots: ["string"], - sort: [["string", "desc"]] + sort: [["string", "desc"]], }); let json = await view.to_json({start_col: 1}); for (let d of json) { @@ -344,13 +385,13 @@ module.exports = perspective => { }); }); - describe("to_json", function() { - it("should emit same number of column names as number of pivots", async function() { + describe("to_json", function () { + it("should emit same number of column names as number of pivots", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], column_pivots: ["float", "string"], - sort: [["int", "asc"]] + sort: [["int", "asc"]], }); let json = await view.to_json(); // Get the first emitted column name that is not __ROW_PATH__ @@ -361,63 +402,107 @@ module.exports = perspective => { table.delete(); }); - it("should return dates in native form by default", async function() { - let table = await perspective.table([{datetime: new Date("2016-06-13")}, {datetime: new Date("2016-06-14")}]); + it("should return dates in native form by default", async function () { + let table = await perspective.table([ + {datetime: new Date("2016-06-13")}, + {datetime: new Date("2016-06-14")}, + ]); let view = await table.view(); let json = await view.to_json(); - expect(json).toEqual([{datetime: 1465776000000}, {datetime: 1465862400000}]); + expect(json).toEqual([ + {datetime: 1465776000000}, + {datetime: 1465862400000}, + ]); view.delete(); table.delete(); }); - it("should return dates in readable format on passing string in options", async function() { - let table = await perspective.table([{datetime: new Date("2016-06-13")}, {datetime: new Date("2016-06-14")}]); + it("should return dates in readable format on passing string in options", async function () { + let table = await perspective.table([ + {datetime: new Date("2016-06-13")}, + {datetime: new Date("2016-06-14")}, + ]); let view = await table.view(); let json = await view.to_json({formatted: true}); - expect(json).toEqual([{datetime: "6/13/2016"}, {datetime: "6/14/2016"}]); + expect(json).toEqual([ + {datetime: "6/13/2016"}, + {datetime: "6/14/2016"}, + ]); view.delete(); table.delete(); }); - it("should return datetimes in readable format on passing string in options", async function() { - let table = await perspective.table([{datetime: new Date(2016, 0, 1, 0, 30)}, {datetime: new Date(2016, 5, 15, 19, 20)}]); + it("should return datetimes in readable format on passing string in options", async function () { + let table = await perspective.table([ + {datetime: new Date(2016, 0, 1, 0, 30)}, + {datetime: new Date(2016, 5, 15, 19, 20)}, + ]); let view = await table.view(); let json = await view.to_json({formatted: true}); - expect(json).toEqual([{datetime: "1/1/2016, 12:30:00 AM"}, {datetime: "6/15/2016, 7:20:00 PM"}]); + expect(json).toEqual([ + {datetime: "1/1/2016, 12:30:00 AM"}, + {datetime: "6/15/2016, 7:20:00 PM"}, + ]); view.delete(); table.delete(); }); }); - describe("leaves_only flag", function() { - it("only emits leaves when leaves_only is set", async function() { + describe("leaves_only flag", function () { + it("only emits leaves when leaves_only is set", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - row_pivots: ["int"] + row_pivots: ["int"], }); let json = await view.to_json({leaves_only: true}); expect(json).toEqual([ - {__ROW_PATH__: [1], datetime: 1, float: 2.25, int: 1, string: 1}, + { + __ROW_PATH__: [1], + datetime: 1, + float: 2.25, + int: 1, + string: 1, + }, {__ROW_PATH__: [2], datetime: 1, float: 3.5, int: 2, string: 1}, - {__ROW_PATH__: [3], datetime: 1, float: 4.75, int: 3, string: 1}, - {__ROW_PATH__: [4], datetime: 1, float: 5.25, int: 4, string: 1} + { + __ROW_PATH__: [3], + datetime: 1, + float: 4.75, + int: 3, + string: 1, + }, + { + __ROW_PATH__: [4], + datetime: 1, + float: 5.25, + int: 4, + string: 1, + }, ]); view.delete(); table.delete(); }); }); - describe("to_arrow()", function() { - it("serializes boolean arrays correctly", async function() { + describe("to_arrow()", function () { + it("serializes boolean arrays correctly", async function () { // prevent regression in boolean parsing let table = await perspective.table({ - bool: [true, false, true, false, true, false, false] + bool: [true, false, true, false, true, false, false], }); let view = await table.view(); let arrow = await view.to_arrow(); let json = await view.to_json(); - expect(json).toEqual([{bool: true}, {bool: false}, {bool: true}, {bool: false}, {bool: true}, {bool: false}, {bool: false}]); + expect(json).toEqual([ + {bool: true}, + {bool: false}, + {bool: true}, + {bool: false}, + {bool: true}, + {bool: false}, + {bool: false}, + ]); let table2 = await perspective.table(arrow); let view2 = await table2.view(); @@ -430,12 +515,12 @@ module.exports = perspective => { table.delete(); }); - it("does not break when booleans are undefined", async function() { + it("does not break when booleans are undefined", async function () { let table = await perspective.table([ {int: 1, bool: true}, {int: 2, bool: false}, {int: 3, bool: true}, - {int: 4, bool: undefined} + {int: 4, bool: undefined}, ]); let view = await table.view(); let arrow = await view.to_arrow(); @@ -445,7 +530,7 @@ module.exports = perspective => { {int: 1, bool: true}, {int: 2, bool: false}, {int: 3, bool: true}, - {int: 4, bool: null} + {int: 4, bool: null}, ]); let table2 = await perspective.table(arrow); @@ -459,12 +544,12 @@ module.exports = perspective => { table.delete(); }); - it("arrow output respects start/end rows", async function() { + it("arrow output respects start/end rows", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let arrow = await view.to_arrow({ start_row: 1, - end_row: 2 + end_row: 2, }); let json2 = await view.to_json(); //expect(arrow.byteLength).toEqual(1010); @@ -480,7 +565,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 0-sided", async function() { + it("Transitive arrow output 0-sided", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let arrow = await view.to_arrow(); @@ -498,9 +583,12 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 0-sided hidden sort", async function() { + it("Transitive arrow output 0-sided hidden sort", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({columns: ["float"], sort: [["string", "desc"]]}); + let view = await table.view({ + columns: ["float"], + sort: [["string", "desc"]], + }); let arrow = await view.to_arrow(); let json2 = await view.to_json(); //expect(arrow.byteLength).toEqual(1010); @@ -516,7 +604,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 0-sided, with row range", async function() { + it("Transitive arrow output 0-sided, with row range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let arrow = await view.to_arrow({start_row: 1, end_row: 3}); @@ -535,7 +623,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 0-sided, with col range", async function() { + it("Transitive arrow output 0-sided, with col range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let arrow = await view.to_arrow({start_col: 1, end_col: 3}); @@ -554,7 +642,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 1-sided", async function() { + it("Transitive arrow output 1-sided", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({row_pivots: ["string"]}); let json = await view.to_json(); @@ -564,7 +652,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -576,12 +664,15 @@ module.exports = perspective => { table.delete(); }); - it("Arrow output 1-sided mean", async function() { + it("Arrow output 1-sided mean", async function () { let table = await perspective.table({ float: [1.25, 2.25, 3.25, 4.25], - string: ["a", "a", "b", "b"] + string: ["a", "a", "b", "b"], + }); + let view = await table.view({ + row_pivots: ["string"], + aggregates: {float: "mean"}, }); - let view = await table.view({row_pivots: ["string"], aggregates: {float: "mean"}}); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); let view2 = await table2.view(); @@ -589,7 +680,7 @@ module.exports = perspective => { expect(result).toEqual({ float: [2.75, 1.75, 3.75], - string: [4, 2, 2] + string: [4, 2, 2], }); await view2.delete(); @@ -598,9 +689,12 @@ module.exports = perspective => { await table.delete(); }); - it("Transitive arrow output 1-sided mean", async function() { + it("Transitive arrow output 1-sided mean", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], aggregates: {float: "mean"}}); + let view = await table.view({ + row_pivots: ["string"], + aggregates: {float: "mean"}, + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -608,7 +702,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -620,9 +714,13 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 1-sided sorted mean", async function() { + it("Transitive arrow output 1-sided sorted mean", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], aggregates: {float: "mean"}, sort: [["string", "desc"]]}); + let view = await table.view({ + row_pivots: ["string"], + aggregates: {float: "mean"}, + sort: [["string", "desc"]], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -630,7 +728,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -641,9 +739,14 @@ module.exports = perspective => { view.delete(); table.delete(); }); - it("Transitive arrow output 1-sided hidden sort mean", async function() { + it("Transitive arrow output 1-sided hidden sort mean", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], aggregates: {float: "mean"}, columns: ["float", "int"], sort: [["string", "desc"]]}); + let view = await table.view({ + row_pivots: ["string"], + aggregates: {float: "mean"}, + columns: ["float", "int"], + sort: [["string", "desc"]], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -651,7 +754,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -663,7 +766,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 1-sided with row range", async function() { + it("Transitive arrow output 1-sided with row range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({row_pivots: ["string"]}); let json = await view.to_json({start_row: 1, end_row: 3}); @@ -673,7 +776,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -685,7 +788,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 1-sided with col range", async function() { + it("Transitive arrow output 1-sided with col range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({row_pivots: ["string"]}); let json = await view.to_json({start_col: 1, end_col: 3}); @@ -695,7 +798,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -707,9 +810,12 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided", async function() { + it("Transitive arrow output 2-sided", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], column_pivots: ["int"]}); + let view = await table.view({ + row_pivots: ["string"], + column_pivots: ["int"], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -717,7 +823,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -729,9 +835,13 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided sorted", async function() { + it("Transitive arrow output 2-sided sorted", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], column_pivots: ["int"], sort: [["int", "desc"]]}); + let view = await table.view({ + row_pivots: ["string"], + column_pivots: ["int"], + sort: [["int", "desc"]], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -739,7 +849,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -751,9 +861,12 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided with row range", async function() { + it("Transitive arrow output 2-sided with row range", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], column_pivots: ["int"]}); + let view = await table.view({ + row_pivots: ["string"], + column_pivots: ["int"], + }); let json = await view.to_json({start_row: 1, end_row: 3}); let arrow = await view.to_arrow({start_row: 1, end_row: 3}); let table2 = await perspective.table(arrow); @@ -761,7 +874,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -773,9 +886,12 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided with col range", async function() { + it("Transitive arrow output 2-sided with col range", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({row_pivots: ["string"], column_pivots: ["int"]}); + let view = await table.view({ + row_pivots: ["string"], + column_pivots: ["int"], + }); let json = await view.to_json({start_col: 1, end_col: 3}); let arrow = await view.to_arrow({start_col: 1, end_col: 3}); let table2 = await perspective.table(arrow); @@ -783,7 +899,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -795,7 +911,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided column only", async function() { + it("Transitive arrow output 2-sided column only", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({column_pivots: ["string"]}); let json = await view.to_json(); @@ -805,7 +921,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -817,9 +933,13 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided column only hidden sort", async function() { + it("Transitive arrow output 2-sided column only hidden sort", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({column_pivots: ["string"], columns: ["float"], sort: [["int", "desc"]]}); + let view = await table.view({ + column_pivots: ["string"], + columns: ["float"], + sort: [["int", "desc"]], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -829,7 +949,7 @@ module.exports = perspective => { console.log(json, json2); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -841,9 +961,12 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided column only sorted", async function() { + it("Transitive arrow output 2-sided column only sorted", async function () { let table = await perspective.table(int_float_string_data); - let view = await table.view({column_pivots: ["string"], sort: [["int", "desc"]]}); + let view = await table.view({ + column_pivots: ["string"], + sort: [["int", "desc"]], + }); let json = await view.to_json(); let arrow = await view.to_arrow(); let table2 = await perspective.table(arrow); @@ -853,7 +976,7 @@ module.exports = perspective => { console.log(json, json2); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -865,7 +988,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided column only row range", async function() { + it("Transitive arrow output 2-sided column only row range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({column_pivots: ["string"]}); let json = await view.to_json({start_row: 1, end_row: 3}); @@ -875,7 +998,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -887,7 +1010,7 @@ module.exports = perspective => { table.delete(); }); - it("Transitive arrow output 2-sided column only col range", async function() { + it("Transitive arrow output 2-sided column only col range", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({column_pivots: ["string"]}); let json = await view.to_json({start_col: 1, end_col: 3}); @@ -897,7 +1020,7 @@ module.exports = perspective => { let json2 = await view2.to_json(); expect(json2).toEqual( - json.map(x => { + json.map((x) => { delete x["__ROW_PATH__"]; return x; }) @@ -909,9 +1032,9 @@ module.exports = perspective => { table.delete(); }); - describe("to_format with index", function() { - describe("0-sided", function() { - it("should return correct pkey for unindexed table", async function() { + describe("to_format with index", function () { + describe("0-sided", function () { + it("should return correct pkey for unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ @@ -919,67 +1042,79 @@ module.exports = perspective => { end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 2.25, __INDEX__: [0]}]); view.delete(); table.delete(); }); - it("should return correct pkey for float indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "float"}); + it("should return correct pkey for float indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "float", + }); let view = await table.view(); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 2.25, __INDEX__: [2.25]}]); view.delete(); table.delete(); }); - it("should return correct pkey for string indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for string indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view(); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 2.25, __INDEX__: ["a"]}]); view.delete(); table.delete(); }); - it("should return correct pkey for date indexed table", async function() { + it("should return correct pkey for date indexed table", async function () { // default data generates the same datetime for each row, thus pkeys get collapsed const data = [ {int: 1, datetime: new Date()}, - {int: 2, datetime: new Date()} + {int: 2, datetime: new Date()}, ]; data[1].datetime.setDate(data[1].datetime.getDate() + 1); - let table = await perspective.table(data, {index: "datetime"}); + let table = await perspective.table(data, { + index: "datetime", + }); let view = await table.view(); let json = await view.to_json({ start_row: 1, end_row: 2, - index: true + index: true, }); - expect(json).toEqual([{int: 2, datetime: data[1].datetime.getTime(), __INDEX__: [data[1].datetime.getTime()]}]); + expect(json).toEqual([ + { + int: 2, + datetime: data[1].datetime.getTime(), + __INDEX__: [data[1].datetime.getTime()], + }, + ]); view.delete(); table.delete(); }); - it("should return correct pkey for all rows + columns on an unindexed table", async function() { + it("should return correct pkey for all rows + columns on an unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view(); let json = await view.to_json({ - index: true + index: true, }); for (let i = 0; i < json.length; i++) { @@ -989,11 +1124,13 @@ module.exports = perspective => { table.delete(); }); - it("should return correct pkey for all rows + columns on an indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for all rows + columns on an indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view(); let json = await view.to_json({ - index: true + index: true, }); let pkeys = ["a", "b", "c", "d"]; @@ -1006,86 +1143,104 @@ module.exports = perspective => { }); }); - describe("0-sided column subset", function() { - it("should return correct pkey for unindexed table", async function() { + describe("0-sided column subset", function () { + it("should return correct pkey for unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - columns: ["int", "datetime"] + columns: ["int", "datetime"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); - expect(json).toEqual([{datetime: int_float_string_data[0]["datetime"].getTime(), __INDEX__: [0]}]); + expect(json).toEqual([ + { + datetime: + int_float_string_data[0]["datetime"].getTime(), + __INDEX__: [0], + }, + ]); view.delete(); table.delete(); }); - it("should return correct pkey for float indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "float"}); + it("should return correct pkey for float indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "float", + }); let view = await table.view({ - columns: ["float", "int"] + columns: ["float", "int"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{int: 1, __INDEX__: [2.25]}]); view.delete(); table.delete(); }); - it("should return correct pkey for string indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for string indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view({ - columns: ["string", "datetime"] + columns: ["string", "datetime"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); - expect(json).toEqual([{datetime: int_float_string_data[0]["datetime"].getTime(), __INDEX__: ["a"]}]); + expect(json).toEqual([ + { + datetime: + int_float_string_data[0]["datetime"].getTime(), + __INDEX__: ["a"], + }, + ]); view.delete(); table.delete(); }); - it("should return correct pkey for date indexed table", async function() { + it("should return correct pkey for date indexed table", async function () { // default data generates the same datetime for each row, thus pkeys get collapsed const data = [ {int: 1, datetime: new Date()}, - {int: 2, datetime: new Date()} + {int: 2, datetime: new Date()}, ]; data[1].datetime.setDate(data[1].datetime.getDate() + 1); let table = await perspective.table(data, {index: "datetime"}); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ start_row: 1, end_row: 2, - index: true + index: true, }); - expect(json).toEqual([{int: 2, __INDEX__: [data[1].datetime.getTime()]}]); + expect(json).toEqual([ + {int: 2, __INDEX__: [data[1].datetime.getTime()]}, + ]); view.delete(); table.delete(); }); - it("should return correct pkey for all rows + columns on an unindexed table", async function() { + it("should return correct pkey for all rows + columns on an unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ - index: true + index: true, }); for (let i = 0; i < json.length; i++) { @@ -1095,11 +1250,13 @@ module.exports = perspective => { table.delete(); }); - it("should return correct pkey for all rows + columns on an indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for all rows + columns on an indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view(); let json = await view.to_json({ - index: true + index: true, }); let pkeys = ["a", "b", "c", "d"]; @@ -1111,150 +1268,158 @@ module.exports = perspective => { }); }); - describe("0-sided column subset invalid bounds", function() { - it("should return correct pkey for unindexed table, invalid column", async function() { + describe("0-sided column subset invalid bounds", function () { + it("should return correct pkey for unindexed table, invalid column", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{__INDEX__: [0]}]); view.delete(); table.delete(); }); - it("should not return pkey for unindexed table, invalid row", async function() { + it("should not return pkey for unindexed table, invalid row", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ start_row: 10, end_row: 15, - index: true + index: true, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should return correct pkey for float indexed table, invalid column", async function() { - let table = await perspective.table(int_float_string_data, {index: "float"}); + it("should return correct pkey for float indexed table, invalid column", async function () { + let table = await perspective.table(int_float_string_data, { + index: "float", + }); let view = await table.view({ - columns: ["float"] + columns: ["float"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{__INDEX__: [2.25]}]); view.delete(); table.delete(); }); - it("should not return pkey for float indexed table, invalid row", async function() { - let table = await perspective.table(int_float_string_data, {index: "float"}); + it("should not return pkey for float indexed table, invalid row", async function () { + let table = await perspective.table(int_float_string_data, { + index: "float", + }); let view = await table.view({ - columns: ["float"] + columns: ["float"], }); let json = await view.to_json({ start_row: 10, end_row: 15, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should return correct pkey for string indexed table, invalid column", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for string indexed table, invalid column", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view({ - columns: ["string"] + columns: ["string"], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{__INDEX__: ["a"]}]); view.delete(); table.delete(); }); - it("should not return pkey for string indexed table, invalid row", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should not return pkey for string indexed table, invalid row", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view({ - columns: ["string"] + columns: ["string"], }); let json = await view.to_json({ start_row: 10, end_row: 11, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([]); view.delete(); table.delete(); }); - it("should return correct pkey for date indexed table, invalid column", async function() { + it("should return correct pkey for date indexed table, invalid column", async function () { // default data generates the same datetime for each row, thus pkeys get collapsed const data = [ {int: 1, datetime: new Date()}, - {int: 2, datetime: new Date()} + {int: 2, datetime: new Date()}, ]; data[1].datetime.setDate(data[1].datetime.getDate() + 1); let table = await perspective.table(data, {index: "datetime"}); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ start_col: 1, start_col: 2, - index: true + index: true, }); expect(json).toEqual([ { - __INDEX__: [data[0].datetime.getTime()] + __INDEX__: [data[0].datetime.getTime()], }, { - __INDEX__: [data[1].datetime.getTime()] - } + __INDEX__: [data[1].datetime.getTime()], + }, ]); view.delete(); table.delete(); }); - it("should not return pkey for date indexed table, invalid row", async function() { + it("should not return pkey for date indexed table, invalid row", async function () { // default data generates the same datetime for each row, thus pkeys get collapsed const data = [ {int: 1, datetime: new Date()}, - {int: 2, datetime: new Date()} + {int: 2, datetime: new Date()}, ]; data[1].datetime.setDate(data[1].datetime.getDate() + 1); let table = await perspective.table(data, {index: "datetime"}); let view = await table.view({ - columns: ["int"] + columns: ["int"], }); let json = await view.to_json({ start_row: 11, start_row: 12, - index: true + index: true, }); expect(json).toEqual([]); view.delete(); @@ -1262,88 +1427,100 @@ module.exports = perspective => { }); }); - describe("0-sided sorted", function() { - it("should return correct pkey for unindexed table", async function() { + describe("0-sided sorted", function () { + it("should return correct pkey for unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - sort: [["float", "desc"]] + sort: [["float", "desc"]], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 5.25, __INDEX__: [3]}]); view.delete(); table.delete(); }); - it("should return correct pkey for float indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "float"}); + it("should return correct pkey for float indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "float", + }); let view = await table.view({ - sort: [["float", "desc"]] + sort: [["float", "desc"]], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 5.25, __INDEX__: [5.25]}]); view.delete(); table.delete(); }); - it("should return correct pkey for string indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for string indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view({ - sort: [["float", "desc"]] + sort: [["float", "desc"]], }); let json = await view.to_json({ start_row: 0, end_row: 1, start_col: 1, end_col: 2, - index: true + index: true, }); expect(json).toEqual([{float: 5.25, __INDEX__: ["d"]}]); view.delete(); table.delete(); }); - it("should return correct pkey for date indexed table", async function() { + it("should return correct pkey for date indexed table", async function () { // default data generates the same datetime for each row, // thus pkeys get collapsed const data = [ {int: 200, datetime: new Date()}, - {int: 100, datetime: new Date()} + {int: 100, datetime: new Date()}, ]; data[1].datetime.setDate(data[1].datetime.getDate() + 1); let table = await perspective.table(data, {index: "datetime"}); let view = await table.view({ - sort: [["int", "desc"]] + sort: [["int", "desc"]], }); let json = await view.to_json({ - index: true + index: true, }); expect(json).toEqual([ - {int: 200, datetime: data[0].datetime.getTime(), __INDEX__: [data[0].datetime.getTime()]}, - {int: 100, datetime: data[1].datetime.getTime(), __INDEX__: [data[1].datetime.getTime()]} + { + int: 200, + datetime: data[0].datetime.getTime(), + __INDEX__: [data[0].datetime.getTime()], + }, + { + int: 100, + datetime: data[1].datetime.getTime(), + __INDEX__: [data[1].datetime.getTime()], + }, ]); view.delete(); table.delete(); }); - it("should return correct pkey for all rows + columns on an unindexed table", async function() { + it("should return correct pkey for all rows + columns on an unindexed table", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - sort: [["float", "asc"]] + sort: [["float", "asc"]], }); let json = await view.to_json({ - index: true + index: true, }); for (let i = 0; i < json.length; i++) { @@ -1353,13 +1530,15 @@ module.exports = perspective => { table.delete(); }); - it("should return correct pkey for all rows + columns on an indexed table", async function() { - let table = await perspective.table(int_float_string_data, {index: "string"}); + it("should return correct pkey for all rows + columns on an indexed table", async function () { + let table = await perspective.table(int_float_string_data, { + index: "string", + }); let view = await table.view({ - sort: [["float", "desc"]] + sort: [["float", "desc"]], }); let json = await view.to_json({ - index: true + index: true, }); let pkeys = ["d", "c", "b", "a"]; @@ -1371,14 +1550,14 @@ module.exports = perspective => { }); }); - describe("1-sided", function() { - it("should generate pkeys of aggregated rows for 1-sided", async function() { + describe("1-sided", function () { + it("should generate pkeys of aggregated rows for 1-sided", async function () { let table = await perspective.table(int_float_string_data); let view = await table.view({ - row_pivots: ["int"] + row_pivots: ["int"], }); let json = await view.to_json({ - index: true + index: true, }); // total row contains all pkeys for underlying rows; each aggregated row should have pkeys for the rows that belong to it @@ -1388,16 +1567,16 @@ module.exports = perspective => { }); }); - describe("2-sided", function() { - it.skip("should generate pkey for 2-sided", async function() { + describe("2-sided", function () { + it.skip("should generate pkey for 2-sided", async function () { // 2-sided implicit pkeys do not work let table = await perspective.table(int_float_string_data); let view = await table.view({ row_pivots: ["int"], - column_pivots: ["float"] + column_pivots: ["float"], }); let json = await view.to_json({ - index: true + index: true, }); expect(json[0]["__INDEX__"]).toEqual([0, 1]); // total row should have indices for every row inside it diff --git a/packages/perspective/test/js/to_format_viewport.spec.js b/packages/perspective/test/js/to_format_viewport.spec.js index 28ddfe3316..5da2bef552 100644 --- a/packages/perspective/test/js/to_format_viewport.spec.js +++ b/packages/perspective/test/js/to_format_viewport.spec.js @@ -10,15 +10,52 @@ const perspective = require("../../dist/cjs/perspective.node.js"); const data = { - w: [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, -1.5, -3.5, -1.5, -4.5, -9.5, -5.5, -8.5, -7.5], + w: [ + 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, -1.5, -3.5, -1.5, -4.5, -9.5, + -5.5, -8.5, -7.5, + ], x: [1, 2, 3, 4, 4, 3, 2, 1, 3, 4, 2, 1, 4, 3, 1, 2], - y: ["a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d"], - z: [true, false, true, false, true, false, true, false, true, true, true, true, false, false, false, false] + y: [ + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + "a", + "b", + "c", + "d", + ], + z: [ + true, + false, + true, + false, + true, + false, + true, + false, + true, + true, + true, + true, + false, + false, + false, + false, + ], }; -describe("to_format viewport", function() { - describe("0 sided", function() { - it("start_col 0 is the first col", async function() { +describe("to_format viewport", function () { + describe("0 sided", function () { + it("start_col 0 is the first col", async function () { var table = await perspective.table(data); var view = await table.view({}); const cols = await view.to_columns({start_col: 0, end_col: 1}); @@ -27,7 +64,7 @@ describe("to_format viewport", function() { table.delete(); }); - it("start_col 2 is the second col", async function() { + it("start_col 2 is the second col", async function () { var table = await perspective.table(data); var view = await table.view({}); const cols = await view.to_columns({start_col: 1, end_col: 2}); @@ -36,7 +73,7 @@ describe("to_format viewport", function() { table.delete(); }); - it("start_col 0, end_col 2 is the first two columns", async function() { + it("start_col 0, end_col 2 is the first two columns", async function () { var table = await perspective.table(data); var view = await table.view({}); const cols = await view.to_columns({start_col: 0, end_col: 2}); @@ -46,111 +83,203 @@ describe("to_format viewport", function() { }); }); - describe("1 sided", function() { - it("start_col 0 is the first col", async function() { + describe("1 sided", function () { + it("start_col 0 is the first col", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.to_columns({start_col: 0, end_col: 1}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], w: [-2, -4, 0, 1, 1]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + w: [-2, -4, 0, 1, 1], + }); view.delete(); table.delete(); }); - it("start_col 2 is the second col", async function() { + it("start_col 2 is the second col", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.to_columns({start_col: 1, end_col: 2}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], x: [40, 12, 12, 8, 8]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + x: [40, 12, 12, 8, 8], + }); view.delete(); table.delete(); }); - it("start_col 0, end_col 2 is the first two columns", async function() { + it("start_col 0, end_col 2 is the first two columns", async function () { var table = await perspective.table(data); var view = await table.view({ - row_pivots: ["y"] + row_pivots: ["y"], }); const cols = await view.to_columns({start_col: 0, end_col: 2}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], w: [-2, -4, 0, 1, 1], x: [40, 12, 12, 8, 8]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + w: [-2, -4, 0, 1, 1], + x: [40, 12, 12, 8, 8], + }); view.delete(); table.delete(); }); }); - describe("2 sided", function() { - it("start_col 0 is the first col", async function() { + describe("2 sided", function () { + it("start_col 0 is the first col", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 0, end_col: 1}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], "false|w": [-9, -9.5, 3.5, -8.5, 5.5]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + "false|w": [-9, -9.5, 3.5, -8.5, 5.5], + }); view.delete(); table.delete(); }); - it("start_col 2 is the second col", async function() { + it("start_col 2 is the second col", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 1, end_col: 2}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], "false|x": [20, 4, 8, 1, 7]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + "false|x": [20, 4, 8, 1, 7], + }); view.delete(); table.delete(); }); - it("start_col 0, end_col 2 is the first two columns", async function() { + it("start_col 0, end_col 2 is the first two columns", async function () { var table = await perspective.table(data); var view = await table.view({ row_pivots: ["y"], - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 0, end_col: 2}); - expect(cols).toEqual({__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], "false|w": [-9, -9.5, 3.5, -8.5, 5.5], "false|x": [20, 4, 8, 1, 7]}); + expect(cols).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"]], + "false|w": [-9, -9.5, 3.5, -8.5, 5.5], + "false|x": [20, 4, 8, 1, 7], + }); view.delete(); table.delete(); }); }); - describe("column only", function() { - it("start_col 0 is the first col", async function() { + describe("column only", function () { + it("start_col 0 is the first col", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 0, end_col: 1}); - expect(cols).toEqual({"false|w": [null, 2.5, null, 4.5, null, 6.5, null, 8.5, null, null, null, null, -9.5, -5.5, -8.5, -7.5]}); + expect(cols).toEqual({ + "false|w": [ + null, + 2.5, + null, + 4.5, + null, + 6.5, + null, + 8.5, + null, + null, + null, + null, + -9.5, + -5.5, + -8.5, + -7.5, + ], + }); view.delete(); table.delete(); }); - it("start_col 2 is the second col", async function() { + it("start_col 2 is the second col", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 1, end_col: 2}); - expect(cols).toEqual({"false|x": [null, 2, null, 4, null, 3, null, 1, null, null, null, null, 4, 3, 1, 2]}); + expect(cols).toEqual({ + "false|x": [ + null, + 2, + null, + 4, + null, + 3, + null, + 1, + null, + null, + null, + null, + 4, + 3, + 1, + 2, + ], + }); view.delete(); table.delete(); }); - it("start_col 0, end_col 2 is the first two columns", async function() { + it("start_col 0, end_col 2 is the first two columns", async function () { var table = await perspective.table(data); var view = await table.view({ - column_pivots: ["z"] + column_pivots: ["z"], }); const cols = await view.to_columns({start_col: 0, end_col: 2}); expect(cols).toEqual({ - "false|w": [null, 2.5, null, 4.5, null, 6.5, null, 8.5, null, null, null, null, -9.5, -5.5, -8.5, -7.5], - "false|x": [null, 2, null, 4, null, 3, null, 1, null, null, null, null, 4, 3, 1, 2] + "false|w": [ + null, + 2.5, + null, + 4.5, + null, + 6.5, + null, + 8.5, + null, + null, + null, + null, + -9.5, + -5.5, + -8.5, + -7.5, + ], + "false|x": [ + null, + 2, + null, + 4, + null, + 3, + null, + 1, + null, + null, + null, + null, + 4, + 3, + 1, + 2, + ], }); view.delete(); table.delete(); diff --git a/packages/perspective/test/js/updates.js b/packages/perspective/test/js/updates.js index e9c64c9ce9..c32b0f5b47 100644 --- a/packages/perspective/test/js/updates.js +++ b/packages/perspective/test/js/updates.js @@ -14,57 +14,266 @@ var data = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var col_data = { x: [1, 2, 3, 4], y: ["a", "b", "c", "d"], - z: [true, false, true, false] + z: [true, false, true, false], }; var meta = { x: "integer", y: "string", - z: "boolean" + z: "boolean", }; var data_2 = [ {x: 3, y: "c", z: false}, {x: 4, y: "d", z: true}, {x: 5, y: "g", z: false}, - {x: 6, y: "h", z: true} + {x: 6, y: "h", z: true}, ]; const arrow_result = [ - {f32: 1.5, f64: 1.5, i64: 1, i32: 1, i16: 1, i8: 1, bool: true, char: "a", dict: "a", datetime: +new Date("2018-01-25")}, - {f32: 2.5, f64: 2.5, i64: 2, i32: 2, i16: 2, i8: 2, bool: false, char: "b", dict: "b", datetime: +new Date("2018-01-26")}, - {f32: 3.5, f64: 3.5, i64: 3, i32: 3, i16: 3, i8: 3, bool: true, char: "c", dict: "c", datetime: +new Date("2018-01-27")}, - {f32: 4.5, f64: 4.5, i64: 4, i32: 4, i16: 4, i8: 4, bool: false, char: "d", dict: "d", datetime: +new Date("2018-01-28")}, - {f32: 5.5, f64: 5.5, i64: 5, i32: 5, i16: 5, i8: 5, bool: true, char: "d", dict: "d", datetime: +new Date("2018-01-29")} + { + f32: 1.5, + f64: 1.5, + i64: 1, + i32: 1, + i16: 1, + i8: 1, + bool: true, + char: "a", + dict: "a", + datetime: +new Date("2018-01-25"), + }, + { + f32: 2.5, + f64: 2.5, + i64: 2, + i32: 2, + i16: 2, + i8: 2, + bool: false, + char: "b", + dict: "b", + datetime: +new Date("2018-01-26"), + }, + { + f32: 3.5, + f64: 3.5, + i64: 3, + i32: 3, + i16: 3, + i8: 3, + bool: true, + char: "c", + dict: "c", + datetime: +new Date("2018-01-27"), + }, + { + f32: 4.5, + f64: 4.5, + i64: 4, + i32: 4, + i16: 4, + i8: 4, + bool: false, + char: "d", + dict: "d", + datetime: +new Date("2018-01-28"), + }, + { + f32: 5.5, + f64: 5.5, + i64: 5, + i32: 5, + i16: 5, + i8: 5, + bool: true, + char: "d", + dict: "d", + datetime: +new Date("2018-01-29"), + }, ]; const arrow_partial_result = [ - {f32: 1.5, f64: 1.5, i64: 1, i32: 1, i16: 1, i8: 1, bool: false, char: "a", dict: "a", datetime: +new Date("2018-01-25")}, - {f32: 2.5, f64: 2.5, i64: 2, i32: 2, i16: 2, i8: 2, bool: true, char: "b", dict: "b", datetime: +new Date("2018-01-26")}, - {f32: 3.5, f64: 3.5, i64: 3, i32: 3, i16: 3, i8: 3, bool: false, char: "c", dict: "c", datetime: +new Date("2018-01-27")}, - {f32: 4.5, f64: 4.5, i64: 4, i32: 4, i16: 4, i8: 4, bool: true, char: "d", dict: "d", datetime: +new Date("2018-01-28")}, - {f32: 5.5, f64: 5.5, i64: 5, i32: 5, i16: 5, i8: 5, bool: false, char: "d", dict: "d", datetime: +new Date("2018-01-29")} + { + f32: 1.5, + f64: 1.5, + i64: 1, + i32: 1, + i16: 1, + i8: 1, + bool: false, + char: "a", + dict: "a", + datetime: +new Date("2018-01-25"), + }, + { + f32: 2.5, + f64: 2.5, + i64: 2, + i32: 2, + i16: 2, + i8: 2, + bool: true, + char: "b", + dict: "b", + datetime: +new Date("2018-01-26"), + }, + { + f32: 3.5, + f64: 3.5, + i64: 3, + i32: 3, + i16: 3, + i8: 3, + bool: false, + char: "c", + dict: "c", + datetime: +new Date("2018-01-27"), + }, + { + f32: 4.5, + f64: 4.5, + i64: 4, + i32: 4, + i16: 4, + i8: 4, + bool: true, + char: "d", + dict: "d", + datetime: +new Date("2018-01-28"), + }, + { + f32: 5.5, + f64: 5.5, + i64: 5, + i32: 5, + i16: 5, + i8: 5, + bool: false, + char: "d", + dict: "d", + datetime: +new Date("2018-01-29"), + }, ]; const arrow_partial_missing_result = [ - {f32: 1.5, f64: 1.5, i64: 1, i32: 1, i16: 1, i8: 1, bool: false, char: "a", dict: "a", datetime: +new Date("2018-01-25")}, - {f32: 2.5, f64: 2.5, i64: 2, i32: 2, i16: 2, i8: 2, bool: false, char: "b", dict: "b", datetime: +new Date("2018-01-26")}, - {f32: 3.5, f64: 3.5, i64: 3, i32: 3, i16: 3, i8: 3, bool: false, char: "c", dict: "c", datetime: +new Date("2018-01-27")}, - {f32: 4.5, f64: 4.5, i64: 4, i32: 4, i16: 4, i8: 4, bool: false, char: "d", dict: "d", datetime: +new Date("2018-01-28")}, - {f32: 5.5, f64: 5.5, i64: 5, i32: 5, i16: 5, i8: 5, bool: false, char: "d", dict: "d", datetime: +new Date("2018-01-29")} + { + f32: 1.5, + f64: 1.5, + i64: 1, + i32: 1, + i16: 1, + i8: 1, + bool: false, + char: "a", + dict: "a", + datetime: +new Date("2018-01-25"), + }, + { + f32: 2.5, + f64: 2.5, + i64: 2, + i32: 2, + i16: 2, + i8: 2, + bool: false, + char: "b", + dict: "b", + datetime: +new Date("2018-01-26"), + }, + { + f32: 3.5, + f64: 3.5, + i64: 3, + i32: 3, + i16: 3, + i8: 3, + bool: false, + char: "c", + dict: "c", + datetime: +new Date("2018-01-27"), + }, + { + f32: 4.5, + f64: 4.5, + i64: 4, + i32: 4, + i16: 4, + i8: 4, + bool: false, + char: "d", + dict: "d", + datetime: +new Date("2018-01-28"), + }, + { + f32: 5.5, + f64: 5.5, + i64: 5, + i32: 5, + i16: 5, + i8: 5, + bool: false, + char: "d", + dict: "d", + datetime: +new Date("2018-01-29"), + }, ]; const arrow_indexed_result = [ - {f32: 1.5, f64: 1.5, i64: 1, i32: 1, i16: 1, i8: 1, bool: true, char: "a", dict: "a", datetime: +new Date("2018-01-25")}, - {f32: 2.5, f64: 2.5, i64: 2, i32: 2, i16: 2, i8: 2, bool: false, char: "b", dict: "b", datetime: +new Date("2018-01-26")}, - {f32: 3.5, f64: 3.5, i64: 3, i32: 3, i16: 3, i8: 3, bool: true, char: "c", dict: "c", datetime: +new Date("2018-01-27")}, - {f32: 5.5, f64: 5.5, i64: 5, i32: 5, i16: 5, i8: 5, bool: true, char: "d", dict: "d", datetime: +new Date("2018-01-29")} + { + f32: 1.5, + f64: 1.5, + i64: 1, + i32: 1, + i16: 1, + i8: 1, + bool: true, + char: "a", + dict: "a", + datetime: +new Date("2018-01-25"), + }, + { + f32: 2.5, + f64: 2.5, + i64: 2, + i32: 2, + i16: 2, + i8: 2, + bool: false, + char: "b", + dict: "b", + datetime: +new Date("2018-01-26"), + }, + { + f32: 3.5, + f64: 3.5, + i64: 3, + i32: 3, + i16: 3, + i8: 3, + bool: true, + char: "c", + dict: "c", + datetime: +new Date("2018-01-27"), + }, + { + f32: 5.5, + f64: 5.5, + i64: 5, + i32: 5, + i16: 5, + i8: 5, + bool: true, + char: "d", + dict: "d", + datetime: +new Date("2018-01-29"), + }, ]; async function match_delta(perspective, delta, expected) { @@ -76,9 +285,9 @@ async function match_delta(perspective, delta, expected) { table.delete(); } -module.exports = perspective => { - describe("Removes", function() { - it("should not remove without explicit index", async function() { +module.exports = (perspective) => { + describe("Removes", function () { + it("should not remove without explicit index", async function () { const table = await perspective.table(meta); table.update(data); const view = await table.view(); @@ -92,7 +301,7 @@ module.exports = perspective => { table.delete(); }); - it("after an `update()`", async function() { + it("after an `update()`", async function () { const table = await perspective.table(meta, {index: "x"}); table.update(data); const view = await table.view(); @@ -106,7 +315,7 @@ module.exports = perspective => { table.delete(); }); - it("after a regular data load", async function() { + it("after a regular data load", async function () { const table = await perspective.table(data, {index: "x"}); const view = await table.view(); table.remove([1, 2]); @@ -119,7 +328,7 @@ module.exports = perspective => { table.delete(); }); - it("after an `update()`, string pkey", async function() { + it("after an `update()`, string pkey", async function () { const table = await perspective.table(meta, {index: "y"}); table.update(data); const view = await table.view(); @@ -133,7 +342,7 @@ module.exports = perspective => { table.delete(); }); - it("after a regular data load, string pkey", async function() { + it("after a regular data load, string pkey", async function () { const table = await perspective.table(data, {index: "y"}); const view = await table.view(); table.remove(["a", "b"]); @@ -146,20 +355,25 @@ module.exports = perspective => { table.delete(); }); - it("after an update, date pkey", async function() { - const datetimes = [new Date(2020, 0, 15), new Date(2020, 1, 15), new Date(2020, 2, 15), new Date(2020, 3, 15)]; + it("after an update, date pkey", async function () { + const datetimes = [ + new Date(2020, 0, 15), + new Date(2020, 1, 15), + new Date(2020, 2, 15), + new Date(2020, 3, 15), + ]; const table = await perspective.table( { x: "integer", y: "date", - z: "float" + z: "float", }, {index: "y"} ); table.update({ x: [1, 2, 3, 4], y: datetimes, - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view(); table.remove(datetimes.slice(0, 2)); @@ -168,27 +382,32 @@ module.exports = perspective => { expect(result).toEqual({ x: [3, 4], y: [1584230400000, 1586908800000], - z: [3.5, 4.5] + z: [3.5, 4.5], }); // expect(await table.size()).toEqual(2); view.delete(); table.delete(); }); - it("after an update, datetime pkey", async function() { - const datetimes = [new Date(2020, 0, 15), new Date(2020, 1, 15), new Date(2020, 2, 15), new Date(2020, 3, 15)]; + it("after an update, datetime pkey", async function () { + const datetimes = [ + new Date(2020, 0, 15), + new Date(2020, 1, 15), + new Date(2020, 2, 15), + new Date(2020, 3, 15), + ]; const table = await perspective.table( { x: "integer", y: "datetime", - z: "float" + z: "float", }, {index: "y"} ); table.update({ x: [1, 2, 3, 4], y: datetimes, - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }); const view = await table.view(); table.remove(datetimes.slice(0, 2)); @@ -197,20 +416,25 @@ module.exports = perspective => { expect(result).toEqual({ x: [3, 4], y: [1584230400000, 1586908800000], - z: [3.5, 4.5] + z: [3.5, 4.5], }); expect(await table.size()).toEqual(2); view.delete(); table.delete(); }); - it("after a regular data load, datetime pkey", async function() { - const datetimes = [new Date(2020, 0, 15), new Date(2020, 1, 15), new Date(2020, 2, 15), new Date(2020, 3, 15)]; + it("after a regular data load, datetime pkey", async function () { + const datetimes = [ + new Date(2020, 0, 15), + new Date(2020, 1, 15), + new Date(2020, 2, 15), + new Date(2020, 3, 15), + ]; const table = await perspective.table( { x: [1, 2, 3, 4], y: datetimes, - z: [1.5, 2.5, 3.5, 4.5] + z: [1.5, 2.5, 3.5, 4.5], }, {index: "y"} ); @@ -221,14 +445,14 @@ module.exports = perspective => { expect(result).toEqual({ x: [3, 4], y: [1584230400000, 1586908800000], - z: [3.5, 4.5] + z: [3.5, 4.5], }); expect(await table.size()).toEqual(2); view.delete(); table.delete(); }); - it("multiple single element removes", async function() { + it("multiple single element removes", async function () { const table = await perspective.table(meta, {index: "x"}); for (let i = 0; i < 100; i++) { table.update([{x: i, y: "test", z: false}]); @@ -245,12 +469,12 @@ module.exports = perspective => { table.delete(); }); - it("remove while pivoted, last and count", async function() { + it("remove while pivoted, last and count", async function () { const table = await perspective.table( { x: ["a"], y: ["A"], - idx: [1] + idx: [1], }, {index: "idx"} ); @@ -258,29 +482,29 @@ module.exports = perspective => { const view = await table.view({ aggregates: { x: "last", - y: "count" + y: "count", }, - row_pivots: ["y"] + row_pivots: ["y"], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"]], x: ["a", "a"], y: [1, 1], - idx: [1, 1] + idx: [1, 1], }); table.update({ x: ["b"], y: ["B"], - idx: [2] + idx: [2], }); expect(await view.to_columns()).toEqual({ __ROW_PATH__: [[], ["A"], ["B"]], x: ["b", "a", "b"], y: [2, 1, 1], - idx: [3, 1, 2] + idx: [3, 1, 2], }); table.remove([1]); @@ -289,7 +513,7 @@ module.exports = perspective => { __ROW_PATH__: [[], ["B"]], x: ["b", "b"], y: [1, 1], - idx: [2, 2] + idx: [2, 2], }); table.remove([2]); @@ -298,31 +522,31 @@ module.exports = perspective => { __ROW_PATH__: [[]], x: [""], y: [0], - idx: [0] + idx: [0], }); await view.delete(); await table.delete(); }); - it("Removes out of order", async function() { + it("Removes out of order", async function () { const table = await perspective.table( { x: "string", - y: "integer" + y: "integer", }, {index: "x"} ); table.update({ x: ["b", "1", "a", "c"], - y: [3, 1, 2, 4] + y: [3, 1, 2, 4], }); const view = await table.view(); expect(await view.to_columns()).toEqual({ x: ["1", "a", "b", "c"], - y: [1, 2, 3, 4] + y: [1, 2, 3, 4], }); table.remove(["a", "c", "1", "b"]); @@ -337,49 +561,49 @@ module.exports = perspective => { await table.delete(); }); - it("No-op on pkeys not in the set", async function() { + it("No-op on pkeys not in the set", async function () { const table = await perspective.table( { x: "string", - y: "integer" + y: "integer", }, {index: "x"} ); table.update({ x: ["b", "1", "a", "c"], - y: [3, 1, 2, 4] + y: [3, 1, 2, 4], }); const view = await table.view(); expect(await view.to_columns()).toEqual({ x: ["1", "a", "b", "c"], - y: [1, 2, 3, 4] + y: [1, 2, 3, 4], }); table.remove(["z", "ff", "2312", "b"]); expect(await view.to_columns()).toEqual({ x: ["1", "a", "c"], - y: [1, 2, 4] + y: [1, 2, 4], }); await view.delete(); await table.delete(); }); - it("conflation order is consistent", async function() { + it("conflation order is consistent", async function () { const table = await perspective.table( { x: "string", - y: "integer" + y: "integer", }, {index: "x"} ); table.update({ x: ["b", "1", "a", "c"], - y: [3, 1, 2, 4] + y: [3, 1, 2, 4], }); table.remove(["1", "c"]); @@ -387,55 +611,55 @@ module.exports = perspective => { const view = await table.view(); expect(await view.to_columns()).toEqual({ x: ["a", "b"], - y: [2, 3] + y: [2, 3], }); table.update({ x: ["b"], - y: [103] + y: [103], }); table.remove(["b"]); expect(await view.to_columns()).toEqual({ x: ["a"], - y: [2] + y: [2], }); table.update({ x: ["b"], - y: [100] + y: [100], }); table.remove(["a"]); expect(await view.to_columns()).toEqual({ x: ["b"], - y: [100] + y: [100], }); // remove applied after update for (let i = 0; i < 100; i++) { table.update({ x: ["c", "a"], - y: [i + 1, i + 2] + y: [i + 1, i + 2], }); table.remove(["c", "a"]); } expect(await view.to_columns()).toEqual({ x: ["b"], - y: [100] + y: [100], }); await view.delete(); await table.delete(); }); - it("correct size", async function() { + it("correct size", async function () { const table = await perspective.table( { x: "integer", - y: "integer" + y: "integer", }, {index: "x"} ); @@ -469,8 +693,8 @@ module.exports = perspective => { }); }); - describe("Schema", function() { - it("updates with columns not in schema", async function() { + describe("Schema", function () { + it("updates with columns not in schema", async function () { var table = await perspective.table({x: "integer", y: "string"}); table.update(data); var view = await table.view(); @@ -479,14 +703,18 @@ module.exports = perspective => { {x: 1, y: "a"}, {x: 2, y: "b"}, {x: 3, y: "c"}, - {x: 4, y: "d"} + {x: 4, y: "d"}, ]); view.delete(); table.delete(); }); - it("coerces to string", async function() { - var table = await perspective.table({x: "string", y: "string", z: "string"}); + it("coerces to string", async function () { + var table = await perspective.table({ + x: "string", + y: "string", + z: "string", + }); table.update(data); var view = await table.view(); let result = await view.to_json(); @@ -494,15 +722,15 @@ module.exports = perspective => { {x: "1", y: "a", z: "true"}, {x: "2", y: "b", z: "false"}, {x: "3", y: "c", z: "true"}, - {x: "4", y: "d", z: "false"} + {x: "4", y: "d", z: "false"}, ]); view.delete(); table.delete(); }); }); - describe("Updates", function() { - it("Meta constructor then `update()`", async function() { + describe("Updates", function () { + it("Meta constructor then `update()`", async function () { var table = await perspective.table(meta); table.update(data); var view = await table.view(); @@ -512,7 +740,7 @@ module.exports = perspective => { table.delete(); }); - it("Meta constructor then column oriented `update()`", async function() { + it("Meta constructor then column oriented `update()`", async function () { var table = await perspective.table(meta); table.update(col_data); var view = await table.view(); @@ -522,13 +750,13 @@ module.exports = perspective => { table.delete(); }); - it("Column oriented `update()` with columns in different order to schema", async function() { + it("Column oriented `update()` with columns in different order to schema", async function () { var table = await perspective.table(meta); var reordered_col_data = { y: ["a", "b", "c", "d"], z: [true, false, true, false], - x: [1, 2, 3, 4] + x: [1, 2, 3, 4], }; table.update(reordered_col_data); @@ -539,11 +767,11 @@ module.exports = perspective => { table.delete(); }); - it("Column data constructor then column oriented `update()`", async function() { + it("Column data constructor then column oriented `update()`", async function () { var colUpdate = { x: [3, 4, 5], y: ["h", "i", "j"], - z: [false, true, false] + z: [false, true, false], }; var expected = [ @@ -551,7 +779,7 @@ module.exports = perspective => { {x: 2, y: "b", z: false}, {x: 3, y: "h", z: false}, {x: 4, y: "i", z: true}, - {x: 5, y: "j", z: false} + {x: 5, y: "j", z: false}, ]; var table = await perspective.table(col_data, {index: "x"}); @@ -563,7 +791,7 @@ module.exports = perspective => { table.delete(); }); - it("`update()` should increment `table.size()` without a view created", async function() { + it("`update()` should increment `table.size()` without a view created", async function () { const table = await perspective.table(data); expect(await table.size()).toEqual(4); table.update(data); @@ -573,7 +801,7 @@ module.exports = perspective => { table.delete(); }); - it("`update()` unbound to table", async function() { + it("`update()` unbound to table", async function () { var table = await perspective.table(meta); var updater = table.update; console.log(updater); @@ -585,7 +813,7 @@ module.exports = perspective => { table.delete(); }); - it("`update([])` does not error", async function() { + it("`update([])` does not error", async function () { let table = await perspective.table(meta); let view = await table.view(); table.update([]); @@ -595,7 +823,7 @@ module.exports = perspective => { table.delete(); }); - it("Multiple `update()`s", async function() { + it("Multiple `update()`s", async function () { var table = await perspective.table(meta); table.update(data); table.update(data); @@ -606,7 +834,7 @@ module.exports = perspective => { table.delete(); }); - it("`update()` called after `view()`", async function() { + it("`update()` called after `view()`", async function () { var table = await perspective.table(meta); var view = await table.view(); table.update(data); @@ -617,8 +845,8 @@ module.exports = perspective => { }); }); - describe("Arrow Updates", function() { - it("arrow contructor then arrow `update()`", async function() { + describe("Arrow Updates", function () { + it("arrow contructor then arrow `update()`", async function () { const arrow = arrows.test_arrow; var table = await perspective.table(arrow.slice()); table.update(arrow.slice()); @@ -629,7 +857,7 @@ module.exports = perspective => { table.delete(); }); - it("non-arrow constructor then arrow `update()`", async function() { + it("non-arrow constructor then arrow `update()`", async function () { let table = await perspective.table(arrow_result); let view = await table.view(); let generated_arrow = await view.to_arrow(); @@ -640,68 +868,97 @@ module.exports = perspective => { table.delete(); }); - it("arrow dict contructor then arrow dict `update()`", async function() { + it("arrow dict contructor then arrow dict `update()`", async function () { var table = await perspective.table(arrows.dict_arrow.slice()); table.update(arrows.dict_update_arrow.slice()); var view = await table.view(); let result = await view.to_columns(); expect(result).toEqual({ - a: ["abc", "def", "def", null, "abc", null, "update1", "update2"], - b: ["klm", "hij", null, "hij", "klm", "update3", null, "update4"] + a: [ + "abc", + "def", + "def", + null, + "abc", + null, + "update1", + "update2", + ], + b: [ + "klm", + "hij", + null, + "hij", + "klm", + "update3", + null, + "update4", + ], }); view.delete(); table.delete(); }); - it("non-arrow constructor then arrow dict `update()`", async function() { + it("non-arrow constructor then arrow dict `update()`", async function () { let table = await perspective.table({ a: ["a", "b", "c"], - b: ["d", "e", "f"] + b: ["d", "e", "f"], }); let view = await table.view(); table.update(arrows.dict_update_arrow.slice()); let result = await view.to_columns(); expect(result).toEqual({ a: ["a", "b", "c", null, "update1", "update2"], - b: ["d", "e", "f", "update3", null, "update4"] + b: ["d", "e", "f", "update3", null, "update4"], }); view.delete(); table.delete(); }); - it("arrow dict contructor then arrow dict `update()` subset of columns", async function() { + it("arrow dict contructor then arrow dict `update()` subset of columns", async function () { var table = await perspective.table(arrows.dict_arrow.slice()); table.update(arrows.dict_update_arrow.slice()); var view = await table.view({ - columns: ["a"] + columns: ["a"], }); let result = await view.to_columns(); expect(result).toEqual({ - a: ["abc", "def", "def", null, "abc", null, "update1", "update2"] + a: [ + "abc", + "def", + "def", + null, + "abc", + null, + "update1", + "update2", + ], }); view.delete(); table.delete(); }); - it("non-arrow constructor then arrow dict `update()`, subset of columns", async function() { + it("non-arrow constructor then arrow dict `update()`, subset of columns", async function () { let table = await perspective.table({ a: ["a", "b", "c"], - b: ["d", "e", "f"] + b: ["d", "e", "f"], }); let view = await table.view({ - columns: ["a"] + columns: ["a"], }); table.update(arrows.dict_update_arrow.slice()); let result = await view.to_columns(); expect(result).toEqual({ - a: ["a", "b", "c", null, "update1", "update2"] + a: ["a", "b", "c", null, "update1", "update2"], }); view.delete(); table.delete(); }); - it("arrow partial `update()` a single column", async function() { - let table = await perspective.table(arrows.test_arrow.slice(), {index: "i64"}); + it("arrow partial `update()` a single column", async function () { + let table = await perspective.table(arrows.test_arrow.slice(), { + index: "i64", + }); table.update(arrows.partial_arrow.slice()); const view = await table.view(); const result = await view.to_json(); @@ -710,8 +967,10 @@ module.exports = perspective => { table.delete(); }); - it("arrow partial `update()` a single column with missing rows", async function() { - let table = await perspective.table(arrows.test_arrow.slice(), {index: "i64"}); + it("arrow partial `update()` a single column with missing rows", async function () { + let table = await perspective.table(arrows.test_arrow.slice(), { + index: "i64", + }); table.update(arrows.partial_missing_rows_arrow.slice()); const view = await table.view(); const result = await view.to_json(); @@ -720,11 +979,11 @@ module.exports = perspective => { table.delete(); }); - it("schema constructor, then arrow `update()`", async function() { + it("schema constructor, then arrow `update()`", async function () { const table = await perspective.table({ a: "integer", b: "float", - c: "string" + c: "string", }); table.update(arrows.int_float_str_arrow.slice()); const view = await table.view(); @@ -734,16 +993,16 @@ module.exports = perspective => { expect(result).toEqual({ a: [1, 2, 3, 4], b: [1.5, 2.5, 3.5, 4.5], - c: ["a", "b", "c", "d"] + c: ["a", "b", "c", "d"], }); view.delete(); table.delete(); }); - it("schema constructor, then arrow dictionary `update()`", async function() { + it("schema constructor, then arrow dictionary `update()`", async function () { const table = await perspective.table({ a: "string", - b: "string" + b: "string", }); table.update(arrows.dict_arrow.slice()); const view = await table.view(); @@ -752,17 +1011,17 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ a: ["abc", "def", "def", null, "abc"], - b: ["klm", "hij", null, "hij", "klm"] + b: ["klm", "hij", null, "hij", "klm"], }); view.delete(); table.delete(); }); - it.skip("schema constructor, then indexed arrow dictionary `update()`", async function() { + it.skip("schema constructor, then indexed arrow dictionary `update()`", async function () { const table = await perspective.table( { a: "string", - b: "string" + b: "string", }, {index: "a"} ); @@ -773,16 +1032,16 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ a: [null, "abc", "def"], - b: ["hij", "klm", "hij"] + b: ["hij", "klm", "hij"], }); view.delete(); table.delete(); }); - it.skip("schema constructor, then indexed arrow dictionary `update()` with more columns than in schema", async function() { + it.skip("schema constructor, then indexed arrow dictionary `update()` with more columns than in schema", async function () { const table = await perspective.table( { - a: "string" + a: "string", }, {index: "a"} ); @@ -793,18 +1052,18 @@ module.exports = perspective => { expect(size).toEqual(3); const result = await view.to_columns(); expect(result).toEqual({ - a: [null, "abc", "def"] + a: [null, "abc", "def"], }); view.delete(); table.delete(); }); - it.skip("schema constructor, then indexed arrow dictionary `update()` with less columns than in schema", async function() { + it.skip("schema constructor, then indexed arrow dictionary `update()` with less columns than in schema", async function () { const table = await perspective.table( { a: "string", b: "string", - x: "integer" + x: "integer", }, {index: "a"} ); @@ -817,18 +1076,18 @@ module.exports = perspective => { expect(result).toEqual({ a: [null, "abc", "def"], b: ["hij", "klm", "hij"], - x: [null, null, null] + x: [null, null, null], }); view.delete(); table.delete(); }); - it("schema constructor, then indexed arrow `update()`", async function() { + it("schema constructor, then indexed arrow `update()`", async function () { const table = await perspective.table( { a: "integer", b: "float", - c: "string" + c: "string", }, {index: "a"} ); @@ -841,35 +1100,35 @@ module.exports = perspective => { expect(result).toEqual({ a: [1, 2, 3, 4], b: [100.5, 2.5, 3.5, 400.5], - c: ["x", "b", "c", "y"] + c: ["x", "b", "c", "y"], }); view.delete(); table.delete(); }); - it("schema constructor, then arrow update() with more columns than in the Table", async function() { + it("schema constructor, then arrow update() with more columns than in the Table", async function () { const table = await perspective.table({ - a: "integer" + a: "integer", }); const view = await table.view(); table.update(arrows.int_float_str_arrow.slice()); expect(await table.size()).toBe(4); const result = await view.to_columns(); expect(result).toEqual({ - a: [1, 2, 3, 4] + a: [1, 2, 3, 4], }); view.delete(); table.delete(); }); - it("schema constructor indexed, then arrow update() with more columns than in the Table", async function() { + it("schema constructor indexed, then arrow update() with more columns than in the Table", async function () { const table = await perspective.table( { a: "integer", - b: "float" + b: "float", }, { - index: "a" + index: "a", } ); const view = await table.view(); @@ -879,17 +1138,17 @@ module.exports = perspective => { const result = await view.to_columns(); expect(result).toEqual({ a: [1, 2, 3, 4], - b: [100.5, 2.5, 3.5, 400.5] + b: [100.5, 2.5, 3.5, 400.5], }); view.delete(); table.delete(); }); - it("schema constructor, then arrow update() with less columns than in the Table", async function() { + it("schema constructor, then arrow update() with less columns than in the Table", async function () { const table = await perspective.table({ a: "integer", x: "float", - y: "string" + y: "string", }); const view = await table.view(); table.update(arrows.int_float_str_arrow.slice()); @@ -898,21 +1157,21 @@ module.exports = perspective => { expect(result).toEqual({ a: [1, 2, 3, 4], x: [null, null, null, null], - y: [null, null, null, null] + y: [null, null, null, null], }); view.delete(); table.delete(); }); - it("schema constructor indexed, then arrow update() with less columns than in the Table", async function() { + it("schema constructor indexed, then arrow update() with less columns than in the Table", async function () { const table = await perspective.table( { a: "integer", x: "float", - y: "string" + y: "string", }, { - index: "a" + index: "a", } ); const view = await table.view(); @@ -923,17 +1182,17 @@ module.exports = perspective => { expect(result).toEqual({ a: [1, 2, 3, 4], x: [null, null, null, null], - y: [null, null, null, null] + y: [null, null, null, null], }); view.delete(); table.delete(); }); - it("schema constructor, then arrow stream and arrow file `update()`", async function() { + it("schema constructor, then arrow stream and arrow file `update()`", async function () { const table = await perspective.table({ a: "integer", b: "float", - c: "string" + c: "string", }); table.update(arrows.int_float_str_arrow.slice()); @@ -943,19 +1202,19 @@ module.exports = perspective => { expect(result).toEqual({ a: [1, 2, 3, 4, 1, 2, 3, 4], b: [1.5, 2.5, 3.5, 4.5, 1.5, 2.5, 3.5, 4.5], - c: ["a", "b", "c", "d", "a", "b", "c", "d"] + c: ["a", "b", "c", "d", "a", "b", "c", "d"], }); view.delete(); table.delete(); }); }); - describe("Notifications", function() { - it("`on_update()`", async function(done) { + describe("Notifications", function () { + it("`on_update()`", async function (done) { var table = await perspective.table(meta); var view = await table.view(); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, data); view.delete(); table.delete(); @@ -966,13 +1225,13 @@ module.exports = perspective => { table.update(data); }); - it("`on_update` before and after `update()`", async function(done) { + it("`on_update` before and after `update()`", async function (done) { var table = await perspective.table(meta); var view = await table.view(); table.update(data); var ran = false; view.on_update( - async function(updated) { + async function (updated) { if (!ran) { await match_delta(perspective, updated.delta, data); ran = true; @@ -986,13 +1245,13 @@ module.exports = perspective => { table.update(data); }); - it("`on_update(table.update) !`", async function(done) { + it("`on_update(table.update) !`", async function (done) { var table1 = await perspective.table(meta); var table2 = await perspective.table(meta); var view1 = await table1.view(); var view2 = await table2.view(); view1.on_update( - async function(updated) { + async function (updated) { table2.update(updated.delta); let result = await view2.to_json(); expect(result).toEqual(data); @@ -1007,7 +1266,7 @@ module.exports = perspective => { table1.update(data); }); - it("`on_update(table.update)` before and after `update()`", async function(done) { + it("`on_update(table.update)` before and after `update()`", async function (done) { var table1 = await perspective.table(meta); var table2 = await perspective.table(meta); var view1 = await table1.view(); @@ -1016,7 +1275,7 @@ module.exports = perspective => { table1.update(data); table2.update(data); view1.on_update( - async function(updated) { + async function (updated) { table2.update(updated.delta); let result = await view2.to_json(); expect(result).toEqual(data.concat(data)); @@ -1031,7 +1290,7 @@ module.exports = perspective => { table1.update(data); }); - it("properly removes a failed update callback on a table", async function(done) { + it("properly removes a failed update callback on a table", async function (done) { const table = await perspective.table({x: "integer"}); const view = await table.view(); let size = await table.size(); @@ -1064,10 +1323,10 @@ module.exports = perspective => { done(); }); - it("`on_update()` that calls operations on the table should not recurse", async function(done) { + it("`on_update()` that calls operations on the table should not recurse", async function (done) { var table = await perspective.table(meta); var view = await table.view(); - view.on_update(async function(updated) { + view.on_update(async function (updated) { expect(updated.port_id).toEqual(0); const json = await view.to_json(); // Not checking for correctness, literally just to assert @@ -1081,13 +1340,13 @@ module.exports = perspective => { table.update(data); }); - it("`on_update()` should be triggered in sequence", async function(done) { + it("`on_update()` should be triggered in sequence", async function (done) { var table = await perspective.table(meta); var view = await table.view(); let order = []; - const finish = function() { + const finish = function () { if (order.length === 3) { expect(order).toEqual([0, 1, 2]); view.delete(); @@ -1106,13 +1365,17 @@ module.exports = perspective => { table.update(data); }); - it("`on_update()` should be triggered in sequence across multiple views", async function(done) { + it("`on_update()` should be triggered in sequence across multiple views", async function (done) { var table = await perspective.table(meta); - const views = [await table.view(), await table.view(), await table.view()]; + const views = [ + await table.view(), + await table.view(), + await table.view(), + ]; let order = []; - const finish = function() { + const finish = function () { if (order.length === 3) { expect(order).toEqual([0, 1, 2]); for (const view of views) { @@ -1134,8 +1397,8 @@ module.exports = perspective => { }); }); - describe("Limit", function() { - it("{limit: 2} with table of size 4", async function() { + describe("Limit", function () { + it("{limit: 2} with table of size 4", async function () { var table = await perspective.table(data, {limit: 2}); var view = await table.view(); let result = await view.to_json(); @@ -1144,22 +1407,19 @@ module.exports = perspective => { table.delete(); }); - it("{limit: 5} with 2 updates of size 4", async function() { + it("{limit: 5} with 2 updates of size 4", async function () { var table = await perspective.table(data, {limit: 5}); table.update(data); var view = await table.view(); let result = await view.to_json(); expect(result).toEqual( - data - .slice(1) - .concat(data.slice(3, 4)) - .concat(data.slice(0, 1)) + data.slice(1).concat(data.slice(3, 4)).concat(data.slice(0, 1)) ); view.delete(); table.delete(); }); - it("{limit: 1} with arrow update", async function() { + it("{limit: 1} with arrow update", async function () { const arrow = arrows.test_arrow.slice(); var table = await perspective.table(arrow, {limit: 1}); table.update(arrow.slice()); @@ -1171,8 +1431,8 @@ module.exports = perspective => { }); }); - describe("Indexed", function() { - it("{index: 'x'} (int)", async function() { + describe("Indexed", function () { + it("{index: 'x'} (int)", async function () { var table = await perspective.table(data, {index: "x"}); var view = await table.view(); table.update(data); @@ -1182,7 +1442,7 @@ module.exports = perspective => { table.delete(); }); - it("{index: 'y'} (string)", async function() { + it("{index: 'y'} (string)", async function () { var table = await perspective.table(data, {index: "y"}); var view = await table.view(); table.update(data); @@ -1192,87 +1452,87 @@ module.exports = perspective => { table.delete(); }); - it("{index: 'x'} (int) with null and 0", async function() { + it("{index: 'x'} (int) with null and 0", async function () { const data = { x: [0, 1, null, 2, 3], - y: ["a", "b", "c", "d", "e"] + y: ["a", "b", "c", "d", "e"], }; const table = await perspective.table(data, {index: "x"}); const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ x: [null, 0, 1, 2, 3], // null before 0 - y: ["c", "a", "b", "d", "e"] + y: ["c", "a", "b", "d", "e"], }); view.delete(); table.delete(); }); - it("{index: 'y'} (str) with null and empty string", async function() { + it("{index: 'y'} (str) with null and empty string", async function () { const data = { x: [0, 1, 2, 3, 4], - y: ["", "a", "b", "c", null] + y: ["", "a", "b", "c", null], }; const table = await perspective.table(data, {index: "y"}); const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ x: [4, 0, 1, 2, 3], - y: [null, "", "a", "b", "c"] // null before empties + y: [null, "", "a", "b", "c"], // null before empties }); view.delete(); table.delete(); }); - it("{index: 'x'} (int) with null and 0, update", async function() { + it("{index: 'x'} (int) with null and 0, update", async function () { const data = { x: [0, 1, null, 2, 3], - y: ["a", "b", "c", "d", "e"] + y: ["a", "b", "c", "d", "e"], }; const table = await perspective.table(data, {index: "x"}); table.update({ x: [null, 0], - y: ["x", "y"] + y: ["x", "y"], }); const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ x: [null, 0, 1, 2, 3], // null before 0 - y: ["x", "y", "b", "d", "e"] + y: ["x", "y", "b", "d", "e"], }); view.delete(); table.delete(); }); - it("{index: 'y'} (str) with null and empty string, update", async function() { + it("{index: 'y'} (str) with null and empty string, update", async function () { const data = { x: [0, 1, 2, 3, 4], - y: ["", "a", "b", "c", null] + y: ["", "a", "b", "c", null], }; const table = await perspective.table(data, {index: "y"}); table.update({ x: [5, 6], - y: ["", null] + y: ["", null], }); const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ x: [6, 5, 1, 2, 3], - y: [null, "", "a", "b", "c"] // null before empties + y: [null, "", "a", "b", "c"], // null before empties }); view.delete(); table.delete(); }); - it("{index: 'x'} (date) with null", async function() { + it("{index: 'x'} (date) with null", async function () { const data = { x: ["10/30/2016", "11/1/2016", null, "1/1/2000"], - y: [1, 2, 3, 4] + y: [1, 2, 3, 4], }; const table = await perspective.table( { x: "date", - y: "integer" + y: "integer", }, {index: "x"} ); @@ -1280,22 +1540,32 @@ module.exports = perspective => { const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ - x: [null, new Date("2000-1-1").getTime(), new Date("2016-10-30").getTime(), new Date("2016-11-1").getTime()], - y: [3, 4, 1, 2] + x: [ + null, + new Date("2000-1-1").getTime(), + new Date("2016-10-30").getTime(), + new Date("2016-11-1").getTime(), + ], + y: [3, 4, 1, 2], }); view.delete(); table.delete(); }); - it("{index: 'y'} (datetime) with datetime and null", async function() { + it("{index: 'y'} (datetime) with datetime and null", async function () { const data = { - x: ["2016-11-01 11:00:00", "2016-11-01 11:10:00", null, "2016-11-01 11:20:00"], - y: [1, 2, 3, 4] + x: [ + "2016-11-01 11:00:00", + "2016-11-01 11:10:00", + null, + "2016-11-01 11:20:00", + ], + y: [1, 2, 3, 4], }; const table = await perspective.table( { x: "datetime", - y: "integer" + y: "integer", }, {index: "x"} ); @@ -1303,15 +1573,22 @@ module.exports = perspective => { const view = await table.view(); const result = await view.to_columns(); expect(result).toEqual({ - x: [null, new Date("2016-11-1 11:00:00").getTime(), new Date("2016-11-1 11:10:00").getTime(), new Date("2016-11-1 11:20:00").getTime()], - y: [3, 1, 2, 4] + x: [ + null, + new Date("2016-11-1 11:00:00").getTime(), + new Date("2016-11-1 11:10:00").getTime(), + new Date("2016-11-1 11:20:00").getTime(), + ], + y: [3, 1, 2, 4], }); view.delete(); table.delete(); }); - it("Arrow with {index: 'i64'} (int)", async function() { - var table = await perspective.table(arrows.test_arrow.slice(), {index: "i64"}); + it("Arrow with {index: 'i64'} (int)", async function () { + var table = await perspective.table(arrows.test_arrow.slice(), { + index: "i64", + }); var view = await table.view(); let result = await view.to_json(); expect(result).toEqual(arrow_result); @@ -1319,8 +1596,10 @@ module.exports = perspective => { table.delete(); }); - it("Arrow with {index: 'char'} (char)", async function() { - var table = await perspective.table(arrows.test_arrow.slice(), {index: "char"}); + it("Arrow with {index: 'char'} (char)", async function () { + var table = await perspective.table(arrows.test_arrow.slice(), { + index: "char", + }); var view = await table.view(); let result = await view.to_json(); expect(result).toEqual(arrow_indexed_result); @@ -1328,8 +1607,10 @@ module.exports = perspective => { table.delete(); }); - it("Arrow with {index: 'dict'} (dict)", async function() { - var table = await perspective.table(arrows.test_arrow.slice(), {index: "dict"}); + it("Arrow with {index: 'dict'} (dict)", async function () { + var table = await perspective.table(arrows.test_arrow.slice(), { + index: "dict", + }); var view = await table.view(); let result = await view.to_json(); expect(result).toEqual(arrow_indexed_result); @@ -1337,7 +1618,7 @@ module.exports = perspective => { table.delete(); }); - it("multiple updates on int {index: 'x'}", async function() { + it("multiple updates on int {index: 'x'}", async function () { var table = await perspective.table(data, {index: "x"}); var view = await table.view(); table.update(data); @@ -1349,7 +1630,7 @@ module.exports = perspective => { table.delete(); }); - it("multiple updates on str {index: 'y'}", async function() { + it("multiple updates on str {index: 'y'}", async function () { var table = await perspective.table(data, {index: "y"}); var view = await table.view(); table.update(data); @@ -1361,7 +1642,7 @@ module.exports = perspective => { table.delete(); }); - it("multiple updates on str {index: 'y'} with new, old, null pkey", async function() { + it("multiple updates on str {index: 'y'} with new, old, null pkey", async function () { var table = await perspective.table(data, {index: "y"}); var view = await table.view(); table.update([{x: 1, y: "a", z: true}]); @@ -1374,18 +1655,18 @@ module.exports = perspective => { {x: 123, y: "abc", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); view.delete(); table.delete(); }); - it.skip("multiple updates on str {index: 'y'} with new, old, null in dataset", async function() { + it.skip("multiple updates on str {index: 'y'} with new, old, null in dataset", async function () { // FIXME: this is an engine bug const table = await perspective.table( { x: [1, 2, 3], - y: ["a", null, "c"] + y: ["a", null, "c"], }, {index: "y"} ); @@ -1401,14 +1682,14 @@ module.exports = perspective => { {x: 100, y: null}, {x: 12345, y: "a"}, {x: 123, y: "abc"}, - {x: 3, y: "c"} + {x: 3, y: "c"}, ]); await view.delete(); await table.delete(); }); - it("{index: 'x'} with overlap", async function() { + it("{index: 'x'} with overlap", async function () { var table = await perspective.table(data, {index: "x"}); var view = await table.view(); table.update(data); @@ -1419,12 +1700,12 @@ module.exports = perspective => { table.delete(); }); - it("update and index (int)", async function(done) { + it("update and index (int)", async function (done) { var table = await perspective.table(meta, {index: "x"}); var view = await table.view(); table.update(data); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, data_2); let json = await view.to_json(); expect(json).toEqual(data.slice(0, 2).concat(data_2)); @@ -1437,12 +1718,12 @@ module.exports = perspective => { table.update(data_2); }); - it("update and index (string)", async function(done) { + it("update and index (string)", async function (done) { var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); table.update(data); view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, data_2); let json = await view.to_json(); expect(json).toEqual(data.slice(0, 2).concat(data_2)); @@ -1455,11 +1736,11 @@ module.exports = perspective => { table.update(data_2); }); - it("update with depth expansion", async function() { + it("update with depth expansion", async function () { var table = await perspective.table(meta, {index: "y"}); var view = await table.view({ row_pivots: ["z", "y"], - columns: [] + columns: [], }); table.update(data); @@ -1472,30 +1753,34 @@ module.exports = perspective => { {__ROW_PATH__: [false, "d"]}, {__ROW_PATH__: [true]}, {__ROW_PATH__: [true, "a"]}, - {__ROW_PATH__: [true, "c"]} + {__ROW_PATH__: [true, "c"]}, ]; expect(result).toEqual(expected); view.delete(); table.delete(); }); - it("partial update", async function(done) { + it("partial update", async function (done) { var partial = [ {x: 5, y: "a"}, - {y: "b", z: true} + {y: "b", z: true}, ]; var expected = [ {x: 5, y: "a", z: true}, {x: 2, y: "b", z: true}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); table.update(data); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, expected.slice(0, 2)); + async function (updated) { + await match_delta( + perspective, + updated.delta, + expected.slice(0, 2) + ); let json = await view.to_json(); expect(json).toEqual(expected); view.delete(); @@ -1507,25 +1792,29 @@ module.exports = perspective => { table.update(partial); }); - it("partial column oriented update", async function(done) { + it("partial column oriented update", async function (done) { var partial = { x: [5, undefined], y: ["a", "b"], - z: [undefined, true] + z: [undefined, true], }; var expected = [ {x: 5, y: "a", z: true}, {x: 2, y: "b", z: true}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); table.update(col_data); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, expected.slice(0, 2)); + async function (updated) { + await match_delta( + perspective, + updated.delta, + expected.slice(0, 2) + ); let json = await view.to_json(); expect(json).toEqual(expected); view.delete(); @@ -1537,10 +1826,10 @@ module.exports = perspective => { table.update(partial); }); - it("Partial column oriented update with new pkey", async function(done) { + it("Partial column oriented update with new pkey", async function (done) { const data = { x: [0, 1, null, 2, 3], - y: ["a", "b", "c", "d", "e"] + y: ["a", "b", "c", "d", "e"], }; const table = await perspective.table(data, {index: "x"}); const view = await table.view(); @@ -1548,21 +1837,21 @@ module.exports = perspective => { expect(result).toEqual({ x: [null, 0, 1, 2, 3], // null before 0 - y: ["c", "a", "b", "d", "e"] + y: ["c", "a", "b", "d", "e"], }); const expected = { x: [null, 0, 1, 2, 3, 4], - y: ["c", "a", "b", "d", "e", "f"] + y: ["c", "a", "b", "d", "e", "f"], }; view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, [ { x: 4, - y: "f" - } + y: "f", + }, ]); const result2 = await view.to_columns(); expect(result2).toEqual(expected); @@ -1575,15 +1864,15 @@ module.exports = perspective => { table.update({ x: [4], - y: ["f"] + y: ["f"], }); }); - it("Partial column oriented update with new pkey, missing columns", async function(done) { + it("Partial column oriented update with new pkey, missing columns", async function (done) { const data = { x: [0, 1, null, 2, 3], y: ["a", "b", "c", "d", "e"], - z: [true, false, true, false, true] + z: [true, false, true, false, true], }; const table = await perspective.table(data, {index: "x"}); const view = await table.view(); @@ -1592,23 +1881,23 @@ module.exports = perspective => { expect(result).toEqual({ x: [null, 0, 1, 2, 3], // null before 0 y: ["c", "a", "b", "d", "e"], - z: [true, true, false, false, true] + z: [true, true, false, false, true], }); const expected = { x: [null, 0, 1, 2, 3, 4], y: ["c", "a", "b", "d", "e", "f"], - z: [true, true, false, false, true, null] + z: [true, true, false, false, true, null], }; view.on_update( - async function(updated) { + async function (updated) { await match_delta(perspective, updated.delta, [ { x: 4, y: "f", - z: null - } + z: null, + }, ]); const result2 = await view.to_columns(); expect(result2).toEqual(expected); @@ -1621,28 +1910,32 @@ module.exports = perspective => { table.update({ x: [4], - y: ["f"] + y: ["f"], }); }); - it("partial column oriented update with entire columns missing", async function(done) { + it("partial column oriented update with entire columns missing", async function (done) { var partial = { y: ["a", "b"], - z: [false, true] + z: [false, true], }; var expected = [ {x: 1, y: "a", z: false}, {x: 2, y: "b", z: true}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); table.update(col_data); view.on_update( - async function(updated) { - await match_delta(perspective, updated.delta, expected.slice(0, 2)); + async function (updated) { + await match_delta( + perspective, + updated.delta, + expected.slice(0, 2) + ); let json = await view.to_json(); expect(json).toEqual(expected); view.delete(); @@ -1655,31 +1948,31 @@ module.exports = perspective => { }); }); - describe("null handling", function() { - it("recalculates sum aggregates when a null unsets a value", async function() { + describe("null handling", function () { + it("recalculates sum aggregates when a null unsets a value", async function () { var table = await perspective.table( [ {x: 1, y: 1}, - {x: 2, y: 1} + {x: 2, y: 1}, ], {index: "x"} ); table.update([{x: 2, y: null}]); var view = await table.view({ row_pivots: ["x"], - columns: ["y"] + columns: ["y"], }); let json = await view.to_json(); expect(json).toEqual([ {__ROW_PATH__: [], y: 1}, {__ROW_PATH__: [1], y: 1}, - {__ROW_PATH__: [2], y: 0} + {__ROW_PATH__: [2], y: 0}, ]); view.delete(); table.delete(); }); - it("can be removed entirely", async function() { + it("can be removed entirely", async function () { var table = await perspective.table([{x: 1, y: 1}], {index: "x"}); table.update([{x: 1, y: null}]); table.update([{x: 1, y: 1}]); @@ -1690,13 +1983,13 @@ module.exports = perspective => { table.delete(); }); - it("partial update with null unsets value", async function() { + it("partial update with null unsets value", async function () { var partial = [{x: null, y: "a", z: false}]; var expected = [ {x: null, y: "a", z: false}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); @@ -1708,14 +2001,14 @@ module.exports = perspective => { table.delete(); }); - it("update by adding rows (new pkeys) with partials/nulls", async function() { + it("update by adding rows (new pkeys) with partials/nulls", async function () { var update = [{x: null, y: "e", z: null}]; var expected = [ {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, {x: 4, y: "d", z: false}, - {x: null, y: "e", z: null} + {x: null, y: "e", z: null}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); @@ -1727,17 +2020,17 @@ module.exports = perspective => { table.delete(); }); - it("partial column oriented update with null unsets value", async function() { + it("partial column oriented update with null unsets value", async function () { var partial = { x: [null], - y: ["a"] + y: ["a"], }; var expected = [ {x: null, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "c", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]; var table = await perspective.table(meta, {index: "y"}); var view = await table.view(); @@ -1750,13 +2043,13 @@ module.exports = perspective => { }); }); - describe("Viewport", function() { - it("`height`", async function() { + describe("Viewport", function () { + it("`height`", async function () { var table = await perspective.table(data); var view = await table.view({ viewport: { - height: 2 - } + height: 2, + }, }); let result = await view.to_json(); expect(result).toEqual(data.slice(0, 2)); @@ -1764,12 +2057,12 @@ module.exports = perspective => { table.delete(); }); - it("`top`", async function() { + it("`top`", async function () { var table = await perspective.table(data); var view = await table.view({ viewport: { - top: 2 - } + top: 2, + }, }); let result = await view.to_json(); expect(result).toEqual(data.slice(2)); @@ -1777,28 +2070,28 @@ module.exports = perspective => { table.delete(); }); - it("`width`", async function() { + it("`width`", async function () { var table = await perspective.table(data); var view = await table.view({ viewport: { - width: 2 - } + width: 2, + }, }); - var result2 = _.map(data, x => _.pick(x, "x", "y")); + var result2 = _.map(data, (x) => _.pick(x, "x", "y")); let result = await view.to_json(); expect(result).toEqual(result2); view.delete(); table.delete(); }); - it("`left`", async function() { + it("`left`", async function () { var table = await perspective.table(data); var view = await table.view({ viewport: { - left: 1 - } + left: 1, + }, }); - var result = _.map(data, function(x) { + var result = _.map(data, function (x) { return _.pick(x, "y", "z"); }); let result2 = await view.to_json(); @@ -1807,17 +2100,17 @@ module.exports = perspective => { table.delete(); }); - it("All", async function() { + it("All", async function () { var table = await perspective.table(data); var view = await table.view({ viewport: { top: 1, left: 1, width: 1, - height: 2 - } + height: 2, + }, }); - var result = _.map(data, function(x) { + var result = _.map(data, function (x) { return _.pick(x, "y"); }); let result2 = await view.to_json(); @@ -1827,14 +2120,14 @@ module.exports = perspective => { }); }); - describe("implicit index", function() { - it("should apply single partial update on unindexed table using row id from '__INDEX__'", async function() { + describe("implicit index", function () { + it("should apply single partial update on unindexed table using row id from '__INDEX__'", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 2, - y: "new_string" - } + y: "new_string", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -1847,11 +2140,11 @@ module.exports = perspective => { table.delete(); }); - it("should partial update on unindexed table, column dataset", async function() { + it("should partial update on unindexed table, column dataset", async function () { let table = await perspective.table(data); table.update({ __INDEX__: [2], - y: ["new_string"] + y: ["new_string"], }); let view = await table.view(); @@ -1862,20 +2155,20 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "new_string", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); view.delete(); table.delete(); }); - it("should partial update and unset on unindexed table", async function() { + it("should partial update and unset on unindexed table", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 2, y: "new_string", - z: null - } + z: null, + }, ]); let view = await table.view(); @@ -1886,18 +2179,18 @@ module.exports = perspective => { {x: 1, y: "a", z: true}, {x: 2, y: "b", z: false}, {x: 3, y: "new_string", z: null}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); view.delete(); table.delete(); }); - it("should partial update and unset on unindexed table, column dataset", async function() { + it("should partial update and unset on unindexed table, column dataset", async function () { let table = await perspective.table(data); table.update({ __INDEX__: [0, 2], y: [undefined, "new_string"], - z: [null, undefined] + z: [null, undefined], }); let view = await table.view(); @@ -1908,20 +2201,20 @@ module.exports = perspective => { {x: 1, y: "a", z: null}, {x: 2, y: "b", z: false}, {x: 3, y: "new_string", z: true}, - {x: 4, y: "d", z: false} + {x: 4, y: "d", z: false}, ]); view.delete(); table.delete(); }); - it("should apply single multi-column partial update on unindexed table using row id from '__INDEX__'", async function() { + it("should apply single multi-column partial update on unindexed table using row id from '__INDEX__'", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 2, y: "new_string", - x: 100 - } + x: 100, + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -1935,13 +2228,13 @@ module.exports = perspective => { table.delete(); }); - it("should apply updates using '__INDEX__' on a table with explicit index set", async function() { + it("should apply updates using '__INDEX__' on a table with explicit index set", async function () { let table = await perspective.table(data, {index: "x"}); table.update([ { __INDEX__: 2, - y: "new_string" - } + y: "new_string", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -1954,17 +2247,17 @@ module.exports = perspective => { table.delete(); }); - it("should apply multiple sequential updates using '__INDEX__' on a table with explicit index set", async function() { + it("should apply multiple sequential updates using '__INDEX__' on a table with explicit index set", async function () { let table = await perspective.table(data, {index: "x"}); table.update([ { __INDEX__: 2, - y: "new_string" + y: "new_string", }, { __INDEX__: 3, - y: "new_string" - } + y: "new_string", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -1978,17 +2271,17 @@ module.exports = perspective => { table.delete(); }); - it("should apply mulitple nonsequential updates using '__INDEX__' on a table with explicit index set", async function() { + it("should apply mulitple nonsequential updates using '__INDEX__' on a table with explicit index set", async function () { let table = await perspective.table(data, {index: "x"}); table.update([ { __INDEX__: 2, - y: "new_string" + y: "new_string", }, { __INDEX__: 4, - y: "new_string" - } + y: "new_string", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -2002,21 +2295,21 @@ module.exports = perspective => { table.delete(); }); - it("should apply multiple sequential partial updates on unindexed table using '__INDEX__'", async function() { + it("should apply multiple sequential partial updates on unindexed table using '__INDEX__'", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 0, - y: "new_string1" + y: "new_string1", }, { __INDEX__: 1, - y: "new_string2" + y: "new_string2", }, { __INDEX__: 2, - y: "new_string3" - } + y: "new_string3", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -2031,25 +2324,25 @@ module.exports = perspective => { table.delete(); }); - it("should correctly apply multiple out-of-sequence partial updates on unindexed table using '__INDEX__'", async function() { + it("should correctly apply multiple out-of-sequence partial updates on unindexed table using '__INDEX__'", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 0, - y: "new_string1" + y: "new_string1", }, { __INDEX__: 2, - y: "new_string3" + y: "new_string3", }, { __INDEX__: 3, - y: "new_string4" + y: "new_string4", }, { __INDEX__: 1, - y: "new_string2" - } + y: "new_string2", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -2065,21 +2358,21 @@ module.exports = perspective => { table.delete(); }); - it("should stack multiple partial updates on unindexed table using the same '__INDEX__'", async function() { + it("should stack multiple partial updates on unindexed table using the same '__INDEX__'", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 0, - y: "new_string1" + y: "new_string1", }, { __INDEX__: 0, - y: "new_string2" + y: "new_string2", }, { __INDEX__: 0, - y: "new_string3" - } + y: "new_string3", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -2092,18 +2385,18 @@ module.exports = perspective => { table.delete(); }); - it("updates without '__INDEX' should append", async function() { + it("updates without '__INDEX' should append", async function () { let table = await perspective.table(data); table.update([ { __INDEX__: 0, - y: "new_string" - } + y: "new_string", + }, ]); table.update([ { - y: "new_string" - } + y: "new_string", + }, ]); let view = await table.view(); let result = await view.to_json(); @@ -2117,17 +2410,17 @@ module.exports = perspective => { table.delete(); }); - it("should partial update on 1-sided views using implicit '__INDEX__'", async function() { + it("should partial update on 1-sided views using implicit '__INDEX__'", async function () { let table = await perspective.table(data); let view = await table.view({ - row_pivots: ["x"] + row_pivots: ["x"], }); table.update([ { __INDEX__: 0, - x: 100 - } + x: 100, + }, ]); let result = await view.to_json(); @@ -2137,7 +2430,7 @@ module.exports = perspective => { {__ROW_PATH__: [2], x: 2, y: 1, z: 1}, {__ROW_PATH__: [3], x: 3, y: 1, z: 1}, {__ROW_PATH__: [4], x: 4, y: 1, z: 1}, - {__ROW_PATH__: [100], x: 100, y: 1, z: 1} + {__ROW_PATH__: [100], x: 100, y: 1, z: 1}, ]); // check that un-pivoted view reflects data correctly @@ -2153,28 +2446,98 @@ module.exports = perspective => { table.delete(); }); - it("should partial update on 2-sided views using implicit '__INDEX__'", async function() { + it("should partial update on 2-sided views using implicit '__INDEX__'", async function () { let table = await perspective.table(data); let view = await table.view({ row_pivots: ["x"], - column_pivots: ["y"] + column_pivots: ["y"], }); table.update([ { __INDEX__: 0, - x: 100 - } + x: 100, + }, ]); let result = await view.to_json(); // update should be applied properly expect(result).toEqual([ - {__ROW_PATH__: [], "a|x": 100, "a|y": 1, "a|z": 1, "b|x": 2, "b|y": 1, "b|z": 1, "c|x": 3, "c|y": 1, "c|z": 1, "d|x": 4, "d|y": 1, "d|z": 1}, - {__ROW_PATH__: [2], "a|x": null, "a|y": null, "a|z": null, "b|x": 2, "b|y": 1, "b|z": 1, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null}, - {__ROW_PATH__: [3], "a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": 3, "c|y": 1, "c|z": 1, "d|x": null, "d|y": null, "d|z": null}, - {__ROW_PATH__: [4], "a|x": null, "a|y": null, "a|z": null, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": 4, "d|y": 1, "d|z": 1}, - {__ROW_PATH__: [100], "a|x": 100, "a|y": 1, "a|z": 1, "b|x": null, "b|y": null, "b|z": null, "c|x": null, "c|y": null, "c|z": null, "d|x": null, "d|y": null, "d|z": null} + { + __ROW_PATH__: [], + "a|x": 100, + "a|y": 1, + "a|z": 1, + "b|x": 2, + "b|y": 1, + "b|z": 1, + "c|x": 3, + "c|y": 1, + "c|z": 1, + "d|x": 4, + "d|y": 1, + "d|z": 1, + }, + { + __ROW_PATH__: [2], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": 2, + "b|y": 1, + "b|z": 1, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + __ROW_PATH__: [3], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": 3, + "c|y": 1, + "c|z": 1, + "d|x": null, + "d|y": null, + "d|z": null, + }, + { + __ROW_PATH__: [4], + "a|x": null, + "a|y": null, + "a|z": null, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": 4, + "d|y": 1, + "d|z": 1, + }, + { + __ROW_PATH__: [100], + "a|x": 100, + "a|y": 1, + "a|z": 1, + "b|x": null, + "b|y": null, + "b|z": null, + "c|x": null, + "c|y": null, + "c|z": null, + "d|x": null, + "d|y": null, + "d|z": null, + }, ]); // check that un-pivoted view reflects data correctly @@ -2191,8 +2554,8 @@ module.exports = perspective => { }); }); - describe("Remove update", function() { - it("Should remove a single update", async function(done) { + describe("Remove update", function () { + it("Should remove a single update", async function (done) { const cb1 = jest.fn(); const cb2 = () => { expect(cb1).toBeCalledTimes(0); @@ -2210,10 +2573,10 @@ module.exports = perspective => { table.update(data); }); - it("Should remove multiple updates", async function(done) { + it("Should remove multiple updates", async function (done) { const cb1 = jest.fn(); const cb2 = jest.fn(); - const cb3 = function() { + const cb3 = function () { // cb2 should have been called expect(cb1).toBeCalledTimes(0); expect(cb2).toBeCalledTimes(0); @@ -2242,7 +2605,7 @@ module.exports = perspective => { table = await perspective.table( { x: "integer", - y: "string" + y: "string", }, {index: "x"} ); @@ -2280,7 +2643,7 @@ module.exports = perspective => { // insert table.update({ x: [i], - y: [i % 2 ? "a" : "b"] + y: [i % 2 ? "a" : "b"], }); } break; @@ -2289,7 +2652,7 @@ module.exports = perspective => { // partial update table.update({ x: [update_idx], - y: [update_idx % 2 ? "b" : "a"] + y: [update_idx % 2 ? "b" : "a"], }); update_idx++; @@ -2307,7 +2670,9 @@ module.exports = perspective => { } const flat = await flat_view.to_json(); - expect(await filtered_view.to_json()).toEqual(flat.filter(row => row.y === "a")); + expect(await filtered_view.to_json()).toEqual( + flat.filter((row) => row.y === "a") + ); op++; } }); @@ -2327,7 +2692,7 @@ module.exports = perspective => { // insert table.update({ x: [i], - y: [i % 2 ? "a" : "b"] + y: [i % 2 ? "a" : "b"], }); } break; @@ -2336,7 +2701,7 @@ module.exports = perspective => { // partial update table.update({ x: [update_idx], - y: [update_idx % 2 ? "b" : "a"] + y: [update_idx % 2 ? "b" : "a"], }); update_idx++; @@ -2355,14 +2720,18 @@ module.exports = perspective => { flat_view = await table.view(); await filtered_view.delete(); - filtered_view = await table.view({filter: [["y", "==", "a"]]}); + filtered_view = await table.view({ + filter: [["y", "==", "a"]], + }); } default: break; } const flat = await flat_view.to_json(); - expect(await filtered_view.to_json()).toEqual(flat.filter(row => row.y === "a")); + expect(await filtered_view.to_json()).toEqual( + flat.filter((row) => row.y === "a") + ); op++; } }); @@ -2380,7 +2749,9 @@ module.exports = perspective => { expect(result[result.length - 1]).toEqual({x: i, y: "a"}); // filtering - expect(await filtered_view.to_json()).toEqual(result.filter(row => row.y === "b")); + expect(await filtered_view.to_json()).toEqual( + result.filter((row) => row.y === "b") + ); } await filtered_view.delete(); @@ -2401,8 +2772,13 @@ module.exports = perspective => { flat = await flat_view.to_json(); filtered = await filtered_view.to_json(); expect(flat.length).toBeGreaterThan(filtered.length); - expect(flat.filter(row => row.x === i)[0]).toEqual({x: i, y: "b"}); - expect(filtered).toEqual(flat.filter(row => row.y === "a")); + expect(flat.filter((row) => row.x === i)[0]).toEqual({ + x: i, + y: "b", + }); + expect(filtered).toEqual( + flat.filter((row) => row.y === "a") + ); // partial updates not appends expect(await table.size()).toEqual(10); @@ -2411,13 +2787,15 @@ module.exports = perspective => { // Remove "b" rows flat = await flat_view.to_json(); - table.remove(flat.filter(row => row.y === "b").map(row => row.x)); + table.remove( + flat.filter((row) => row.y === "b").map((row) => row.x) + ); expect(await flat_view.to_json()).toEqual([ {x: 1, y: "a"}, {x: 3, y: "a"}, {x: 5, y: "a"}, {x: 7, y: "a"}, - {x: 9, y: "a"} + {x: 9, y: "a"}, ]); }); @@ -2425,7 +2803,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "string", - x: "string" + x: "string", }, {index: "index"} ); @@ -2448,7 +2826,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "string", - x: "string" + x: "string", }, {index: "index"} ); @@ -2471,7 +2849,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "string", - x: "string" + x: "string", }, {index: "index"} ); @@ -2494,7 +2872,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "float", - x: "string" + x: "string", }, {index: "index"} ); @@ -2517,7 +2895,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "float", - x: "string" + x: "string", }, {index: "index"} ); @@ -2540,19 +2918,28 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "date", - x: "string" + x: "string", }, {index: "index"} ); const view = await tbl.view(); - tbl.update({index: [new Date(2021, 3, 15), new Date(2009, 1, 13), new Date(2015, 5, 10)], x: ["abc", "def", "acc"]}); + tbl.update({ + index: [ + new Date(2021, 3, 15), + new Date(2009, 1, 13), + new Date(2015, 5, 10), + ], + x: ["abc", "def", "acc"], + }); tbl.remove([1]); const view2 = await tbl.view({filter: [["x", "==", "acc"]]}); await view.delete(); - expect(await view2.to_json()).toEqual([{index: new Date(2015, 5, 10).getTime(), x: "acc"}]); + expect(await view2.to_json()).toEqual([ + {index: new Date(2015, 5, 10).getTime(), x: "acc"}, + ]); console.log(await view2.to_json()); await view2.delete(); @@ -2563,19 +2950,28 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "date", - x: "string" + x: "string", }, {index: "index"} ); const view = await tbl.view(); - tbl.update({index: [new Date(2009, 1, 13), new Date(2015, 5, 10), new Date(2021, 3, 15)], x: ["abc", "def", "acc"]}); + tbl.update({ + index: [ + new Date(2009, 1, 13), + new Date(2015, 5, 10), + new Date(2021, 3, 15), + ], + x: ["abc", "def", "acc"], + }); tbl.remove([3]); const view2 = await tbl.view({filter: [["x", "==", "def"]]}); await view.delete(); - expect(await view2.to_json()).toEqual([{index: new Date(2015, 5, 10).getTime(), x: "def"}]); + expect(await view2.to_json()).toEqual([ + {index: new Date(2015, 5, 10).getTime(), x: "def"}, + ]); console.log(await view2.to_json()); await view2.delete(); @@ -2586,19 +2982,28 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "datetime", - x: "string" + x: "string", }, {index: "index"} ); const view = await tbl.view(); - tbl.update({index: [new Date(2021, 3, 15, 5, 10, 3), new Date(2009, 1, 13, 0, 0, 1), new Date(2015, 5, 10, 15, 10, 2)], x: ["abc", "def", "acc"]}); + tbl.update({ + index: [ + new Date(2021, 3, 15, 5, 10, 3), + new Date(2009, 1, 13, 0, 0, 1), + new Date(2015, 5, 10, 15, 10, 2), + ], + x: ["abc", "def", "acc"], + }); tbl.remove([1]); const view2 = await tbl.view({filter: [["x", "==", "acc"]]}); await view.delete(); - expect(await view2.to_json()).toEqual([{index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "acc"}]); + expect(await view2.to_json()).toEqual([ + {index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "acc"}, + ]); console.log(await view2.to_json()); await view2.delete(); @@ -2609,19 +3014,28 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "datetime", - x: "string" + x: "string", }, {index: "index"} ); const view = await tbl.view(); - tbl.update({index: [new Date(2009, 1, 13, 0, 0, 1), new Date(2015, 5, 10, 15, 10, 2), new Date(2021, 3, 15, 5, 10, 3)], x: ["abc", "def", "acc"]}); + tbl.update({ + index: [ + new Date(2009, 1, 13, 0, 0, 1), + new Date(2015, 5, 10, 15, 10, 2), + new Date(2021, 3, 15, 5, 10, 3), + ], + x: ["abc", "def", "acc"], + }); tbl.remove([3]); const view2 = await tbl.view({filter: [["x", "==", "def"]]}); await view.delete(); - expect(await view2.to_json()).toEqual([{index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "def"}]); + expect(await view2.to_json()).toEqual([ + {index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "def"}, + ]); console.log(await view2.to_json()); await view2.delete(); @@ -2632,7 +3046,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "string", - x: "string" + x: "string", }, {index: "index"} ); @@ -2650,7 +3064,7 @@ module.exports = perspective => { expect(await view2.to_json()).toEqual([ {index: "a", x: "abc"}, - {index: "b", x: "abc"} + {index: "b", x: "abc"}, ]); tbl.remove(["a"]); @@ -2659,13 +3073,13 @@ module.exports = perspective => { tbl.update([ {index: "a", x: "abc"}, - {index: null, x: "abc"} + {index: null, x: "abc"}, ]); expect(await view2.to_json()).toEqual([ {index: null, x: "abc"}, {index: "a", x: "abc"}, - {index: "b", x: "abc"} + {index: "b", x: "abc"}, ]); const indices = "abcdefghijklmnopqrstuvwxyz".split(""); @@ -2691,7 +3105,7 @@ module.exports = perspective => { const tbl = await perspective.table( { index: "string", - x: "string" + x: "string", }, {index: "index"} ); @@ -2709,7 +3123,7 @@ module.exports = perspective => { expect(await view2.to_json()).toEqual([ {index: "a", x: "abc"}, - {index: "b", x: "abc"} + {index: "b", x: "abc"}, ]); tbl.remove(["a"]); @@ -2718,23 +3132,23 @@ module.exports = perspective => { tbl.update([ {index: "a", x: "abc"}, - {index: null, x: "abc"} + {index: null, x: "abc"}, ]); expect(await view2.to_json()).toEqual([ {index: null, x: "abc"}, {index: "a", x: "abc"}, - {index: "b", x: "abc"} + {index: "b", x: "abc"}, ]); // randomize let indices = "abcdefghijklmnopqrstuvwxyz" .split("") - .map(v => { + .map((v) => { return {value: v, key: Math.random()}; }) .sort((a, b) => a.key - b.key) - .map(v => v.value); + .map((v) => v.value); console.log(indices); diff --git a/rust/perspective-viewer/concat-md.js b/rust/perspective-viewer/concat-md.js index e810980a88..cf0fb14324 100644 --- a/rust/perspective-viewer/concat-md.js +++ b/rust/perspective-viewer/concat-md.js @@ -51,16 +51,19 @@ class ConvertLinksToAnchors extends Transform { } } -const inPaths = ["./dist/docs/README.md", "./dist/docs/classes/PerspectiveViewerElement.md"]; +const inPaths = [ + "./dist/docs/README.md", + "./dist/docs/classes/PerspectiveViewerElement.md", +]; const outPath = "./README.md"; -const inputs = inPaths.map(x => fs.createReadStream(x)); +const inputs = inPaths.map((x) => fs.createReadStream(x)); const output = fs.createWriteStream(outPath); concatStreams(inputs) .pipe(new AddEndOfLine()) .pipe(new ConvertLinksToAnchors()) .pipe(output) - .on("finish", function() { + .on("finish", function () { console.log("Done merging!"); }); diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index 9d202598ed..4dc6fec7e1 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -34,8 +34,8 @@ "docs:build": "typedoc --hideBreadcrumbs --out dist/docs --readme none --excludePrivate src/ts/index.ts", "docs:concat": "node ./concat-md.js", "docs:deploy": "(echo \"---\nid: perspective\ntitle: perspective-viewer API\n---\n\n\"; cat README.md) > ../../docs/obj/perspective-viewer.md", - "fix": "yarn lint --fix", - "lint": "eslint src examples/*.md examples/*.html", + "fix": "yarn lint --fix && cargo fmt", + "lint": "eslint src/ts", "test:build:rust": "cpx ../../packages/perspective/dist/umd/perspective.inline.js dist/pkg/", "test:build:js": "cpx \"test/html/*\" dist/umd && cpx \"test/csv/*\" dist/umd && cpx \"test/css/*\" dist/umd", "test:build": "npm-run-all test:build:*", @@ -53,4 +53,4 @@ "monaco-editor": "0.24.0", "monaco-editor-webpack-plugin": "3.1.0" } -} \ No newline at end of file +} diff --git a/rust/perspective-viewer/rollup.config.js b/rust/perspective-viewer/rollup.config.js index d0a635328c..86aaf65128 100644 --- a/rust/perspective-viewer/rollup.config.js +++ b/rust/perspective-viewer/rollup.config.js @@ -10,61 +10,61 @@ export default () => { { input: `src/less/viewer.less`, output: { - dir: "dist/css" + dir: "dist/css", }, plugins: [ postcss({ inject: false, extract: path.resolve(`dist/css/viewer.css`), - minimize: {preset: "lite"} - }) - ] + minimize: {preset: "lite"}, + }), + ], }, { input: `src/less/column-style.less`, output: { - dir: "dist/css" + dir: "dist/css", }, plugins: [ postcss({ inject: false, extract: path.resolve(`dist/css/column-style.css`), - minimize: {preset: "lite"} - }) - ] + minimize: {preset: "lite"}, + }), + ], }, { input: `src/less/filter-dropdown.less`, output: { - dir: "dist/css" + dir: "dist/css", }, plugins: [ postcss({ inject: false, extract: path.resolve(`dist/css/filter-dropdown.css`), - minimize: {preset: "lite"} - }) - ] + minimize: {preset: "lite"}, + }), + ], }, { input: `src/less/expression-editor.less`, output: { - dir: "dist/css" + dir: "dist/css", }, plugins: [ postcss({ inject: false, extract: path.resolve(`dist/css/expression-editor.css`), - minimize: {preset: "lite"} - }) - ] + minimize: {preset: "lite"}, + }), + ], }, - ...["index", "monaco"].map(name => ({ + ...["index", "monaco"].map((name) => ({ input: `src/ts/${name}.ts`, external: [/pkg/, /node_modules/, /monaco\-editor/], output: { sourcemap: true, - dir: "dist/esm/" + dir: "dist/esm/", }, plugins: [ typescript({tsconfig: "./tsconfig.json"}), @@ -72,15 +72,15 @@ export default () => { postcss({ inject: false, sourceMap: true, - minimize: {mergeLonghand: false} + minimize: {mergeLonghand: false}, }), - sourcemaps() + sourcemaps(), ], watch: { - clearScreen: false - } + clearScreen: false, + }, })), - ...generate_themes() + ...generate_themes(), ]; }; @@ -91,7 +91,7 @@ function generate_themes() { return { input: `${val}`, output: { - dir: "dist/umd" + dir: "dist/umd", }, plugins: [ { @@ -100,21 +100,29 @@ function generate_themes() { return null; }, buildEnd: () => { - fs.rm(path.resolve(__dirname, "dist", "css", `${key}.js`), () => {}); + fs.rm( + path.resolve(__dirname, "dist", "css", `${key}.js`), + () => {} + ); }, load(id) { return null; - } + }, }, postcss({ inject: false, extract: path.resolve(`dist/umd/${key}.css`), sourceMap: false, - minimize: false - }) - ] + minimize: false, + }), + ], }; } - return THEMES.map(theme => reducer(theme.replace(".less", ""), path.resolve(__dirname, "src", "themes", theme))); + return THEMES.map((theme) => + reducer( + theme.replace(".less", ""), + path.resolve(__dirname, "src", "themes", theme) + ) + ); } diff --git a/rust/perspective-viewer/src/ts/index.ts b/rust/perspective-viewer/src/ts/index.ts index d766a2c957..762063cf0b 100644 --- a/rust/perspective-viewer/src/ts/index.ts +++ b/rust/perspective-viewer/src/ts/index.ts @@ -35,7 +35,7 @@ import * as perspective from "@finos/perspective"; // There is no way to provide a default rejection handler within a promise and // also not lock the await-er, so this module attaches a global handler to // filter out cancelled query messages. -window.addEventListener("unhandledrejection", event => { +window.addEventListener("unhandledrejection", (event) => { if (event.reason?.message === "View method cancelled") { event.preventDefault(); } @@ -56,7 +56,7 @@ const WASM_MODULE = import( export type PerspectiveViewerConfig = perspective.ViewConfig & { plugin?: string; settings?: boolean; - plugin_config?: object; + plugin_config?: any; }; /** @@ -120,7 +120,7 @@ export class PerspectiveViewerElement extends HTMLElement { * @param name The `name` of the custom element to register, as supplied * to the `customElements.define(name)` method. */ - static async registerPlugin(name): Promise { + static async registerPlugin(name: string): Promise { const module = await WASM_MODULE; module.register_plugin(name); } @@ -234,7 +234,9 @@ export class PerspectiveViewerElement extends HTMLElement { * await viewer.restore(token); * ``` */ - async restore(config: PerspectiveViewerConfig | string | ArrayBuffer): Promise { + async restore( + config: PerspectiveViewerConfig | string | ArrayBuffer + ): Promise { await this.load_wasm(); await this.instance.js_restore(config); } @@ -263,7 +265,9 @@ export class PerspectiveViewerElement extends HTMLElement { async save(format: "json"): Promise; async save(format: "arraybuffer"): Promise; async save(format: "string"): Promise; - async save(format?: "json" | "arraybuffer" | "string"): Promise { + async save( + format?: "json" | "arraybuffer" | "string" + ): Promise { await this.load_wasm(); const config = await this.instance.js_save(format); return config; @@ -460,7 +464,7 @@ export class PerspectiveViewerElement extends HTMLElement { * active plugin. * @returns The active or requested plugin instance. */ - async getPlugin(name): Promise { + async getPlugin(name?: string): Promise { await this.load_wasm(); const plugin = await this.instance.js_get_plugin(name); return plugin; @@ -485,7 +489,10 @@ export class PerspectiveViewerElement extends HTMLElement { } if (document.createElement("perspective-viewer").constructor === HTMLElement) { - window.customElements.define("perspective-viewer", PerspectiveViewerElement); + window.customElements.define( + "perspective-viewer", + PerspectiveViewerElement + ); } class PerspectiveColumnStyleElement extends HTMLElement { @@ -495,24 +502,41 @@ class PerspectiveColumnStyleElement extends HTMLElement { super(); } - async open(target: HTMLElement, config: any, default_config: any): Promise { + async open( + target: HTMLElement, + config: any, + default_config: any + ): Promise { if (this.instance) { this.instance.reset(config, default_config); } else { - this.instance = new internal.PerspectiveColumnStyleElement(this, config, default_config); + this.instance = new internal.PerspectiveColumnStyleElement( + this, + config, + default_config + ); } this.instance.open(target); } } -if (document.createElement("perspective-column-style").constructor === HTMLElement) { - window.customElements.define("perspective-column-style", PerspectiveColumnStyleElement); +if ( + document.createElement("perspective-column-style").constructor === + HTMLElement +) { + window.customElements.define( + "perspective-column-style", + PerspectiveColumnStyleElement + ); } type ReactPerspectiveViewerAttributes = React.HTMLAttributes; -type JsxPerspectiveViewerElement = {class?: string} & React.DetailedHTMLProps, PerspectiveViewerElement>; +type JsxPerspectiveViewerElement = {class?: string} & React.DetailedHTMLProps< + ReactPerspectiveViewerAttributes, + PerspectiveViewerElement +>; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -523,6 +547,9 @@ declare global { } interface Document { - createElement(tagName: "perspective-viewer", options?: ElementCreationOptions): PerspectiveViewerElement; + createElement( + tagName: "perspective-viewer", + options?: ElementCreationOptions + ): PerspectiveViewerElement; } } diff --git a/rust/perspective-viewer/src/ts/monaco.ts b/rust/perspective-viewer/src/ts/monaco.ts index 2b4e000792..fd42d7e72f 100644 --- a/rust/perspective-viewer/src/ts/monaco.ts +++ b/rust/perspective-viewer/src/ts/monaco.ts @@ -8,56 +8,55 @@ * */ - import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js"; - import "monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js"; - import "monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js"; - import "monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js"; - import "monaco-editor/esm/vs/editor/contrib/anchorSelect/anchorSelect.js"; - import "monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js"; - import "monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js"; - import "monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js"; - import "monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js"; - import "monaco-editor/esm/vs/editor/contrib/codeAction/codeActionContributions.js"; - import "monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js"; - import "monaco-editor/esm/vs/editor/contrib/colorPicker/colorContributions.js"; - import "monaco-editor/esm/vs/editor/contrib/comment/comment.js"; - import "monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js"; - import "monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js"; - import "monaco-editor/esm/vs/editor/contrib/dnd/dnd.js"; - import "monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols.js"; - import "monaco-editor/esm/vs/editor/contrib/find/findController.js"; - import "monaco-editor/esm/vs/editor/contrib/folding/folding.js"; - import "monaco-editor/esm/vs/editor/contrib/fontZoom/fontZoom.js"; - import "monaco-editor/esm/vs/editor/contrib/format/formatActions.js"; - import "monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js"; - import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/goToCommands.js"; - import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.js"; - import "monaco-editor/esm/vs/editor/contrib/hover/hover.js"; - import "monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js"; - import "monaco-editor/esm/vs/editor/contrib/indentation/indentation.js"; - import "monaco-editor/esm/vs/editor/contrib/inlineHints/inlineHintsController.js"; - import "monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js"; - import "monaco-editor/esm/vs/editor/contrib/linkedEditing/linkedEditing.js"; - import "monaco-editor/esm/vs/editor/contrib/links/links.js"; - import "monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js"; - import "monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js"; - import "monaco-editor/esm/vs/editor/contrib/rename/rename.js"; - import "monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js"; - import "monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js"; - import "monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js"; - import "monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js"; - import "monaco-editor/esm/vs/editor/contrib/unusualLineTerminators/unusualLineTerminators.js"; - import "monaco-editor/esm/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.js"; - import "monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js"; - import "monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js"; - import "monaco-editor/esm/vs/editor/contrib/wordPartOperations/wordPartOperations.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.js"; - import "monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js"; - \ No newline at end of file +import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js"; +import "monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js"; +import "monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js"; +import "monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js"; +import "monaco-editor/esm/vs/editor/contrib/anchorSelect/anchorSelect.js"; +import "monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js"; +import "monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js"; +import "monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js"; +import "monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js"; +import "monaco-editor/esm/vs/editor/contrib/codeAction/codeActionContributions.js"; +import "monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js"; +import "monaco-editor/esm/vs/editor/contrib/colorPicker/colorContributions.js"; +import "monaco-editor/esm/vs/editor/contrib/comment/comment.js"; +import "monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js"; +import "monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js"; +import "monaco-editor/esm/vs/editor/contrib/dnd/dnd.js"; +import "monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols.js"; +import "monaco-editor/esm/vs/editor/contrib/find/findController.js"; +import "monaco-editor/esm/vs/editor/contrib/folding/folding.js"; +import "monaco-editor/esm/vs/editor/contrib/fontZoom/fontZoom.js"; +import "monaco-editor/esm/vs/editor/contrib/format/formatActions.js"; +import "monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js"; +import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/goToCommands.js"; +import "monaco-editor/esm/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.js"; +import "monaco-editor/esm/vs/editor/contrib/hover/hover.js"; +import "monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js"; +import "monaco-editor/esm/vs/editor/contrib/indentation/indentation.js"; +import "monaco-editor/esm/vs/editor/contrib/inlineHints/inlineHintsController.js"; +import "monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js"; +import "monaco-editor/esm/vs/editor/contrib/linkedEditing/linkedEditing.js"; +import "monaco-editor/esm/vs/editor/contrib/links/links.js"; +import "monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js"; +import "monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js"; +import "monaco-editor/esm/vs/editor/contrib/rename/rename.js"; +import "monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js"; +import "monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js"; +import "monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js"; +import "monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js"; +import "monaco-editor/esm/vs/editor/contrib/unusualLineTerminators/unusualLineTerminators.js"; +import "monaco-editor/esm/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.js"; +import "monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js"; +import "monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js"; +import "monaco-editor/esm/vs/editor/contrib/wordPartOperations/wordPartOperations.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.js"; +import "monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js"; diff --git a/rust/perspective-viewer/test/js/expressions.spec.js b/rust/perspective-viewer/test/js/expressions.spec.js index 8839d37306..530f4467dc 100644 --- a/rust/perspective-viewer/test/js/expressions.spec.js +++ b/rust/perspective-viewer/test/js/expressions.spec.js @@ -14,34 +14,53 @@ utils.with_server({}, () => { describe.page( "superstore.html", () => { - test.capture("click on add column button opens the expression UI.", async page => { - await page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - await elem.getTable(); - await elem.toggleConfig(true); - }); + test.capture( + "click on add column button opens the expression UI.", + async (page) => { + await page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + await elem.getTable(); + await elem.toggleConfig(true); + }); - await page.shadow_click("perspective-viewer", "#add-expression"); - const monaco = await page.waitFor(async () => { - const elem = document.querySelector("perspective-expression-editor"); - return elem?.shadowRoot.querySelector("#monaco-container"); - }); + await page.shadow_click( + "perspective-viewer", + "#add-expression" + ); + const monaco = await page.waitFor(async () => { + const elem = document.querySelector( + "perspective-expression-editor" + ); + return elem?.shadowRoot.querySelector( + "#monaco-container" + ); + }); - return await monaco.evaluate(x => x.outerHTML); - }); + return await monaco.evaluate((x) => x.outerHTML); + } + ); - test.capture("blur closes the expression UI.", async page => { + test.capture("blur closes the expression UI.", async (page) => { await page.evaluate(async () => { document.activeElement.blur(); const elem = document.querySelector("perspective-viewer"); await elem.toggleConfig(true); }); - await page.shadow_click("perspective-viewer", "#add-expression"); - await page.waitForSelector("perspective-expression-editor:not([initializing]"); + await page.shadow_click( + "perspective-viewer", + "#add-expression" + ); + await page.waitForSelector( + "perspective-expression-editor:not([initializing]" + ); await page.evaluate(() => document.activeElement.blur()); return await page.evaluate(async () => { - return document.querySelector("perspective-expression-editor")?.shadowRoot?.innerHTML || "MISSING"; + return ( + document.querySelector("perspective-expression-editor") + ?.shadowRoot?.innerHTML || "MISSING" + ); }); }); @@ -52,210 +71,320 @@ utils.with_server({}, () => { await elem.toggleConfig(true); }); - await page.shadow_click("perspective-viewer", "#add-expression"); - await page.waitForSelector("perspective-expression-editor:not([initializing])"); - await page.shadow_type(expr, "perspective-expression-editor", "textarea"); - await page.waitForSelector("perspective-expression-editor:not([validating])"); + await page.shadow_click( + "perspective-viewer", + "#add-expression" + ); + await page.waitForSelector( + "perspective-expression-editor:not([initializing])" + ); + await page.shadow_type( + expr, + "perspective-expression-editor", + "textarea" + ); + await page.waitForSelector( + "perspective-expression-editor:not([validating])" + ); return await page.evaluate(async () => { - const elem = document.querySelector("perspective-expression-editor"); - return elem.shadowRoot.querySelector("button").getAttribute("disabled") || "MISSING"; + const elem = document.querySelector( + "perspective-expression-editor" + ); + return ( + elem.shadowRoot + .querySelector("button") + .getAttribute("disabled") || "MISSING" + ); }); } // Functionality - make sure the UI will validate error cases so // the engine is not affected. - test.capture("An expression with unknown symbols should disable the save button", async page => { - return await type_expression_test(page, "abc"); - }); - - test.capture("A type-invalid expression should disable the save button", async page => { - return await type_expression_test(page, '"Sales" + "Category";'); - }); - - test.capture("An expression with invalid input columns should disable the save button", async page => { - return await type_expression_test(page, '"aaaa" + "Sales";'); - }); - - test.capture("Should show both aliased and non-aliased expressions in columns", async page => { - return await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["1 + 2", "// abc \n3 + 4"] + test.capture( + "An expression with unknown symbols should disable the save button", + async (page) => { + return await type_expression_test(page, "abc"); + } + ); + + test.capture( + "A type-invalid expression should disable the save button", + async (page) => { + return await type_expression_test( + page, + '"Sales" + "Category";' + ); + } + ); + + test.capture( + "An expression with invalid input columns should disable the save button", + async (page) => { + return await type_expression_test( + page, + '"aaaa" + "Sales";' + ); + } + ); + + test.capture( + "Should show both aliased and non-aliased expressions in columns", + async (page) => { + return await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["1 + 2", "// abc \n3 + 4"], + }); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; }); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Should save an expression when the save button is clicked", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - }); - - await type_expression_test(page, "// abc2 \n 4 + 5"); - await page.shadow_click("perspective-expression-editor", "button"); - - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Should overwrite a duplicate expression alias", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["// abc \n3 + 4"] + } + ); + + test.capture( + "Should save an expression when the save button is clicked", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); }); - }); - - await type_expression_test(page, "// abc \n 4 + 5"); - await page.shadow_click("perspective-expression-editor", "button"); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Should overwrite a duplicate expression", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["3 + 4"] + await type_expression_test(page, "// abc2 \n 4 + 5"); + await page.shadow_click( + "perspective-expression-editor", + "button" + ); + + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; }); - }); - - await type_expression_test(page, "3 + 4"); - await page.shadow_click("perspective-expression-editor", "button"); - - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Resetting the viewer should delete all expressions", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["3 + 4", "1 + 2"] + } + ); + + test.capture( + "Should overwrite a duplicate expression alias", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["// abc \n3 + 4"], + }); }); - await elem.reset(); - }); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Resetting the viewer when expression as in columns field, should delete all expressions", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - columns: ["1 + 2"], - expressions: ["3 + 4", "1 + 2"] + await type_expression_test(page, "// abc \n 4 + 5"); + await page.shadow_click( + "perspective-expression-editor", + "button" + ); + + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; }); - await elem.reset(); - }); - - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); - - test.capture("Resetting the viewer when expression as in row_pivots or other field, should delete all expressions", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - columns: ["1 + 2"], - row_pivots: ["3 + 4"], - sort: [["1 + 2", "asc"]], - filter: [["1 + 2", "==", 3]], - expressions: ["3 + 4", "1 + 2"] + } + ); + + test.capture( + "Should overwrite a duplicate expression", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["3 + 4"], + }); }); - await elem.reset(); - }); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); + await type_expression_test(page, "3 + 4"); + await page.shadow_click( + "perspective-expression-editor", + "button" + ); + + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; + }); + } + ); + + test.capture( + "Resetting the viewer should delete all expressions", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["3 + 4", "1 + 2"], + }); + await elem.reset(); + }); - test.capture("Expressions should persist when new views are created which don't use them", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["3 + 4", "1 + 2"] + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; }); - await elem.restore({ - columns: ["State"] + } + ); + + test.capture( + "Resetting the viewer when expression as in columns field, should delete all expressions", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + columns: ["1 + 2"], + expressions: ["3 + 4", "1 + 2"], + }); + await elem.reset(); }); - }); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; + }); + } + ); + + test.capture( + "Resetting the viewer when expression as in row_pivots or other field, should delete all expressions", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + columns: ["1 + 2"], + row_pivots: ["3 + 4"], + sort: [["1 + 2", "asc"]], + filter: [["1 + 2", "==", 3]], + expressions: ["3 + 4", "1 + 2"], + }); + await elem.reset(); + }); - test.capture("Expressions should persist when new views are created using them", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ["3 + 4", "1 + 2"] + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; }); - await elem.restore({ - columns: ["3 + 4"] + } + ); + + test.capture( + "Expressions should persist when new views are created which don't use them", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["3 + 4", "1 + 2"], + }); + await elem.restore({ + columns: ["State"], + }); }); - }); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; + }); + } + ); + + test.capture( + "Expressions should persist when new views are created using them", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ["3 + 4", "1 + 2"], + }); + await elem.restore({ + columns: ["3 + 4"], + }); + }); - test.capture("Aggregates for expressions should apply", async page => { - await page.evaluate(async () => { - document.activeElement.blur(); - const elem = document.querySelector("perspective-viewer"); - await elem.toggleConfig(true); - await elem.restore({ - expressions: ['"Sales" + 100'], - aggregates: {'"Sales" + 100': "avg"}, - row_pivots: ["State"], - columns: ['"Sales" + 100'] + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; + }); + } + ); + + test.capture( + "Aggregates for expressions should apply", + async (page) => { + await page.evaluate(async () => { + document.activeElement.blur(); + const elem = + document.querySelector("perspective-viewer"); + await elem.toggleConfig(true); + await elem.restore({ + expressions: ['"Sales" + 100'], + aggregates: {'"Sales" + 100': "avg"}, + row_pivots: ["State"], + columns: ['"Sales" + 100'], + }); }); - }); - return page.evaluate(async () => { - const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; - }); - }); + return page.evaluate(async () => { + const elem = + document.querySelector("perspective-viewer"); + return elem.shadowRoot.querySelector( + "#expression-columns" + ).innerHTML; + }); + } + ); - test.capture("Should sort by hidden expressions", async page => { + test.capture("Should sort by hidden expressions", async (page) => { await page.evaluate(async () => { document.activeElement.blur(); const elem = document.querySelector("perspective-viewer"); @@ -263,17 +392,18 @@ utils.with_server({}, () => { await elem.restore({ expressions: ['"Sales" + 100'], sort: [['"Sales" + 100', "asc"]], - columns: ["Row ID"] + columns: ["Row ID"], }); }); return page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; + return elem.shadowRoot.querySelector("#expression-columns") + .innerHTML; }); }); - test.capture("Should filter by an expression", async page => { + test.capture("Should filter by an expression", async (page) => { await page.evaluate(async () => { document.activeElement.blur(); const elem = document.querySelector("perspective-viewer"); @@ -281,19 +411,20 @@ utils.with_server({}, () => { await elem.restore({ expressions: ['"Sales" + 100'], filter: [['"Sales" + 100', ">", 150]], - columns: ["Row ID", '"Sales" + 100'] + columns: ["Row ID", '"Sales" + 100'], }); }); return page.evaluate(async () => { const elem = document.querySelector("perspective-viewer"); - return elem.shadowRoot.querySelector("#expression-columns").innerHTML; + return elem.shadowRoot.querySelector("#expression-columns") + .innerHTML; }); }); }, { root: path.join(__dirname, "..", ".."), - name: "Expressions" + name: "Expressions", } ); }); diff --git a/rust/perspective-viewer/test/js/leaks.spec.js b/rust/perspective-viewer/test/js/leaks.spec.js index 34826ad321..e539c3da37 100644 --- a/rust/perspective-viewer/test/js/leaks.spec.js +++ b/rust/perspective-viewer/test/js/leaks.spec.js @@ -16,21 +16,23 @@ utils.with_server({}, () => { () => { test.capture( "doesn't leak elements", - async page => { + async (page) => { let viewer = await page.$("perspective-viewer"); - await page.evaluate(async viewer => { + await page.evaluate(async (viewer) => { window.__TABLE__ = await viewer.getTable(); await viewer.reset(); }, viewer); for (var i = 0; i < 500; i++) { - await page.evaluate(async element => { + await page.evaluate(async (element) => { await element.delete(); - await element.load(Promise.resolve(window.__TABLE__)); + await element.load( + Promise.resolve(window.__TABLE__) + ); }, viewer); } - return await page.evaluate(async viewer => { + return await page.evaluate(async (viewer) => { await viewer.toggleConfig(); return viewer.innerHTML; }, viewer); @@ -40,24 +42,37 @@ utils.with_server({}, () => { test.capture( "doesn't leak views when setting row pivots", - async page => { + async (page) => { let viewer = await page.$("perspective-viewer"); - await page.evaluate(async viewer => { + await page.evaluate(async (viewer) => { window.__TABLE__ = await viewer.getTable(); await viewer.reset(); }, viewer); for (var i = 0; i < 500; i++) { - await page.evaluate(async element => { + await page.evaluate(async (element) => { await element.reset(); - let pivots = ["State", "City", "Segment", "Ship Mode", "Region", "Category"]; - let start = Math.floor(Math.random() * pivots.length); - let length = Math.ceil(Math.random() * (pivots.length - start)); - await element.restore({row_pivots: pivots.slice(start, length)}); + let pivots = [ + "State", + "City", + "Segment", + "Ship Mode", + "Region", + "Category", + ]; + let start = Math.floor( + Math.random() * pivots.length + ); + let length = Math.ceil( + Math.random() * (pivots.length - start) + ); + await element.restore({ + row_pivots: pivots.slice(start, length), + }); }, viewer); } - return await page.evaluate(async viewer => { + return await page.evaluate(async (viewer) => { await viewer.restore({row_pivots: ["State"]}); await viewer.toggleConfig(); return viewer.innerHTML; @@ -68,21 +83,25 @@ utils.with_server({}, () => { test.capture( "doesn't leak views when setting filters", - async page => { + async (page) => { let viewer = await page.$("perspective-viewer"); - await page.evaluate(async viewer => { + await page.evaluate(async (viewer) => { window.__TABLE__ = await viewer.getTable(); await viewer.reset(); }, viewer); for (var i = 0; i < 500; i++) { - await page.evaluate(async element => { + await page.evaluate(async (element) => { await element.reset(); - await element.restore({filter: [["Sales", ">", Math.random() * 100 + 100]]}); + await element.restore({ + filter: [ + ["Sales", ">", Math.random() * 100 + 100], + ], + }); }, viewer); } - return await page.evaluate(async viewer => { + return await page.evaluate(async (viewer) => { await viewer.restore({filter: [["Sales", "<", 10]]}); await viewer.toggleConfig(); return viewer.innerHTML; diff --git a/rust/perspective-viewer/test/js/render_warning_tests.js b/rust/perspective-viewer/test/js/render_warning_tests.js index 4d98cea4a0..0674c128f1 100644 --- a/rust/perspective-viewer/test/js/render_warning_tests.js +++ b/rust/perspective-viewer/test/js/render_warning_tests.js @@ -7,7 +7,7 @@ * */ -const convert = x => +const convert = (x) => ({ "X/Y Scatter": "perspective-viewer-d3fc-xyscatter", "Y Scatter": "Perspective-viewer-d3fc-yscatter", @@ -15,10 +15,10 @@ const convert = x => Heatmap: "perspective-viewer-d3fc-heatmap", Treemap: "perspective-viewer-d3fc-treemap", "X Bar": "perspective-viewer-d3fc-xbar", - "Y Bar": "perspective-viewer-d3fc-ybar" + "Y Bar": "perspective-viewer-d3fc-ybar", }[x] || x); -exports.default = function(plugin_name, columns) { +exports.default = function (plugin_name, columns) { let view_columns = ["Sales"]; if (columns) { @@ -29,96 +29,215 @@ exports.default = function(plugin_name, columns) { view_columns = JSON.stringify(view_columns); - test.capture("warning should be shown when points exceed max_cells and max_columns", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_columns = 1; - plugin.max_cells = 1; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); - - test.capture("warning should be shown when points exceed max_cells but not max_columns", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_cells = 1; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); - - test.capture("warning should be shown when points exceed max_columns but not max_cells", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_columns = 1; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); - - test.capture("warning should be re-rendered if the config is changed and points exceed max", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_columns = 1; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Row ID"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); - - test.capture("warning should not be re-rendered if the config is changed and points do not exceed max", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_columns = 5; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.evaluate(element => element.removeAttribute("column-pivots"), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); - - test.capture("warning should not be rendered again after the user clicks render all points", async page => { - const viewer = await page.$("perspective-viewer"); - await page.evaluate(plugin_name => { - const plugin = document.querySelector(plugin_name); - plugin.max_columns = 1; - }, convert(plugin_name)); - - await page.evaluate(async () => await document.querySelector("perspective-viewer").toggleConfig()); - await page.evaluate((element, view_columns) => element.setAttribute("columns", view_columns), viewer, view_columns); - await page.evaluate(element => element.setAttribute("column-pivots", '["Row ID"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - await page.waitFor( - () => - !!document - .querySelector("perspective-viewer") - .shadowRoot.querySelector("perspective-vieux") - .shadowRoot.querySelector(".plugin_information--warning:not(.hidden)") - ); - await page.shadow_click("perspective-viewer", "perspective-vieux", ".plugin_information__action"); - await page.evaluate(element => element.setAttribute("column-pivots", '["Profit"]'), viewer); - await page.waitForSelector("perspective-viewer:not([updating])"); - }); + test.capture( + "warning should be shown when points exceed max_cells and max_columns", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_columns = 1; + plugin.max_cells = 1; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); + + test.capture( + "warning should be shown when points exceed max_cells but not max_columns", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_cells = 1; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); + + test.capture( + "warning should be shown when points exceed max_columns but not max_cells", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_columns = 1; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); + + test.capture( + "warning should be re-rendered if the config is changed and points exceed max", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_columns = 1; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Row ID"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); + + test.capture( + "warning should not be re-rendered if the config is changed and points do not exceed max", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_columns = 5; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + await page.evaluate( + (element) => element.removeAttribute("column-pivots"), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); + + test.capture( + "warning should not be rendered again after the user clicks render all points", + async (page) => { + const viewer = await page.$("perspective-viewer"); + await page.evaluate((plugin_name) => { + const plugin = document.querySelector(plugin_name); + plugin.max_columns = 1; + }, convert(plugin_name)); + + await page.evaluate( + async () => + await document + .querySelector("perspective-viewer") + .toggleConfig() + ); + await page.evaluate( + (element, view_columns) => + element.setAttribute("columns", view_columns), + viewer, + view_columns + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Row ID"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + await page.waitFor( + () => + !!document + .querySelector("perspective-viewer") + .shadowRoot.querySelector("perspective-vieux") + .shadowRoot.querySelector( + ".plugin_information--warning:not(.hidden)" + ) + ); + await page.shadow_click( + "perspective-viewer", + "perspective-vieux", + ".plugin_information__action" + ); + await page.evaluate( + (element) => + element.setAttribute("column-pivots", '["Profit"]'), + viewer + ); + await page.waitForSelector("perspective-viewer:not([updating])"); + } + ); }; diff --git a/rust/perspective-viewer/test/js/settings.spec.js b/rust/perspective-viewer/test/js/settings.spec.js index f6a463e9c5..d76a901659 100644 --- a/rust/perspective-viewer/test/js/settings.spec.js +++ b/rust/perspective-viewer/test/js/settings.spec.js @@ -13,7 +13,9 @@ const path = require("path"); async function get_contents(page) { return await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer").shadowRoot.querySelector("#app_panel"); + const viewer = document + .querySelector("perspective-viewer") + .shadowRoot.querySelector("#app_panel"); return viewer ? viewer.innerHTML : "MISSING"; }); } @@ -22,25 +24,33 @@ utils.with_server({}, () => { describe.page( "superstore.html", () => { - test.capture("opens settings when field is set to true", async page => { - await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer"); - await viewer.getTable(); - await viewer.restore({settings: true}); - }); + test.capture( + "opens settings when field is set to true", + async (page) => { + await page.evaluate(async () => { + const viewer = + document.querySelector("perspective-viewer"); + await viewer.getTable(); + await viewer.restore({settings: true}); + }); - return await get_contents(page); - }); + return await get_contents(page); + } + ); - test.capture("opens settings when field is set to false", async page => { - await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer"); - await viewer.getTable(); - await viewer.restore({settings: false}); - }); + test.capture( + "opens settings when field is set to false", + async (page) => { + await page.evaluate(async () => { + const viewer = + document.querySelector("perspective-viewer"); + await viewer.getTable(); + await viewer.restore({settings: false}); + }); - return await get_contents(page); - }); + return await get_contents(page); + } + ); }, {root: path.join(__dirname, "..", "..")} ); diff --git a/rust/perspective-viewer/test/js/simple_tests.js b/rust/perspective-viewer/test/js/simple_tests.js index 0b0b47eaa0..fc31fbd893 100644 --- a/rust/perspective-viewer/test/js/simple_tests.js +++ b/rust/perspective-viewer/test/js/simple_tests.js @@ -9,13 +9,15 @@ async function get_contents_default(page) { return await page.evaluate(async () => { - const viewer = document.querySelector("perspective-viewer perspective-viewer-debug"); + const viewer = document.querySelector( + "perspective-viewer perspective-viewer-debug" + ); return viewer.innerHTML; }); } -exports.default = function(get_contents = get_contents_default) { - test.capture("shows a grid without any settings applied", async page => { +exports.default = function (get_contents = get_contents_default) { + test.capture("shows a grid without any settings applied", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.getTable(); @@ -25,72 +27,74 @@ exports.default = function(get_contents = get_contents_default) { return await get_contents(page); }); - test.capture("displays visible columns.", async page => { + test.capture("displays visible columns.", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); - await viewer.restore({columns: ["Discount", "Profit", "Sales", "Quantity"]}); + await viewer.restore({ + columns: ["Discount", "Profit", "Sales", "Quantity"], + }); }); return await get_contents(page); }); describe("pivot", () => { - test.capture("by a row", async page => { + test.capture("by a row", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ row_pivots: ["State"], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by two rows", async page => { + test.capture("by two rows", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ row_pivots: ["Category", "Sub-Category"], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by a column", async page => { + test.capture("by a column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ column_pivots: ["Category"], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by a row and a column", async page => { + test.capture("by a row and a column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ row_pivots: ["State"], column_pivots: ["Category"], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by two rows and two columns", async page => { + test.capture("by two rows and two columns", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ row_pivots: ["Region", "State"], column_pivots: ["Category", "Sub-Category"], - settings: true + settings: true, }); await viewer.notifyResize(); }); @@ -100,39 +104,39 @@ exports.default = function(get_contents = get_contents_default) { }); describe("sort", () => { - test.capture("by a hidden column", async page => { + test.capture("by a hidden column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "Quantity"], sort: [["Sales", "asc"]], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by a numeric column", async page => { + test.capture("by a numeric column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "Sales"], sort: [["Sales", "asc"]], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("by an alpha column", async page => { + test.capture("by an alpha column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "State", "Sales"], sort: [["State", "asc"]], - settings: true + settings: true, }); }); @@ -141,52 +145,52 @@ exports.default = function(get_contents = get_contents_default) { }); describe("filters", () => { - test.capture("filters by a numeric column", async page => { + test.capture("filters by a numeric column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "State", "Sales"], filter: [["Sales", ">", 500]], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("filters by an alpha column", async page => { + test.capture("filters by an alpha column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "State", "Sales"], filter: [["State", "==", "Texas"]], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.capture("filters with 'in' comparator", async page => { + test.capture("filters with 'in' comparator", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "State", "Sales"], filter: [["State", "in", ["Texas", "California"]]], - settings: true + settings: true, }); }); return await get_contents(page); }); - test.skip("filters by a datetime column", async page => { + test.skip("filters by a datetime column", async (page) => { await page.evaluate(async () => { const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ columns: ["Row ID", "Order Date", "Sales"], filter: [["Order Date", ">", "01/01/2012"]], - settings: true + settings: true, }); }); diff --git a/rust/perspective-viewer/test/js/superstore.spec.js b/rust/perspective-viewer/test/js/superstore.spec.js index cb9bcab9f7..e088705d6c 100644 --- a/rust/perspective-viewer/test/js/superstore.spec.js +++ b/rust/perspective-viewer/test/js/superstore.spec.js @@ -17,7 +17,6 @@ utils.with_server({}, () => { "superstore.html", () => { simple_tests.default(); - }, {reload_page: false, root: path.join(__dirname, "..", "..")} ); diff --git a/rust/perspective-viewer/test/js/test_arrows.js b/rust/perspective-viewer/test/js/test_arrows.js index a89087eab6..dfb8990cfb 100644 --- a/rust/perspective-viewer/test/js/test_arrows.js +++ b/rust/perspective-viewer/test/js/test_arrows.js @@ -24,11 +24,16 @@ const path = require("path"); */ function load_arrow(arrow_path) { const data = fs.readFileSync(arrow_path); - return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); + return data.buffer.slice( + data.byteOffset, + data.byteOffset + data.byteLength + ); } -const int_float_str_arrow = load_arrow(path.join(__dirname, "..", "arrow", "int_float_str.arrow")); +const int_float_str_arrow = load_arrow( + path.join(__dirname, "..", "arrow", "int_float_str.arrow") +); module.exports = { - int_float_str_arrow + int_float_str_arrow, }; diff --git a/rust/perspective-viewer/webpack.config.js b/rust/perspective-viewer/webpack.config.js index 5a24a715d0..95f0d3f378 100644 --- a/rust/perspective-viewer/webpack.config.js +++ b/rust/perspective-viewer/webpack.config.js @@ -3,19 +3,19 @@ const common = require("@finos/perspective/src/config/common.config.js"); let idx = 0; -module.exports = common({}, config => { +module.exports = common({}, (config) => { return Object.assign(config, { entry: { - "perspective-viewer": "./dist/esm/index.js" + "perspective-viewer": "./dist/esm/index.js", }, output: { filename: "[name].js", chunkFilename: "perspective-viewer.[name].js", libraryTarget: "umd", - path: path.resolve(__dirname, "./dist/umd") + path: path.resolve(__dirname, "./dist/umd"), }, experiments: { - syncWebAssembly: true - } + syncWebAssembly: true, + }, }); }); diff --git a/scripts/_wheel_python.js b/scripts/_wheel_python.js index f4d74106e1..8f20c72f2e 100644 --- a/scripts/_wheel_python.js +++ b/scripts/_wheel_python.js @@ -7,12 +7,28 @@ * */ -const {execute, docker, clean, resolve, getarg, bash, python_image} = require("./script_utils.js"); +const { + execute, + docker, + clean, + resolve, + getarg, + bash, + python_image, +} = require("./script_utils.js"); const fs = require("fs-extra"); const IS_DOCKER = process.env.PSP_DOCKER; const IS_MACOS = getarg("--macos"); const IS_PY2 = getarg("--python2"); -const PYTHON = IS_PY2 ? "python2" : getarg("--python39") ? "python3.9" : getarg("--python38") ? "python3.8" : getarg("--python36") ? "python3.6" : "python3.7"; +const PYTHON = IS_PY2 + ? "python2" + : getarg("--python39") + ? "python3.9" + : getarg("--python38") + ? "python3.8" + : getarg("--python36") + ? "python3.6" + : "python3.7"; let IMAGE = "manylinux2014"; let MANYLINUX_VERSION; @@ -22,7 +38,11 @@ if (IS_DOCKER) { MANYLINUX_VERSION = "manylinux2010"; if (!IS_PY2) { // switch to 2014 only on python3 - MANYLINUX_VERSION = getarg("--manylinux2010") ? "manylinux2010" : getarg("--manylinux2014") ? "manylinux2014" : "manylinux2014"; + MANYLINUX_VERSION = getarg("--manylinux2010") + ? "manylinux2010" + : getarg("--manylinux2014") + ? "manylinux2014" + : "manylinux2014"; } IMAGE = python_image(MANYLINUX_VERSION, PYTHON); } @@ -96,7 +116,9 @@ try { // `import perspective`. if (IS_DOCKER) { - console.log(`Building wheel for \`perspective-python\` using image \`${IMAGE}\` in Docker`); + console.log( + `Building wheel for \`perspective-python\` using image \`${IMAGE}\` in Docker` + ); execute`${docker(IMAGE)} bash -c "cd python/perspective && ${cmd}"`; } else { console.log(`Building wheel for \`perspective-python\``); diff --git a/scripts/bench.js b/scripts/bench.js index a734a7c0b9..7b1616f9eb 100644 --- a/scripts/bench.js +++ b/scripts/bench.js @@ -15,11 +15,13 @@ const args = process.argv.slice(2); if (process.env.PSP_PROJECT === undefined || process.env.PSP_PROJECT === "js") { function docker() { console.log("Creating puppeteer docker image"); - let cmd = "docker run -it --rm --shm-size=2g --cap-add=SYS_NICE -u root -e PACKAGE=${PACKAGE} -e HTTPS_PROXY -e HTTPS_PROXY -v $(pwd):/src -w /src"; + let cmd = + "docker run -it --rm --shm-size=2g --cap-add=SYS_NICE -u root -e PACKAGE=${PACKAGE} -e HTTPS_PROXY -e HTTPS_PROXY -v $(pwd):/src -w /src"; if (process.env.PSP_CPU_COUNT) { cmd += ` --cpus="${parseInt(process.env.PSP_CPU_COUNT)}.0"`; } - cmd += " perspective/puppeteer nice -n -20 node_modules/.bin/lerna exec --scope=@finos/perspective-bench -- yarn bench"; + cmd += + " perspective/puppeteer nice -n -20 node_modules/.bin/lerna exec --scope=@finos/perspective-bench -- yarn bench"; return cmd; } @@ -28,7 +30,9 @@ if (process.env.PSP_PROJECT === undefined || process.env.PSP_PROJECT === "js") { if (!process.env.PSP_DOCKER_PUPPETEER) { execute(docker()); } else { - execute(`nice -n -20 node_modules/.bin/lerna exec --scope=@finos/perspective-bench -- yarn bench`); + execute( + `nice -n -20 node_modules/.bin/lerna exec --scope=@finos/perspective-bench -- yarn bench` + ); } } catch (e) { process.exit(1); diff --git a/scripts/build.js b/scripts/build.js index a5a4acac41..911e887767 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -25,7 +25,9 @@ if (!fs.existsSync("./.perspectiverc")) { require("./build_python"); require("./build_cpp"); } else { - console.error(`Invalid project "${process.env.PSP_PROJECT}" selected, running setup`); + console.error( + `Invalid project "${process.env.PSP_PROJECT}" selected, running setup` + ); process.env.PSP_BUILD_IMMEDIATELY = 1; require("./setup"); } diff --git a/scripts/build_js.js b/scripts/build_js.js index 8d4a142c77..f3ac8c980b 100644 --- a/scripts/build_js.js +++ b/scripts/build_js.js @@ -10,7 +10,10 @@ const {execute} = require("./script_utils.js"); try { - let scope = process.env.PACKAGE && process.env.PACKAGE !== "" ? `${process.env.PACKAGE}` : "*"; + let scope = + process.env.PACKAGE && process.env.PACKAGE !== "" + ? `${process.env.PACKAGE}` + : "*"; execute`lerna exec --scope="@finos/${scope}" -- yarn build`; } catch (e) { diff --git a/scripts/build_python.js b/scripts/build_python.js index 4aa87b4f49..7e7a859a5d 100644 --- a/scripts/build_python.js +++ b/scripts/build_python.js @@ -7,12 +7,26 @@ * */ -const {execute, execute_throw, docker, resolve, getarg, bash, python_image} = require("./script_utils.js"); +const { + execute, + execute_throw, + docker, + resolve, + getarg, + bash, + python_image, +} = require("./script_utils.js"); const fs = require("fs-extra"); const IS_PY2 = getarg("--python2"); -let PYTHON = IS_PY2 ? "python2" : getarg("--python38") ? "python3.8" : getarg("--python36") ? "python3.6" : "python3.7"; +let PYTHON = IS_PY2 + ? "python2" + : getarg("--python38") + ? "python3.8" + : getarg("--python36") + ? "python3.6" + : "python3.7"; let IMAGE = "manylinux2010"; const IS_DOCKER = process.env.PSP_DOCKER; @@ -21,7 +35,12 @@ if (IS_DOCKER) { let MANYLINUX_VERSION = "manylinux2010"; if (!IS_PY2) { // switch to 2014 only on python3 - (MANYLINUX_VERSION = getarg("--manylinux2010") ? "manylinux2010" : getarg("--manylinux2014") ? "manylinux2014" : "manylinux2010"), PYTHON; + (MANYLINUX_VERSION = getarg("--manylinux2010") + ? "manylinux2010" + : getarg("--manylinux2014") + ? "manylinux2014" + : "manylinux2010"), + PYTHON; } IMAGE = python_image(MANYLINUX_VERSION, PYTHON); } @@ -47,7 +66,9 @@ try { const dlic = resolve`${dist}/LICENSE`; fs.mkdirpSync(dist); - fs.copySync(cmakelists, resolve`${dist}/CMakeLists.txt`, {preserveTimestamps: true}); + fs.copySync(cmakelists, resolve`${dist}/CMakeLists.txt`, { + preserveTimestamps: true, + }); fs.copySync(cpp, resolve`${dist}/src`, {preserveTimestamps: true}); fs.copySync(lic, dlic, {preserveTimestamps: true}); fs.copySync(cmake, dcmake, {preserveTimestamps: true}); diff --git a/scripts/clean.js b/scripts/clean.js index 2201404697..9e1f86b6de 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -43,15 +43,33 @@ try { } if (!process.env.PSP_PROJECT || args.indexOf("--deps") > -1) { - clean("cpp/perspective/dist", "cpp/perspective/build", "packages/perspective/build"); + clean( + "cpp/perspective/dist", + "cpp/perspective/build", + "packages/perspective/build" + ); } - let scope = process.env.PACKAGE && process.env.PACKAGE !== "" ? `${process.env.PACKAGE}` : "*"; + let scope = + process.env.PACKAGE && process.env.PACKAGE !== "" + ? `${process.env.PACKAGE}` + : "*"; if (!IS_SCREENSHOTS) { - if (!process.env.PACKAGE || minimatch("perspective", process.env.PACKAGE)) { - const files = ["CMakeFiles", "build", "cmake_install.cmake", "CMakeCache.txt", "compile_commands.json", "libpsp.a", "Makefile"]; - clean(...files.map(x => `cpp/perspective/obj/${x}`)); + if ( + !process.env.PACKAGE || + minimatch("perspective", process.env.PACKAGE) + ) { + const files = [ + "CMakeFiles", + "build", + "cmake_install.cmake", + "CMakeCache.txt", + "compile_commands.json", + "libpsp.a", + "Makefile", + ]; + clean(...files.map((x) => `cpp/perspective/obj/${x}`)); } execute`lerna exec --scope="@finos/${scope}" -- yarn clean`; diff --git a/scripts/jlab_link.js b/scripts/jlab_link.js index bc5bb477c6..5605700d87 100644 --- a/scripts/jlab_link.js +++ b/scripts/jlab_link.js @@ -9,7 +9,12 @@ const {execute} = require("./script_utils.js"); // Packages listed as dependencies & dev dependencies inside Jupyterlab plugin -const packages = ["./packages/perspective", "./rust/perspective-viewer", "./packages/perspective-viewer-datagrid", "./packages/perspective-viewer-d3fc"]; +const packages = [ + "./packages/perspective", + "./rust/perspective-viewer", + "./packages/perspective-viewer-datagrid", + "./packages/perspective-viewer-d3fc", +]; /** * In order for Jupyterlab to pick up changes to @finos/perspective-* in the @@ -28,10 +33,12 @@ const packages = ["./packages/perspective", "./rust/perspective-viewer", "./pack * `jupyter labextension install ./packages/perspective-jupyterlab` to link * to the local plugin. */ -(async function() { +(async function () { try { execute`jupyter labextension link ${packages.join(" ")}`; - console.log("Jupyterlab should now have all changes from the current working directory. To pick up new changes, run `yarn build` and `jupyter lab build`."); + console.log( + "Jupyterlab should now have all changes from the current working directory. To pick up new changes, run `yarn build` and `jupyter lab build`." + ); } catch (e) { console.error(e); process.exit(1); diff --git a/scripts/lint_python.js b/scripts/lint_python.js index 875004f1fd..3f6bd5c699 100644 --- a/scripts/lint_python.js +++ b/scripts/lint_python.js @@ -6,11 +6,24 @@ * the Apache License 2.0. The full license can be found in the LICENSE file. * */ -const {execute, docker, resolve, getarg, python_image} = require("./script_utils.js"); +const { + execute, + execute_throw, + docker, + resolve, + getarg, + python_image, +} = require("./script_utils.js"); const IS_DOCKER = process.env.PSP_DOCKER; const IS_PY2 = getarg("--python2"); -let PYTHON = IS_PY2 ? "python2" : getarg("--python38") ? "python3.8" : getarg("--python36") ? "python3.6" : "python3.7"; +let PYTHON = IS_PY2 + ? "python2" + : getarg("--python38") + ? "python3.8" + : getarg("--python36") + ? "python3.6" + : "python3"; let IMAGE = "manylinux2014"; if (IS_DOCKER) { @@ -18,7 +31,11 @@ if (IS_DOCKER) { let MANYLINUX_VERSION = "manylinux2010"; if (!IS_PY2) { // switch to 2014 only on python3 - MANYLINUX_VERSION = getarg("--manylinux2010") ? "manylinux2010" : getarg("--manylinux2014") ? "manylinux2014" : "manylinux2014"; + MANYLINUX_VERSION = getarg("--manylinux2010") + ? "manylinux2010" + : getarg("--manylinux2014") + ? "manylinux2014" + : "manylinux2014"; } IMAGE = python_image(MANYLINUX_VERSION, PYTHON); } diff --git a/scripts/repl.js b/scripts/repl.js index 3d171db15a..a97812ab9c 100644 --- a/scripts/repl.js +++ b/scripts/repl.js @@ -11,7 +11,13 @@ const {execute, docker, getarg, python_image} = require("./script_utils.js"); const IS_PY2 = getarg("--python2"); -let PYTHON = IS_PY2 ? "python2" : getarg("--python38") ? "python3.8" : getarg("--python36") ? "python3.6" : "python3.7"; +let PYTHON = IS_PY2 + ? "python2" + : getarg("--python38") + ? "python3.8" + : getarg("--python36") + ? "python3.6" + : "python3.7"; let IMAGE = "manylinux2010"; // defaults to 2010 @@ -19,7 +25,12 @@ let MANYLINUX_VERSION = "manylinux2010"; if (!IS_PY2) { // switch to 2014 only on python3 - (MANYLINUX_VERSION = getarg("--manylinux2010") ? "manylinux2010" : getarg("--manylinux2014") ? "manylinux2014" : "manylinux2010"), PYTHON; + (MANYLINUX_VERSION = getarg("--manylinux2010") + ? "manylinux2010" + : getarg("--manylinux2014") + ? "manylinux2014" + : "manylinux2010"), + PYTHON; } IMAGE = python_image(MANYLINUX_VERSION, PYTHON); @@ -29,4 +40,4 @@ try { } catch (e) { console.log(e.message); process.exit(1); -} \ No newline at end of file +} diff --git a/scripts/script_utils.js b/scripts/script_utils.js index fea2b7c53c..980694ad7e 100644 --- a/scripts/script_utils.js +++ b/scripts/script_utils.js @@ -48,20 +48,17 @@ function cut_last(f) { } function cut_first(f) { - return f - .split(" ") - .slice(1) - .join(" "); + return f.split(" ").slice(1).join(" "); } -const execute_throw = cmd => { +const execute_throw = (cmd) => { if (process.argv.indexOf("--debug") > -1) { console.log(`$ ${cmd}`); } execSync(cmd, {stdio: "inherit"}); }; -const execute = cmd => { +const execute = (cmd) => { try { execute_throw(cmd); } catch (e) { @@ -70,7 +67,7 @@ const execute = cmd => { } }; -const execute_return = async cmd => { +const execute_return = async (cmd) => { if (process.argv.indexOf("--debug") > -1) { console.log(`$ ${cmd}`); } @@ -182,7 +179,8 @@ const bash = (exports.bash = function bash(strings, ...args) { * @example * execute`run -t${1} -u"${undefined}" task`; */ -exports.execute = (strings, ...args) => execute(Array.isArray(strings) ? bash(strings, ...args) : strings); +exports.execute = (strings, ...args) => + execute(Array.isArray(strings) ? bash(strings, ...args) : strings); /** * Just like `execute`, except it throws anddoes not exit the child process @@ -193,7 +191,8 @@ exports.execute = (strings, ...args) => execute(Array.isArray(strings) ? bash(st * @example * execute`run -t${1} -u"${undefined}" task`; */ -exports.execute_throw = (strings, ...args) => execute_throw(Array.isArray(strings) ? bash(strings, ...args) : strings); +exports.execute_throw = (strings, ...args) => + execute_throw(Array.isArray(strings) ? bash(strings, ...args) : strings); /** * Just like `execute`, except it will return the output of the command @@ -205,7 +204,10 @@ exports.execute_throw = (strings, ...args) => execute_throw(Array.isArray(string * @example * execute`run -t${1} -u"${undefined}" task`; */ -exports.execute_return = async (strings, ...args) => await execute_return(Array.isArray(strings) ? bash(strings, ...args) : strings); +exports.execute_return = async (strings, ...args) => + await execute_return( + Array.isArray(strings) ? bash(strings, ...args) : strings + ); /** * Returns the value after this command-line flag, or `true` if it is the last @@ -221,7 +223,7 @@ exports.execute_return = async (strings, ...args) => await execute_return(Array. * @example * console.assert(getarg`--debug`); */ -const getarg = (exports.getarg = function(flag, ...args) { +const getarg = (exports.getarg = function (flag, ...args) { if (Array.isArray(flag)) { flag = flag.map((x, i) => x + (args[i] || "")).join(""); } @@ -238,7 +240,7 @@ const getarg = (exports.getarg = function(flag, ...args) { } } else { return argv - .map(function(arg) { + .map(function (arg) { return "'" + arg.replace(/'/g, "'\\''") + "'"; }) .join(" "); @@ -287,7 +289,9 @@ exports.docker = function docker(image = "puppeteer") { * @returns {string} The docker image to use */ exports.python_image = function python_image(image = "", python = "") { - console.log(`-- Getting image for image: '${image}' and python: '${python}'`); + console.log( + `-- Getting image for image: '${image}' and python: '${python}'` + ); if (python == "python2") { if (image == "manylinux2014") { throw "Python2 not supported for manylinux2014"; @@ -305,7 +309,10 @@ exports.python_image = function python_image(image = "", python = "") { function run_suite(tests) { for (const [actual, expected] of tests) { - console.assert(actual === expected, `"${actual}" received, expected: "${expected}"`); + console.assert( + actual === expected, + `"${actual}" received, expected: "${expected}"` + ); } } @@ -313,22 +320,94 @@ if (false) { if (isWin) { run_suite([ [resolve`a/b/c`, `${process.cwd()}\\a\\b\\c`], - [resolve`${__dirname}/../cpp/perspective`, `${process.cwd()}\\cpp\\perspective`], - [resolve`${__dirname}/../python/perspective/dist`, _path.resolve(__dirname, "..", "python", "perspective", "dist")], - [resolve`${__dirname}/../cpp/perspective`, _path.resolve(__dirname, "..", "cpp", "perspective")], - [resolve`${__dirname}/../cmake`, _path.resolve(__dirname, "..", "cmake")], - [resolve`${resolve`${__dirname}/../python/perspective/dist`}/cmake`, _path.resolve(_path.resolve(__dirname, "..", "python", "perspective", "dist"), "cmake")], - [resolve`${resolve`${__dirname}/../python/perspective/dist`}/obj`, _path.resolve(_path.resolve(__dirname, "..", "python", "perspective", "dist"), "obj")] + [ + resolve`${__dirname}/../cpp/perspective`, + `${process.cwd()}\\cpp\\perspective`, + ], + [ + resolve`${__dirname}/../python/perspective/dist`, + _path.resolve(__dirname, "..", "python", "perspective", "dist"), + ], + [ + resolve`${__dirname}/../cpp/perspective`, + _path.resolve(__dirname, "..", "cpp", "perspective"), + ], + [ + resolve`${__dirname}/../cmake`, + _path.resolve(__dirname, "..", "cmake"), + ], + [ + resolve`${resolve`${__dirname}/../python/perspective/dist`}/cmake`, + _path.resolve( + _path.resolve( + __dirname, + "..", + "python", + "perspective", + "dist" + ), + "cmake" + ), + ], + [ + resolve`${resolve`${__dirname}/../python/perspective/dist`}/obj`, + _path.resolve( + _path.resolve( + __dirname, + "..", + "python", + "perspective", + "dist" + ), + "obj" + ), + ], ]); } else { run_suite([ [resolve`a/b/c`, `${process.cwd()}/a/b/c`], - [resolve`${__dirname}/../cpp/perspective`, `${process.cwd()}/cpp/perspective`], - [resolve`${__dirname}/../python/perspective/dist`, _path.resolve(__dirname, "..", "python", "perspective", "dist")], - [resolve`${__dirname}/../cpp/perspective`, _path.resolve(__dirname, "..", "cpp", "perspective")], - [resolve`${__dirname}/../cmake`, _path.resolve(__dirname, "..", "cmake")], - [resolve`${resolve`${__dirname}/../python/perspective/dist`}/cmake`, _path.resolve(_path.resolve(__dirname, "..", "python", "perspective", "dist"), "cmake")], - [resolve`${resolve`${__dirname}/../python/perspective/dist`}/obj`, _path.resolve(_path.resolve(__dirname, "..", "python", "perspective", "dist"), "obj")] + [ + resolve`${__dirname}/../cpp/perspective`, + `${process.cwd()}/cpp/perspective`, + ], + [ + resolve`${__dirname}/../python/perspective/dist`, + _path.resolve(__dirname, "..", "python", "perspective", "dist"), + ], + [ + resolve`${__dirname}/../cpp/perspective`, + _path.resolve(__dirname, "..", "cpp", "perspective"), + ], + [ + resolve`${__dirname}/../cmake`, + _path.resolve(__dirname, "..", "cmake"), + ], + [ + resolve`${resolve`${__dirname}/../python/perspective/dist`}/cmake`, + _path.resolve( + _path.resolve( + __dirname, + "..", + "python", + "perspective", + "dist" + ), + "cmake" + ), + ], + [ + resolve`${resolve`${__dirname}/../python/perspective/dist`}/obj`, + _path.resolve( + _path.resolve( + __dirname, + "..", + "python", + "perspective", + "dist" + ), + "obj" + ), + ], ]); } @@ -356,6 +435,6 @@ if (false) { [bash`TEST=${undefined}`, ``], [bash`this is a test`, `this is a test`], [bash`this is a test `, `this is a test `], - [bash`--test="${undefined}.0" ${1}`, `1`] + [bash`--test="${undefined}.0" ${1}`, `1`], ]); } diff --git a/scripts/setup.js b/scripts/setup.js index b74fd56db9..2c8ea61f9d 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -8,7 +8,7 @@ */ const execSync = require("child_process").execSync; -const execute = cmd => execSync(cmd, {stdio: "inherit"}); +const execute = (cmd) => execSync(cmd, {stdio: "inherit"}); const inquirer = require("inquirer"); const fs = require("fs"); @@ -17,9 +17,14 @@ const CONFIG = new Proxy( new (class { constructor() { this.config = []; - this._values = require("dotenv").config({path: "./.perspectiverc"}).parsed || {}; + this._values = + require("dotenv").config({path: "./.perspectiverc"}).parsed || + {}; if (this._values.PACKAGE && this._values.PACKAGE.startsWith("@")) { - this._values.PACKAGE = this._values.PACKAGE.slice(2, this._values.PACKAGE.length - 1).replace(/\|/g, ","); + this._values.PACKAGE = this._values.PACKAGE.slice( + 2, + this._values.PACKAGE.length - 1 + ).replace(/\|/g, ","); } } @@ -41,16 +46,16 @@ const CONFIG = new Proxy( } })(), { - set: function(target, name, val) { + set: function (target, name, val) { target.add({[name]: val}); }, - get: function(target, name) { + get: function (target, name) { if (name in target._values) { return target._values[name]; } else { return target[name]; } - } + }, } ); @@ -58,14 +63,14 @@ const PROMPT_DEBUG = { type: "confirm", name: "PSP_DEBUG", message: "Run debug build?", - default: CONFIG["PSP_DEBUG"] || false + default: CONFIG["PSP_DEBUG"] || false, }; const PROMPT_DOCKER = { type: "confirm", name: "PSP_DOCKER", message: "Use docker for build env?", - default: CONFIG["PSP_DOCKER"] || false + default: CONFIG["PSP_DOCKER"] || false, }; async function choose_docker() { @@ -87,7 +92,7 @@ async function focus_package() { return [""]; } }, - filter: answer => { + filter: (answer) => { if (!answer || answer.length === 7) { return ""; } else { @@ -100,40 +105,40 @@ async function focus_package() { { key: "c", name: "perspective-cpp", - value: "perspective-cpp" + value: "perspective-cpp", }, { key: "p", name: "perspective", - value: "perspective" + value: "perspective", }, { key: "v", name: "perspective-viewer", - value: "perspective-viewer" + value: "perspective-viewer", }, { key: "e", name: "perspective-viewer-datagrid", - value: "perspective-viewer-datagrid" + value: "perspective-viewer-datagrid", }, { key: "d", name: "perspective-viewer-d3fc", - value: "perspective-viewer-d3fc" + value: "perspective-viewer-d3fc", }, { key: "l", name: "perspective-jupyterlab", - value: "perspective-jupyterlab" + value: "perspective-jupyterlab", }, { key: "w", name: "perspective-workspace", - value: "perspective-workspace" - } - ] - } + value: "perspective-workspace", + }, + ], + }, ]); if (Array.isArray(new_config.PACKAGE)) { if (new_config.PACKAGE.length > 0) { @@ -170,15 +175,15 @@ async function choose_project() { { key: "j", name: "Javascript", - value: "js" + value: "js", }, { key: "p", name: "python", - value: "python" - } - ] - } + value: "python", + }, + ], + }, ]); CONFIG.add(answers); CONFIG.write(); diff --git a/scripts/test.js b/scripts/test.js index de6627dc48..ef1e7a3b02 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -25,7 +25,9 @@ if (!fs.existsSync("./.perspectiverc")) { require("./test_python"); require("./test_cpp"); } else { - console.error(`Invalid project "${process.env.PSP_PROJECT}" selected, running setup`); + console.error( + `Invalid project "${process.env.PSP_PROJECT}" selected, running setup` + ); process.env.PSP_BUILD_IMMEDIATELY = 1; require("./setup"); } diff --git a/scripts/test_js.js b/scripts/test_js.js index 21488bea7f..e15cad9f4f 100644 --- a/scripts/test_js.js +++ b/scripts/test_js.js @@ -18,7 +18,8 @@ const IS_LOCAL_PUPPETEER = fs.existsSync("node_modules/puppeteer"); // Unfortunately we have to handle parts of the Jupyter test case here, // as the Jupyter server needs to be run outside of the main Jest process. -const IS_JUPYTER = getarg("--jupyter") && minimatch("perspective-jupyterlab", PACKAGE); +const IS_JUPYTER = + getarg("--jupyter") && minimatch("perspective-jupyterlab", PACKAGE); if (IS_WRITE) { console.log("-- Running the test suite in Write mode"); @@ -29,7 +30,9 @@ if (getarg("--saturate")) { } if (getarg("--debug")) { - console.log("-- Running tests in debug mode - all console.log statements are preserved."); + console.log( + "-- Running tests in debug mode - all console.log statements are preserved." + ); } function silent(x) { @@ -62,7 +65,8 @@ function jest_all() { */ function jest_single(cmd) { console.log(`-- Running "${PACKAGE}" test suite`); - const RUN_IN_BAND = getarg("--interactive") || IS_JUPYTER ? "--runInBand" : ""; + const RUN_IN_BAND = + getarg("--interactive") || IS_JUPYTER ? "--runInBand" : ""; return bash` PSP_SATURATE=${!!getarg("--saturate")} PSP_PAUSE_ON_FAILURE=${!!getarg("--interactive")} diff --git a/scripts/test_python.js b/scripts/test_python.js index d0ee8f6e68..6b84cebb3d 100644 --- a/scripts/test_python.js +++ b/scripts/test_python.js @@ -6,9 +6,21 @@ * the Apache License 2.0. The full license can be found in the LICENSE file. * */ -const {bash, execute, execute_throw, docker, resolve, getarg, python_image} = require("./script_utils.js"); +const { + bash, + execute, + execute_throw, + docker, + resolve, + getarg, + python_image, +} = require("./script_utils.js"); -let PYTHON = getarg("--python2") ? "python2" : getarg("--python38") ? "python3.8" : "python3.7"; +let PYTHON = getarg("--python2") + ? "python2" + : getarg("--python38") + ? "python3.8" + : "python3.7"; const COVERAGE = getarg("--coverage"); const VERBOSE = getarg("--debug"); @@ -18,7 +30,11 @@ let IMAGE = "manylinux2010"; if (IS_DOCKER) { // defaults to 2010 - let MANYLINUX_VERSION = getarg("--manylinux2010") ? "manylinux2010" : getarg("--manylinux2014") ? "manylinux2014" : ""; + let MANYLINUX_VERSION = getarg("--manylinux2010") + ? "manylinux2010" + : getarg("--manylinux2014") + ? "manylinux2014" + : ""; IMAGE = python_image(MANYLINUX_VERSION, PYTHON); } @@ -37,7 +53,7 @@ if (IS_DOCKER) { * Run `pytest` for client mode PerspectiveWidget, which need to be on a * separate runtime from the other Python tests. */ -const pytest_client_mode = IS_DOCKER => { +const pytest_client_mode = (IS_DOCKER) => { if (IS_DOCKER) { return bash`${docker(IMAGE)} bash -c "cd \ python/perspective && TZ=UTC ${PYTHON} -m pytest \ @@ -53,7 +69,7 @@ const pytest_client_mode = IS_DOCKER => { /** * Run `pytest` for the `perspective-python` library. */ -const pytest = IS_DOCKER => { +const pytest = (IS_DOCKER) => { if (IS_DOCKER) { return bash`${docker(IMAGE)} bash -c "cd \ python/perspective && TZ=UTC ${PYTHON} -m pytest \ diff --git a/yarn.lock b/yarn.lock index 9093ffee77..cea2bdfc32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents" chokidar "^3.4.0" +"@babel/code-frame@7.12.11", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -26,13 +33,6 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" @@ -1181,6 +1181,21 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -1260,6 +1275,20 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.2.tgz#218cd7276ab4f9ab57cc3d2efa2697e6a579f25d" integrity sha512-7l/AX41m609L/EXI9EKH3Vs3v0iA8tKlIOGtw+kgcoanI7p+e4I4GYLqW3UXWiTnjSFymKSmTTPKYrivzbxxqA== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2664,11 +2693,32 @@ readdirp "^2.2.1" upath "^1.1.1" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@npmcli/ci-detect@^1.0.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.3.0.tgz#6c1d2c625fb6ef1b9dea85ad0a5afcbef85ef22a" @@ -3006,11 +3056,6 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/eslint@*": version "7.2.6" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" @@ -3114,16 +3159,28 @@ dependencies: "@types/sizzle" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/json-schema@^7.0.7": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + "@types/lodash@^4.14.134": version "4.14.167" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.167.tgz#ce7d78553e3c886d4ea643c37ec7edc20f16765e" integrity sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw== +"@types/mdast@^3.0.0": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" + integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== + dependencies: + "@types/unist" "*" + "@types/mime@*": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" @@ -3254,6 +3311,11 @@ resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.24.tgz#dede004deed3b3f99c4db0bdb9ee21cae25befdd" integrity sha512-T3NQD8hXNW2sRsSbLNjF/aBo18MyJlbw0lSpQHB/eZZtScPdexN4HSa8cByYwTw9Wy7KuOFr81mlDQcQQaZ79w== +"@types/unist@*", "@types/unist@^2.0.2": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" @@ -3308,48 +3370,74 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^2.4.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== +"@typescript-eslint/eslint-plugin@^4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz#9c3fa6f44bad789a962426ad951b54695bd3af6b" + integrity sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw== dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/experimental-utils" "4.31.0" + "@typescript-eslint/scope-manager" "4.31.0" + debug "^4.3.1" functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^2.4.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" - integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.34.0" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-visitor-keys "^1.1.0" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz#0ef1d5d86c334f983a00f310e43c1ce4c14e054d" + integrity sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.0.tgz#87b7cd16b24b9170c77595d8b1363f8047121e05" + integrity sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w== + dependencies: + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz#9be33aed4e9901db753803ba233b70d79a87fc3e" + integrity sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + +"@typescript-eslint/types@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.0.tgz#9a7c86fcc1620189567dc4e46cad7efa07ee8dce" + integrity sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ== + +"@typescript-eslint/typescript-estree@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz#4da4cb6274a7ef3b21d53f9e7147cc76f278a078" + integrity sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/visitor-keys@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz#4e87b7761cb4e0e627dc2047021aa693fc76ea2b" + integrity sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w== dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/types" "4.31.0" + eslint-visitor-keys "^2.0.0" "@webassemblyjs/ast@1.11.0": version "1.11.0" @@ -3549,10 +3637,10 @@ acorn-globals@^4.3.2: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^6.0.1: version "6.2.0" @@ -3564,7 +3652,7 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.1.0, acorn@^7.1.1: +acorn@^7.1.0, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -3654,6 +3742,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alphanum-sort@^1.0.0, alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -3907,6 +4005,11 @@ array-union@^1.0.1, array-union@^1.0.2: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -3966,6 +4069,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.0, async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -4218,11 +4326,6 @@ backbone@1.2.3: dependencies: underscore ">=1.7.0" -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== - balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -4894,6 +4997,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -5168,11 +5279,6 @@ coffee-script@^1.12.4: resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - collect-all@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/collect-all/-/collect-all-1.0.4.tgz#50cd7119ac24b8e12a661f0f8c3aa0ea7222ddfc" @@ -5719,7 +5825,7 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -6476,6 +6582,13 @@ debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^4.0.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -6754,6 +6867,13 @@ dir-glob@^2.2.2: dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dmd@^3.0.10: version "3.0.13" resolved "https://registry.yarnpkg.com/dmd/-/dmd-3.0.13.tgz#73294e8fae1a7a1a1c849d86b027adf04fbd5662" @@ -7139,7 +7259,7 @@ enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.6: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -7312,6 +7432,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@^1.11.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -7324,31 +7449,26 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@^3.0.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.6.0.tgz#8ca3ffac4bd6eeef623a0651f9d754900e3ec217" - integrity sha512-ixJ4U3uTLXwJts4rmSVW/lMXjlGwCijhBJHk8iVqKKSifeI0qgFEfWl8L63isfc8Od7EiBALF6BX3jKLluf/jQ== - dependencies: - get-stdin "^6.0.0" +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== -eslint-plugin-markdown@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-markdown/-/eslint-plugin-markdown-1.0.2.tgz#79274bf17ce3ead48e4a55cbcb6d7ce735754280" - integrity sha512-BfvXKsO0K+zvdarNc801jsE/NTLmig4oKhZ1U3aSUgTf2dB/US5+CrfGxMsCK2Ki1vS1R3HPok+uYpufFndhzw== +eslint-plugin-markdown@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.0.tgz#9c30bd51538a815e87e96646c69f11466b4c165f" + integrity sha512-Ctuc7aP1tU92qnFwVO1wDLEzf1jqMxwRkcSTw7gjbvnEqfh5CKUcTXM0sxg8CB2KDXrqpTuMZPgJ1XE9Olr7KA== dependencies: - object-assign "^4.0.1" - remark-parse "^5.0.0" - unified "^6.1.2" + mdast-util-from-markdown "^0.8.5" -eslint-plugin-prettier@^2.6.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz#b4312dcf2c1d965379d7f9d5b5f8aaadc6a45904" - integrity sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA== +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== dependencies: - fast-diff "^1.1.1" - jest-docblock "^21.0.0" + prettier-linter-helpers "^1.0.0" -eslint-scope@^5.0.0, eslint-scope@^5.1.1: +eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -7356,76 +7476,84 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^2.0.0: +eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^6.6.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" + chalk "^4.0.0" + cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" + levn "^0.4.1" + lodash.merge "^4.6.2" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.3" + optionator "^0.9.1" progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" esprima@^2.6.0: version "2.7.3" @@ -7442,10 +7570,10 @@ esprima@~3.1.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= -esquery@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -7712,7 +7840,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -7773,12 +7901,12 @@ fast-deep-equal@^1.0.0: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.1: +fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== @@ -7795,12 +7923,23 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -7815,6 +7954,13 @@ fastparse@^1.1.1, fastparse@^1.1.2: resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fastq@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.11.3, faye-websocket@~0.11.1: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" @@ -7877,12 +8023,12 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" file-loader@^2.0.0: version "2.0.0" @@ -8088,24 +8234,23 @@ find-versions@^3.0.0: dependencies: semver-regex "^2.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" flatbuffers@^1.10.2: version "1.12.0" resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.12.0.tgz#72e87d1726cb1b216e839ef02658aa87dcef68aa" integrity sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ== -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== flatten@^1.0.2: version "1.0.3" @@ -8368,11 +8513,6 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" - integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== - get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -8513,6 +8653,13 @@ glob-parent@^5.0.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -8575,12 +8722,12 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0, globals@^13.9.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" + integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" globals@^9.18.0: version "9.18.0" @@ -8600,6 +8747,18 @@ globby@8.0.2, globby@^8.0.1: pify "^3.0.0" slash "^1.0.0" +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -9225,6 +9384,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -9387,7 +9551,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -9594,7 +9758,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.4, is-buffer@^1.1.5: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -9985,21 +10149,11 @@ is-what@^3.7.1: resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.12.0.tgz#f4405ce4bd6dd420d3ced51a026fb90e03705e55" integrity sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw== -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - is-windows@^1.0.0, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -10182,11 +10336,6 @@ jest-diff@^25.5.0: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-docblock@^21.0.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" - integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== - jest-docblock@^25.3.0: version "25.3.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef" @@ -10731,6 +10880,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -10950,7 +11104,15 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.3.0, levn@~0.3.0: +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= @@ -11162,7 +11324,7 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.4.0: +lodash.merge@^4.4.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -11227,6 +11389,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -11460,11 +11627,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - markdown-link@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/markdown-link/-/markdown-link-0.1.1.tgz#32c5c65199a6457316322d1e4229d13407c8c7cf" @@ -11513,6 +11675,22 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== +mdast-util-from-markdown@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c" + integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-string "^2.0.0" + micromark "~2.11.0" + parse-entities "^2.0.0" + unist-util-stringify-position "^2.0.0" + +mdast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" + integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -11607,7 +11785,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3: +merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -11622,6 +11800,14 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== +micromark@~2.11.0: + version "2.11.4" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" + integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + micromatch@4.x, micromatch@^4.0.0, micromatch@^4.0.1, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -11668,6 +11854,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + mime-db@1.45.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: version "1.45.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" @@ -12608,7 +12802,7 @@ optimist@0.6.x: minimist "~0.0.1" wordwrap "~0.0.2" -optionator@^0.8.1, optionator@^0.8.3: +optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -12620,6 +12814,18 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + optipng-bin@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/optipng-bin/-/optipng-bin-5.1.0.tgz#a7c7ab600a3ab5a177dae2f94c2d800aa386b5a9" @@ -12917,10 +13123,10 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-entities@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" - integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg== +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== dependencies: character-entities "^1.0.0" character-entities-legacy "^1.0.0" @@ -13127,6 +13333,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pidtree@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" @@ -13919,6 +14130,11 @@ pre-commit@^1.2.2: spawn-sync "^1.0.15" which "1.2.x" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -13939,10 +14155,17 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.0.tgz#85bdfe0f70c3e777cf13a4ffff39713ca6f64cba" + integrity sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ== pretty-error@^2.1.1: version "2.1.2" @@ -14213,6 +14436,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -14650,15 +14878,10 @@ regexp.prototype.flags@^1.2.0: call-bind "^1.0.2" define-properties "^1.1.3" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpp@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^4.7.1: version "4.7.1" @@ -14694,27 +14917,6 @@ relateurl@0.2.x, relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA== - dependencies: - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^1.1.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" - xtend "^4.0.1" - remarkable@^1.7.1: version "1.7.4" resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" @@ -14752,7 +14954,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -14764,11 +14966,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= - replace-ext@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" @@ -14821,7 +15018,7 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.1: +require-from-string@^2.0.1, require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== @@ -14938,6 +15135,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -14948,13 +15150,6 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -15041,6 +15236,13 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -15212,7 +15414,7 @@ semver-truncate@^1.1.2: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.x, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@6.x, semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -15229,6 +15431,13 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.3.2: dependencies: lru-cache "^6.0.0" +semver@^7.2.1, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -15424,14 +15633,14 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" slide@^1.1.6: version "1.1.6" @@ -15767,11 +15976,6 @@ stack-utils@^1.0.1: dependencies: escape-string-regexp "^2.0.0" -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -16016,7 +16220,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.0.1: +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -16165,15 +16369,17 @@ table-layout@^0.4.2: typical "^2.6.1" wordwrapjs "^3.0.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" taffydb@2.6.2: version "2.6.2" @@ -16597,21 +16803,6 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= - -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== - truncate-html@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/truncate-html/-/truncate-html-1.0.3.tgz#0166dfc7890626130c2e4174c6b73d4d63993e5f" @@ -16667,10 +16858,10 @@ tslib@~1.13.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== -tsutils@^3.17.1: - version "3.19.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.19.1.tgz#d8566e0c51c82f32f9c25a4d367cd62409a547a9" - integrity sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" @@ -16686,6 +16877,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -16708,6 +16906,11 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -16859,14 +17062,6 @@ underscore@~1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -16890,18 +17085,6 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== -unified@^6.1.2: - version "6.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" - integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^1.1.0" - trough "^1.0.0" - vfile "^2.0.0" - x-is-string "^0.1.0" - union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -16943,36 +17126,12 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unist-util-is@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" - integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== - -unist-util-remove-position@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020" - integrity sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A== - dependencies: - unist-util-visit "^1.1.0" - -unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" - integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ== - -unist-util-visit-parents@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" - integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== - dependencies: - unist-util-is "^3.0.0" - -unist-util-visit@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" - integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== dependencies: - unist-util-visit-parents "^2.0.0" + "@types/unist" "^2.0.2" universal-user-agent@^4.0.0: version "4.0.1" @@ -17169,28 +17328,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vfile-location@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" - integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== - -vfile-message@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" - integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA== - dependencies: - unist-util-stringify-position "^1.1.1" - -vfile@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a" - integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w== - dependencies: - is-buffer "^1.1.4" - replace-ext "1.0.0" - unist-util-stringify-position "^1.0.0" - vfile-message "^1.0.0" - vscode-textmate@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" @@ -17505,7 +17642,7 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -17612,13 +17749,6 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@7.4.6: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" @@ -17643,11 +17773,6 @@ ws@^7.0.0, ws@^7.2.0: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== -x-is-string@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" - integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= - xml-formatter@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/xml-formatter/-/xml-formatter-2.4.0.tgz#c956ea6c5345240c0829da86a5e81d44ed4cb9c7" @@ -17702,7 +17827,7 @@ xmldom@0.1.x: resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==