From 87a19bec3de8a440fe562accfae6953681c4c72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Dani=C5=82o?= Date: Fri, 5 Mar 2021 16:22:19 +0100 Subject: [PATCH] CI Fixes (https://github.com/enso-org/ide/pull/1304) Original commit: https://github.com/enso-org/ide/commit/0ff9e3f62bc9b657e5c73e360f48b314354be5a4 --- gui/.github/workflows/gui-ci.yml | 50 +- gui/.prettierrc.toml | 4 - gui/.prettierrc.yaml | 15 + gui/CHANGELOG.md | 48 +- gui/README.md | 25 +- gui/SECURITY.md | 7 +- gui/build/workflow.js | 56 +- gui/src/rust/ensogl/README.md | 609 +++++++++--------- gui/src/rust/ensogl/doc/coordinates.md | 23 +- .../java_script/bubbleVisualization.js | 8 +- .../visualization/java_script/geoMap.js | 47 +- .../visualization/java_script/histogram.js | 99 +-- .../visualization/java_script/scatterPlot.js | 185 ++---- .../visualization/java_script/table.js | 52 +- 14 files changed, 536 insertions(+), 692 deletions(-) delete mode 100644 gui/.prettierrc.toml create mode 100644 gui/.prettierrc.yaml diff --git a/gui/.github/workflows/gui-ci.yml b/gui/.github/workflows/gui-ci.yml index e64e098d93..27f1a682a1 100644 --- a/gui/.github/workflows/gui-ci.yml +++ b/gui/.github/workflows/gui-ci.yml @@ -60,6 +60,7 @@ jobs: run: >- if [[ ${{ steps.checkCurrentReleaseTag.outputs.exists }} == true ]]; then exit 1; fi + if: github.base_ref == 'unstable' || github.base_ref == 'stable' - name: Get list of changed files id: changed_files run: |2- @@ -107,6 +108,8 @@ jobs: run: npm install --save-dev --save-exact prettier - name: Install Clippy run: rustup component add clippy + - name: Lint Markdown sources + run: npx prettier --check '*.md' - name: Lint JavaScript sources run: npx prettier --check 'src/**/*.js' - name: Lint Rust sources @@ -161,7 +164,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'macOS-latest' + if: startsWith(matrix.os,'macOS') - name: Install wasm-pack (Windows) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -172,7 +175,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Install wasm-pack (Linux) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -183,7 +186,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') - name: Run tests (WASM) run: node ./run test --no-native --skip-version-validation simple_build: @@ -215,7 +218,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'macOS-latest' + if: startsWith(matrix.os,'macOS') - name: Install wasm-pack (Windows) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -226,7 +229,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Install wasm-pack (Linux) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -237,10 +240,10 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') - name: Build (macos) run: node ./run dist --skip-version-validation --target macos - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os,'macos') if: >- !(contains(github.event.head_commit.message,'[ci build]') || github.base_ref == 'develop' || github.base_ref == 'unstable' || @@ -286,7 +289,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'macOS-latest' + if: startsWith(matrix.os,'macOS') - name: Install wasm-pack (Windows) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -297,7 +300,7 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Install wasm-pack (Linux) env: WASMPACKURL: https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1 @@ -308,29 +311,29 @@ jobs: mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR shell: bash - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') - name: Build (macos) run: node ./run dist --skip-version-validation --target macos - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os,'macos') - name: Build (win) run: node ./run dist --skip-version-validation --target win - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Build (linux) run: node ./run dist --skip-version-validation --target linux - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') - name: Upload Content Artifacts uses: actions/upload-artifact@v1 with: name: content path: dist/content - if: matrix.os == 'macOS-latest' + if: startsWith(matrix.os,'macOS') - name: Upload Artifacts (macOS, dmg) uses: actions/upload-artifact@v1 with: name: enso-mac-${{fromJson(steps.changelog.outputs.content).version}}.dmg path: >- dist/client/enso-mac-${{fromJson(steps.changelog.outputs.content).version}}.dmg - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os,'macos') - name: Upload Artifacts (macOS, dmg.sha256) uses: actions/upload-artifact@v1 with: @@ -338,14 +341,14 @@ jobs: enso-mac-${{fromJson(steps.changelog.outputs.content).version}}.dmg.sha256 path: >- dist/client/enso-mac-${{fromJson(steps.changelog.outputs.content).version}}.dmg.sha256 - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os,'macos') - name: Upload Artifacts (Windows, exe) uses: actions/upload-artifact@v1 with: name: enso-win-${{fromJson(steps.changelog.outputs.content).version}}.exe path: >- dist/client/enso-win-${{fromJson(steps.changelog.outputs.content).version}}.exe - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Upload Artifacts (Windows, exe.sha256) uses: actions/upload-artifact@v1 with: @@ -353,7 +356,7 @@ jobs: enso-win-${{fromJson(steps.changelog.outputs.content).version}}.exe.sha256 path: >- dist/client/enso-win-${{fromJson(steps.changelog.outputs.content).version}}.exe.sha256 - if: matrix.os == 'windows-latest' + if: startsWith(matrix.os,'windows') - name: Upload Artifacts (Linux, AppImage) uses: actions/upload-artifact@v1 with: @@ -361,7 +364,7 @@ jobs: enso-linux-${{fromJson(steps.changelog.outputs.content).version}}.AppImage path: >- dist/client/enso-linux-${{fromJson(steps.changelog.outputs.content).version}}.AppImage - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') - name: Upload Artifacts (Linux, AppImage.sha256) uses: actions/upload-artifact@v1 with: @@ -369,7 +372,7 @@ jobs: enso-linux-${{fromJson(steps.changelog.outputs.content).version}}.AppImage.sha256 path: >- dist/client/enso-linux-${{fromJson(steps.changelog.outputs.content).version}}.AppImage.sha256 - if: matrix.os == 'ubuntu-latest' + if: startsWith(matrix.os,'ubuntu') if: >- contains(github.event.head_commit.message,'[ci build]') || github.base_ref == 'develop' || github.base_ref == 'unstable' || github.base_ref == @@ -408,6 +411,11 @@ jobs: run: >- if [[ ${{ steps.checkCurrentReleaseTag.outputs.exists }} == true ]]; then exit 1; fi + if: github.base_ref == 'unstable' || github.base_ref == 'stable' + - name: Install Prettier + run: npm install --save-dev --save-exact prettier + - name: Pretty print changelog. + run: npx prettier --prose-wrap never CHANGELOG.md --write - name: Upload GitHub Release uses: softprops/action-gh-release@v1 env: @@ -431,7 +439,7 @@ jobs: strategy: matrix: os: - - ubuntu-latest + - ubuntu-18.04 fail-fast: false steps: - uses: actions/checkout@v1 diff --git a/gui/.prettierrc.toml b/gui/.prettierrc.toml deleted file mode 100644 index 327d72be66..0000000000 --- a/gui/.prettierrc.toml +++ /dev/null @@ -1,4 +0,0 @@ -trailingComma = "es5" -tabWidth = 4 -semi = false -singleQuote = true diff --git a/gui/.prettierrc.yaml b/gui/.prettierrc.yaml new file mode 100644 index 0000000000..c0cf095109 --- /dev/null +++ b/gui/.prettierrc.yaml @@ -0,0 +1,15 @@ +overrides: + + - files: "*.js" + options: + printWidth: 100 + tabWidth: 4 + semi: false + singleQuote: true + trailingComma: "es5" + arrowParens: "avoid" + + - files: "*.md" + options: + printWidth: 80 + proseWrap: "always" diff --git a/gui/CHANGELOG.md b/gui/CHANGELOG.md index 40a8a4ff9d..40d7d37534 100644 --- a/gui/CHANGELOG.md +++ b/gui/CHANGELOG.md @@ -2,18 +2,19 @@ This is a release focused on bug-fixing, stability, and performance. It improves the performance of workflows and visualizations, and improves the look and feel -of the graphical interface. In addition, the graphical interface informs the -users now about errors and where they originate. +of the graphical interface. In addition, the graphical interface now informs the +users about errors and where they originate.
![New Learning Resources](/docs/assets/tags/new_learning_resources.svg) - [Learn how to define custom data visualizations in Enso][podcast-custom-visualizations]. -- [Learn how to use Java libraries in Enso][podcast-java-interop]. -- [Learn how to use HTTP libraries in Enso to build custom server-side website - rendering][podcast-http-server]. -- [Discover why Enso Compiler is so fast and how it was build to support a - dual-representation langauge][podcast-compiler-internals]. +- [Learn how to use Java libraries in Enso, to build a + webserver][podcast-java-interop]. +- [Learn how to use Javascript libraries in Enso, to build custom server-side + website rendering][podcast-http-server]. +- [Discover why Enso Compiler is so fast and how it was built to support a + dual-representation language][podcast-compiler-internals]. - [Learn more about the vision behind Enso and about its planned future][podcast-future-of-enso]. @@ -24,23 +25,22 @@ users now about errors and where they originate. - [Errors in workflows are now displayed in the graphical interface][1215]. Previously, these errors were silently skipped, which was non-intuitive and hard to understand. Now, the IDE displays both dataflow errors and panics in a - nice and descriptive way. + nice and descriptive fashion. - [Added geographic map support for Tables (data frames).][1187] Tables that - have `latitude`, `longitude` and optionally `label` columns can now be shown + have `latitude`, `longitude`, and optionally `label` columns can now be shown as points on a map. -- [Added a shortcut for live reloading of visualization files.][1190] It - drastically improves how fast new visualizations can be tested during their +- [Added a shortcut for live reloading of visualization files.][1190] This + drastically improves how quickly new visualizations can be tested during their development. This is _currently_ limited in that, after reloading visualization definitions, the currently visible visualizations must be - switched to another and switched back to refresh its content. See the [video - podcast about building custom visualizations][podcast-custom-visualizations] + switched to another and switched back to refresh their content. See the [video + podcast about building custom visualizations][podcast-custom-visualizations] to learn more. - [Added a visual indicator of the ongoing standard library compilation][1264]. - Currently, each time after IDE is started, backend needs to compile the - standard library, before it can provide IDE with type information and values. - Because of that, not all functionalities are ready to work directly after - starting the IDE. Now, there is a visible indication of the ongoing background - process. + Currently, each time IDE is started, the backend needs to compile the standard + library before it can provide IDE with type information and values. Because of + that, not all functionalities are ready to work directly after starting the + IDE. Now, there is a visible indication of the ongoing background process. - [Added the ability to reposition visualisations.][1096] There is now an icon in the visualization action bar that allows dragging the visualization away from a node. Once the visualization has been moved, another icon appears that @@ -56,7 +56,7 @@ users now about errors and where they originate. note, that large tables will get truncated to 2000 entries. This limitation will be lifted in future releases. - [Performance improvements during visual workflow][1067]. Nodes added with the - searcher will have their values automatically assigned to a newly generated + searcher will have their values automatically assigned to newly generated variables, which allows the Enso Engine to cache intermediate values and hence improve visualization performance. - [Minor documentation rendering fixes][1098]. Fixed cases where text would be @@ -66,7 +66,7 @@ users now about errors and where they originate. is now better at dealing with incompatible metadata in files, which stores node visual position information, the history of chosen searcher suggestions, etc. This will allow IDE to correctly open projects that were created using a - different IDE version and prevent unnecessary lose of metadata. + different IDE version and prevent unnecessary loss of metadata. - Pressing and holding up and down arrow keys make the list view selection move continuously. - The shortcuts to close the application and to toggle the developer tools at @@ -78,14 +78,14 @@ users now about errors and where they originate. - [Fixed visual glitch where a node's text was displayed as white on a white background][1264]. Most notably this occurred with the output node of a function generated using the node collapse refactoring. -- Many visual glitches vere fixed, including small "pixel-like" artifacts +- Many visual glitches were fixed, including small "pixel-like" artifacts appearing on the screen. - [Several parser improvements][1274]. The parser used in the IDE has been updated to the latest version. This resolves several issues with language - constructs like `import`, lambdas, and parentheses, where upon typing certain + constructs like `import`, lambdas, and parentheses, whereupon typing certain text the edit could be automatically reverted. - [The auto-import functionality was improved][1279]. Libraries' `Main` modules - are omitted in expressions inserted by searcher. For example, the `point` + are omitted in expressions inserted by the searcher. For example, the `point` method of `Geo` library will be displayed as `Geo.point` and will insert import `Geo` instead of `Geo.Main`. - Cursors in text editors behave correctly now (they are not affected by scene @@ -94,7 +94,7 @@ users now about errors and where they originate. #### EnsoGL (rendering engine) -- A new multi-camera management system, allowing the same shape systems be +- A new multi-camera management system, allowing the same shape systems to be rendered on different layers from different cameras. The implementation automatically caches the same shape system definitions per scene layer in order to minimize the amount of WebGL draw calls and hence improve diff --git a/gui/README.md b/gui/README.md index bbe32659f1..52faad399c 100644 --- a/gui/README.md +++ b/gui/README.md @@ -21,7 +21,6 @@

- ### Overview

@@ -52,11 +51,13 @@ University and NASA as one of 20 most unique technologies worldwide. Enso consists of several sub-projects, including the [Enso Language Compiler](https://github.com/enso-org/enso), the [Enso Integrated Development Environment (IDE)](https://github.com/enso-org/ide), -and [a high performance WebGL UI framework (EnsoGL)](https://github.com/enso-org/ide/tree/main/src/rust/ensogl). +and +[a high performance WebGL UI framework (EnsoGL)](https://github.com/enso-org/ide/tree/main/src/rust/ensogl).
### Getting Started + Enso is distributed both in form of [pre-build packages for MacOS, Windows, or Linux](https://github.com/enso-org/ide/releases), as well as the [source code](https://github.com/enso-org). See the @@ -64,11 +65,13 @@ as well as the [source code](https://github.com/enso-org). See the more. Currently to start IDE you have to run **Enso Project Manager** first. For more -information and packages see [Enso repository](https://github.com/enso-org/enso). +information and packages see +[Enso repository](https://github.com/enso-org/enso).
### Building + The project builds on MacOS, Linux, and Windows. Simply run `node ./run build` to build it and use `node ./run --help` to learn about other available commands and options. Read the detailed [development guide](docs/CONTRIBUTING.md) to @@ -77,18 +80,20 @@ learn more.
### License + The Enso Language Compiler is released under the terms of the -[Apache v2 License](https://github.com/enso-org/enso/blob/main/LICENSE). The Enso -Graphical Interface and it's rendering engine are released under the terms of -the [AGPL v3 License](https://github.com/enso-org/ide/blob/main/LICENSE). This -license set was choosen to both provide you with a complete freedom to use Enso, -create libraries, and release them under any license of your choice, while also -allowing us to release commercial products on top of the platform, including -Enso Cloud and Enso Enterprise on-premise server managers. +[Apache v2 License](https://github.com/enso-org/enso/blob/main/LICENSE). The +Enso Graphical Interface and it's rendering engine are released under the terms +of the [AGPL v3 License](https://github.com/enso-org/ide/blob/main/LICENSE). +This license set was choosen to both provide you with a complete freedom to use +Enso, create libraries, and release them under any license of your choice, while +also allowing us to release commercial products on top of the platform, +including Enso Cloud and Enso Enterprise on-premise server managers.
### Contributing + Enso is a community-driven open source project which is and will always be open and free to use. We are committed to a fully transparent development process and highly appreciate every contribution. If you love the vision behind Enso and you diff --git a/gui/SECURITY.md b/gui/SECURITY.md index 0833893c6b..6c4fb2281f 100644 --- a/gui/SECURITY.md +++ b/gui/SECURITY.md @@ -7,10 +7,11 @@ order: 4 --- # Security Policy + This document outlines the security policy for Enso and its libraries. > **If you believe that you have found a vulnerability in Enso or one of its -> libraries, please see the section on +> libraries, please see the section on > [reporting a vulnerability](#reporting-a-vulnerability) below.** @@ -21,16 +22,18 @@ This document outlines the security policy for Enso and its libraries. ## Supported Versions + Security updates for Enso are provided for the versions shown below with a :white_check_mark: next to them. No other versions have security updates provided. | Version | Supported | -|-------------|--------------------| +| ----------- | ------------------ | | `main@HEAD` | :white_check_mark: | | `wip/*` | :x: | ## Reporting a Vulnerability + If you believe that you've found a security vulnerability in the Enso codebase or one of the libraries maintained in this repository, please contact [security@enso.org](mailto:security@enso.org) and provide details of the bug. diff --git a/gui/build/workflow.js b/gui/build/workflow.js index 6e0a5e0237..e2287cdaa5 100644 --- a/gui/build/workflow.js +++ b/gui/build/workflow.js @@ -52,10 +52,14 @@ function job_on_macos(...args) { return job(["macOS-latest"],...args) } -function job_on_linux(...args) { +function job_on_ubuntu(...args) { return job(["ubuntu-latest"],...args) } +function job_on_ubuntu_18_04(...args) { + return job(["ubuntu-18.04"],...args) +} + function list(...args) { let out = [] for (let arg of args) { @@ -128,7 +132,7 @@ function installWasmPackOn(name,sys,pkg) { mv $WASMPACKDIR/wasm-pack ~/.cargo/bin rm -r $WASMPACKDIR`, shell: "bash", - if: `matrix.os == '${sys}-latest'`, + if: `startsWith(matrix.os,'${sys}')`, } } @@ -149,7 +153,7 @@ function buildOn(name,sys) { return { name: `Build (${name})`, run: `node ./run dist --skip-version-validation --target ${name}`, - if: `matrix.os == '${sys}-latest'` + if: `startsWith(matrix.os,'${sys}')` } } @@ -157,6 +161,11 @@ buildOnMacOS = buildOn('macos','macos') buildOnWindows = buildOn('win','windows') buildOnLinux = buildOn('linux','ubuntu') +let lintMarkdown = { + name: "Lint Markdown sources", + run: "npx prettier --check '*.md'", +} + let lintJavaScript = { name: "Lint JavaScript sources", run: "npx prettier --check 'src/**/*.js'", @@ -190,7 +199,7 @@ let uploadContentArtifacts = { name: 'content', path: `dist/content` }, - if: `matrix.os == 'macOS-latest'` + if: `startsWith(matrix.os,'macOS')` } function uploadBinArtifactsFor(name,sys,ext,os) { @@ -201,7 +210,7 @@ function uploadBinArtifactsFor(name,sys,ext,os) { name: `enso-${os}-\${{fromJson(steps.changelog.outputs.content).version}}.${ext}`, path: `dist/client/enso-${os}-\${{fromJson(steps.changelog.outputs.content).version}}.${ext}` }, - if: `matrix.os == '${sys}-latest'` + if: `startsWith(matrix.os,'${sys}')` } } @@ -275,20 +284,27 @@ let assertChangelogWasUpdated = [ // === GitHub Release === // ====================== -let uploadGitHubRelease = { - name: `Upload GitHub Release`, - uses: "softprops/action-gh-release@v1", - env: { - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" +let uploadGitHubRelease = [ + installPrettier, + { + name: `Pretty print changelog.`, + run: "npx prettier --prose-wrap never CHANGELOG.md --write" }, - with: { - files: "artifacts/**/enso-*", - name: "Enso ${{fromJson(steps.changelog.outputs.content).version}}", - tag_name: "v${{fromJson(steps.changelog.outputs.content).version}}", - body: "${{fromJson(steps.changelog.outputs.content).body}}", - prerelease: "${{fromJson(steps.changelog.outputs.content).prerelease}}", - }, -} + { + name: `Upload GitHub Release`, + uses: "softprops/action-gh-release@v1", + env: { + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + }, + with: { + files: "artifacts/**/enso-*", + name: "Enso ${{fromJson(steps.changelog.outputs.content).version}}", + tag_name: "v${{fromJson(steps.changelog.outputs.content).version}}", + body: "${{fromJson(steps.changelog.outputs.content).body}}", + prerelease: "${{fromJson(steps.changelog.outputs.content).prerelease}}", + }, + } +] @@ -355,6 +371,7 @@ let assertReleaseDoNotExists = [ { name: 'Fail if release already exists', run: 'if [[ ${{ steps.checkCurrentReleaseTag.outputs.exists }} == true ]]; then exit 1; fi', + if: `github.base_ref == 'unstable' || github.base_ref == 'stable'` } ] @@ -407,6 +424,7 @@ let workflow = { installRust, installPrettier, installClippy, + lintMarkdown, lintJavaScript, lintRust ]), @@ -451,7 +469,7 @@ let workflow = { ],{ if:releaseCondition, needs:['version_assertions','lint','test','wasm-test','build'] }), - release_to_cdn: job_on_linux("CDN Release", [ + release_to_cdn: job_on_ubuntu_18_04("CDN Release", [ downloadArtifacts, getCurrentReleaseChangelogInfo, prepareAwsSessionCDN, diff --git a/gui/src/rust/ensogl/README.md b/gui/src/rust/ensogl/README.md index 66265136e2..23ecd6ccbd 100644 --- a/gui/src/rust/ensogl/README.md +++ b/gui/src/rust/ensogl/README.md @@ -1,392 +1,391 @@ # Enso App Framework - ## Overview -Enso App Framework is a fully featured framework for building modern, blazing fast web applications -in the Rust programming language. It comes batteries included, containing: +Enso App Framework is a fully featured framework for building modern, blazing +fast web applications in the Rust programming language. It comes batteries +included, containing: - **[Enso Canvas], a WebGL-based vector shapes rendering engine** - It is blazing-fast, pixel-perfect, uses a high-quality computational anti-aliasing, allows - *almost zero-cost* boolean operations on shapes, and uses sophisticated Lab CIECH color management - system for unparalleled results. - -- **[Enso Signals], a [functional reactive programming] signal processing engine designed exclusively - for the needs of efficient GUI programming and optimized for Rust semantics. - -- [Enso GUI], a rich set of modern GUI components, including iOS-like mouse cursor. -- - -EnsoGL is a blazing fast vector rendering engine that comes batteries included. It was developed -as part of the [Enso](https://github.com/enso-org/enso) project. + It is blazing-fast, pixel-perfect, uses a high-quality computational + anti-aliasing, allows _almost zero-cost_ boolean operations on shapes, and + uses sophisticated Lab CIECH color management system for unparalleled results. +- \*\*[Enso Signals], a [functional reactive programming] signal processing + engine designed exclusively for the needs of efficient GUI programming and + optimized for Rust semantics. +- [Enso GUI], a rich set of modern GUI components, including iOS-like mouse + cursor. +- +EnsoGL is a blazing fast vector rendering engine that comes batteries included. +It was developed as part of the [Enso](https://github.com/enso-org/enso) +project. ## Demo -See the demo videos of [Enso](https://github.com/enso-org/enso) to see an example application based -on EnsoGl - +See the demo videos of [Enso](https://github.com/enso-org/enso) to see an +example application based on EnsoGl ## Features ### High performance and small size + - **No garbage collector** - EnsoGL is written in Rust. All memory management is static, there is not garbage collection - needed, and thus, you can be sure that your creations will run 60 frames per second without - unexpected hiccups. - + EnsoGL is written in Rust. All memory management is static, there is not + garbage collection needed, and thus, you can be sure that your creations will + run 60 frames per second without unexpected hiccups. - **Small binary size** - EnsoGL is a very feature rich library, however, it includes all aspects needed to build fully - featured, production ready applications, including rich set of GUI elements, animation engine, - user events processing engine, keyboard shortcut management, mouse gesture management, and even - dedicated theme resolution engine. For example, [Enso](https://github.com/enso-org/enso), which - naturally uses EnsoGl for all client-side logic weights less than 4Mb in production mode build. - - + EnsoGL is a very feature rich library, however, it includes all aspects needed + to build fully featured, production ready applications, including rich set of + GUI elements, animation engine, user events processing engine, keyboard + shortcut management, mouse gesture management, and even dedicated theme + resolution engine. For example, [Enso](https://github.com/enso-org/enso), + which naturally uses EnsoGl for all client-side logic weights less than 4Mb in + production mode build. ### Vector Shapes -- **Highest anti-aliasing quality possible** - The shapes are always smooth and crisp. They are described using mathematical equations and do not - use triangle-based approximation nor are they interpolated in any way. For example, after - subtracting two circles, no matter how much you scale the resulting shape, it will always render - smooth, crisp, and without any visual glitches and imperfections. It's worth noting that EnsoGL - uses [Signed Distance Functions][SDF] to describe shapes and perform anti-aliasing, and thus do - not need - +- **Highest anti-aliasing quality possible** + The shapes are always smooth and crisp. They are described using mathematical + equations and do not use triangle-based approximation nor are they + interpolated in any way. For example, after subtracting two circles, no matter + how much you scale the resulting shape, it will always render smooth, crisp, + and without any visual glitches and imperfections. It's worth noting that + EnsoGL uses [Signed Distance Functions][sdf] to describe shapes and perform + anti-aliasing, and thus do not need - **Pixel prefect** - Shapes align perfectly with the pixels on the screen. Rendering a rectangle with integer position - will not produce any anti-aliased borders. + Shapes align perfectly with the pixels on the screen. Rendering a rectangle + with integer position will not produce any anti-aliased borders. - **Rich set of primitive shapes** - Including a circle, a rectangle, a rectangle with rounded corners, a triangle, a line, a bezier - curve, and many more. You can also define your own shapes by using [Signed Distance - Functions][SDF]. + Including a circle, a rectangle, a rectangle with rounded corners, a triangle, + a line, a bezier curve, and many more. You can also define your own shapes by + using [Signed Distance Functions][sdf]. - **Blazing fast boolean operations on shapes** - EnsoGL allows performing boolean operations on shapes, including subtracting shapes, finding - common part of two shapes, and even merging shapes with rounded intersection areas (bevels). All - these operations are very fast and do not depend on the shapes' complexity. Subtracting two - circles is as fast as subtracting two shapes build out of 100 circles each. - -- **Infinite amount of symbols instancing** - EnsoGL supports rendering of infinite amount of shapes instances at close-to-zero performance - cost (a cost of a few GPU cycles for all instances altogether). The instancing is done by folding - the used coordinate system into cyclic space. + EnsoGL allows performing boolean operations on shapes, including subtracting + shapes, finding common part of two shapes, and even merging shapes with + rounded intersection areas (bevels). All these operations are very fast and do + not depend on the shapes' complexity. Subtracting two circles is as fast as + subtracting two shapes build out of 100 circles each. +- **Infinite amount of symbols instancing** EnsoGL supports rendering of + infinite amount of shapes instances at close-to-zero performance cost (a cost + of a few GPU cycles for all instances altogether). The instancing is done by + folding the used coordinate system into cyclic space. -- **Lab CIECH color space based color management** - EnsoGL uses Lab CIECH color blending in order to output color blending results. Unlike HTML and - CSS implementations in all popular browsers nowadays, EnsoGL do not produce [visual artifacts when - blending colors together][Blending in browsers]. - -- **Various coordinate systems** - EnsoGL supports various coordinate systems including Cartesian and Polar ones. You can freely - switch between in order to for example bend some parts of the shapes around a given point. - +- **Lab CIECH color space based color management** EnsoGL uses Lab CIECH color + blending in order to output color blending results. Unlike HTML and CSS + implementations in all popular browsers nowadays, EnsoGL do not produce + [visual artifacts when blending colors together][blending in browsers]. +- **Various coordinate systems** EnsoGL supports various coordinate systems + including Cartesian and Polar ones. You can freely switch between in order to + for example bend some parts of the shapes around a given point. ### Signals -EnsoGL ships with a state of the art [Functional Reactive Programming (FRP)][FRP] event processing -system designed exclusively for the needs of GUI programming and optimized for Rust semantics. FRP -systems allow designing even very complex event dependencies in a static, easy to debug way. Unlike -old-school event-listener based approach, FRP does not cause [callback hell] nor leads to -'spaghetti' code, which is hard to read and extend. - +EnsoGL ships with a state of the art [Functional Reactive Programming +(FRP)][frp] event processing system designed exclusively for the needs of GUI +programming and optimized for Rust semantics. FRP systems allow designing even +very complex event dependencies in a static, easy to debug way. Unlike +old-school event-listener based approach, FRP does not cause [callback hell] nor +leads to 'spaghetti' code, which is hard to read and extend. ### Animation -EnsoGL delivers a set of lightweight animation engines in a form of a reactive FRP API. It allows -attaching animations to every interface element simply by plugging an FRP event source to FRP -animation node. For example, the Inertia Simulator enables physical-based animations of positions -and colors, allowing at the same time changing the destination values with smooth interpolation -between states. The Tween engine does not allow smooth destination value change, however, its so -lightweight, that you can consider it non-existent from the performance point of view. +EnsoGL delivers a set of lightweight animation engines in a form of a reactive +FRP API. It allows attaching animations to every interface element simply by +plugging an FRP event source to FRP animation node. For example, the Inertia +Simulator enables physical-based animations of positions and colors, allowing at +the same time changing the destination values with smooth interpolation between +states. The Tween engine does not allow smooth destination value change, +however, its so lightweight, that you can consider it non-existent from the +performance point of view. - -+ Mixing HTML elements +- Mixing HTML elements ### Modern GUI Components - - - - ### Built-in performance statistics +# Rendering Architecture +https://www.nomnoml.com : - # Rendering Architecture +```ignore + #zoom: 0.6 + #gutter:100 + #padding: 14 + #leading: 1.4 + #spacing: 60 + #edgeMargin:5 + #arrowSize: 0.8 + #fill: #FFFFFF; #fdf6e3 - https://www.nomnoml.com : - ```ignore - #zoom: 0.6 - #gutter:100 - #padding: 14 - #leading: 1.4 - #spacing: 60 - #edgeMargin:5 - #arrowSize: 0.8 - #fill: #FFFFFF; #fdf6e3 + #background: #FFFFFF + #.usr: visual=roundrect title=bold stroke=rgb(237,80,80) + #.dyn: visual=roundrect title=bold dashed + #.cpu: visual=roundrect title=bold + #.gpu: stroke=rgb(68,133,187) visual=roundrect - #background: #FFFFFF - #.usr: visual=roundrect title=bold stroke=rgb(237,80,80) - #.dyn: visual=roundrect title=bold dashed - #.cpu: visual=roundrect title=bold - #.gpu: stroke=rgb(68,133,187) visual=roundrect + [ Buffer] + [ WebGL Context] + [ AttributeScope] + [ Attribute] + [ Mesh] + [ Material] + [ Symbol] + [ SymbolRegistry] + [ World] + [ Scene] + [ View] + [ SpriteSystem] + [ Sprite] + [ ShapeSystem] + [ ShapeView] + [ *Shape] + [ *ShapeSystem] + [ *Component] + [ Application] - [ Buffer] - [ WebGL Context] - [ AttributeScope] - [ Attribute] - [ Mesh] - [ Material] - [ Symbol] - [ SymbolRegistry] - [ World] - [ Scene] - [ View] - [ SpriteSystem] - [ Sprite] - [ ShapeSystem] - [ ShapeView] - [ *Shape] - [ *ShapeSystem] - [ *Component] - [ Application] + [AttributeScope] o- [Buffer] + [Buffer] o-- [Attribute] + [Mesh]* o- 4[AttributeScope] + [Symbol]* o- [Mesh] + [Symbol]* o- [Material] + [SymbolRegistry] o- [Symbol] + [Scene] - [SymbolRegistry] + [Scene] o- [View] + [Scene] - [WebGL Context] - [AttributeScope] o- [Buffer] - [Buffer] o-- [Attribute] - [Mesh]* o- 4[AttributeScope] - [Symbol]* o- [Mesh] - [Symbol]* o- [Material] - [SymbolRegistry] o- [Symbol] - [Scene] - [SymbolRegistry] - [Scene] o- [View] - [Scene] - [WebGL Context] + [SpriteSystem] o- [Symbol] + [SpriteSystem] o-- [Sprite] + [ShapeSystem] o- [SpriteSystem] + [Sprite] o- [Symbol] + [Sprite] o- [Attribute] + [*Shape] o- [Sprite] + [*ShapeSystem] o- [ShapeSystem] + [*ShapeSystem] o-- [*Shape] + [*Component] o- [ShapeView] + [ShapeView] - [*Shape] + [View] o- [Symbol] + [View] o- [*ShapeSystem] + [World] o- [Scene] + [Application] - [World] + [Application] o- [*Component] +``` - [SpriteSystem] o- [Symbol] - [SpriteSystem] o-- [Sprite] - [ShapeSystem] o- [SpriteSystem] - [Sprite] o- [Symbol] - [Sprite] o- [Attribute] - [*Shape] o- [Sprite] - [*ShapeSystem] o- [ShapeSystem] - [*ShapeSystem] o-- [*Shape] - [*Component] o- [ShapeView] - [ShapeView] - [*Shape] - [View] o- [Symbol] - [View] o- [*ShapeSystem] - [World] o- [Scene] - [Application] - [World] - [Application] o- [*Component] - ``` - # Shapes Rendering +# Shapes Rendering +## The Current Architecture +The current implementation uses instanced rendering to display shapes. First, a +simple rectangular geometry is defined, and for each new instance, a new +attribute is added to the list of attached attribute arrays. During rendering, +we use the `draw_arrays_instanced` WebGL call to iterate over the arrays and +draw each shape. The shape placement is done from within its vertex shader. - ## The Current Architecture - The current implementation uses instanced rendering to display shapes. First, a simple - rectangular geometry is defined, and for each new instance, a new attribute is added to the list - of attached attribute arrays. During rendering, we use the `draw_arrays_instanced` WebGL call to - iterate over the arrays and draw each shape. The shape placement is done from within its vertex - shader. +See the documentation of [`crate::system::gpu::data::Buffer`]. See the +documentation of [`crate::system::gpu::data::Attribute`]. See the documentation +of [`crate::system::gpu::data::AttributeScope`]. - See the documentation of [`crate::system::gpu::data::Buffer`]. - See the documentation of [`crate::system::gpu::data::Attribute`]. - See the documentation of [`crate::system::gpu::data::AttributeScope`]. +### Known Issues / Ideas of Improvement - ### Known Issues / Ideas of Improvement +The current architecture is very efficient at shapes rendering, which comes with +a few limitations. Below, there are many other architectures described with +their own gains and problems and we should consider improving the current +approach in the future. However, keep in mind that the listed limitations allow +us for very fast rendering pipeline, so it's questionable whether we would like +to ever change it. - The current architecture is very efficient at shapes rendering, which comes with a few - limitations. Below, there are many other architectures described with their own gains and - problems and we should consider improving the current approach in the future. However, keep in - mind that the listed limitations allow us for very fast rendering pipeline, so it's questionable - whether we would like to ever change it. +The most significant limitations of the current approach are: - The most significant limitations of the current approach are: +- No possibility to depth-sort the shapes instances. The used + `draw_arrays_instanced` WebGL draw call iterates over all attrib arrays and + draws a new instance for each entry. There is no possibility to specify the + iteration order, while re-ordering the attrib arrays can be CPU heavy (with + big instance count) and would require re-sending big amount of data between + CPU and GPU (e.g. moving the top-most instance to the bottom would require + moving its attribs in all attached attrib arrays from the last position to the + front, and thus, re sending ALL attrib arrays to the GPU (for ALL INSTANCES)). - - No possibility to depth-sort the shapes instances. - The used `draw_arrays_instanced` WebGL draw call iterates over all attrib arrays and draws a - new instance for each entry. There is no possibility to specify the iteration order, while - re-ordering the attrib arrays can be CPU heavy (with big instance count) and would require - re-sending big amount of data between CPU and GPU (e.g. moving the top-most instance to the - bottom would require moving its attribs in all attached attrib arrays from the last position - to the front, and thus, re sending ALL attrib arrays to the GPU (for ALL INSTANCES)). +- No efficient memory management. In case an instance with a high ID exists and + many instances with lower IDs are already destroyed, the memory of the + destroyed instances cannot be freed. This is because currently the sprite + instances remember the ID (wrapper over usize) of the instance, which is used + as the attrib array index. Thus, it is impossible to update the number in all + sprite instances in memory, and sort the instances to move the destroyed ones + to the end of the buffer to free it. This could be easily solved by using + `Rc>` instead, however, it is important to benchmark how big + performance impact this will cause. Also, other architectures may provide + alternative solutions. - - No efficient memory management. - In case an instance with a high ID exists and many instances with lower IDs are already - destroyed, the memory of the destroyed instances cannot be freed. This is because currently - the sprite instances remember the ID (wrapper over usize) of the instance, which is used as - the attrib array index. Thus, it is impossible to update the number in all sprite instances in - memory, and sort the instances to move the destroyed ones to the end of the buffer to free it. - This could be easily solved by using `Rc>` instead, however, it is important to - benchmark how big performance impact this will cause. Also, other architectures may provide - alternative solutions. +- No possibility to render shape instances using different cameras (in separate + draw calls). Currently, the shape instances are drawn with the + `draw_arrays_instanced` WebGL draw call. This API allows drawing all instances + at once, so it is not possible to draw only some subset of them, and thus, it + is not possible to update the view-matrix uniform between the calls. The + OpenGL 4.2 introduced a specialized draw call that would solve this issue + entirely, however, it is not accessible from within WebGL + ([glDrawArraysInstancedBaseInstance](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawArraysInstancedBaseInstance.xhtml)). - - No possibility to render shape instances using different cameras (in separate draw calls). - Currently, the shape instances are drawn with the `draw_arrays_instanced` WebGL draw call. - This API allows drawing all instances at once, so it is not possible to draw only some subset - of them, and thus, it is not possible to update the view-matrix uniform between the calls. - The OpenGL 4.2 introduced a specialized draw call that would solve this issue entirely, - however, it is not accessible from within WebGL - ([glDrawArraysInstancedBaseInstance](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawArraysInstancedBaseInstance.xhtml)). +### Depth-sorting, memory cleaning, and indexes re-using. +The current approach, however, doesn't allow us to depth-sort the shapes +instances. Also, it does not allow for efficient memory management in case an +instance with a high ID exists and many instances with lover IDs are already +destroyed. This section describes possible alternative architectures and +compares them from this perspective. - ### Depth-sorting, memory cleaning, and indexes re-using. +There are several possible implementation architectures for attribute +management. The currently used architecture may not be the best one, but the +choice is not obvious and would require complex benchmarking. However, lets +compare the available architectures and lets list their good and bad sides: - The current approach, however, doesn't allow us to depth-sort the shapes instances. Also, it - does not allow for efficient memory management in case an instance with a high ID exists and - many instances with lover IDs are already destroyed. This section describes possible alternative - architectures and compares them from this perspective. +#### A. Drawing instanced geometry (the current architecture). - There are several possible implementation architectures for attribute management. The currently - used architecture may not be the best one, but the choice is not obvious and would require - complex benchmarking. However, lets compare the available architectures and lets list their - good and bad sides: +- Rendering. Very fast. May not be as fast as some of other methods, but that + may not be the case with modern hardware, see: + https://stackoverflow.com/a/65376034/889902, and also + https://stackoverflow.com/questions/62537968/using-opengl-instancing-for-rendering-2d-scene-with-object-depths-and-alpha-blen#answer-62538277 +- Changing attribute & GPU memory consumption. Very fast and with low memory + consumption. Requires only 1 WebGL call (attribute per instance). - #### A. Drawing instanced geometry (the current architecture). +- Visual sorting of instances (depth management). Complex. Requires sorting of + all attribute buffers connected with a particular instance. For big buffers + (many instances) it may require significant CPU -> GPU data upload. For + example, taking the last element to the front, would require shifting all + attributes in all buffers, which basically would mean uploading all data to + the GPU from scratch for that particular geometry. Also, this would require + keeping instance IDs in some kind of `Rc>`, as during sorting, the + instance IDs will change, so all sprites would need to be updated. - - Rendering. - Very fast. May not be as fast as some of other methods, but that may not be the case with - modern hardware, see: https://stackoverflow.com/a/65376034/889902, and also - https://stackoverflow.com/questions/62537968/using-opengl-instancing-for-rendering-2d-scene-with-object-depths-and-alpha-blen#answer-62538277 +#### B. Drawing non-instanced, indexed geometry. - - Changing attribute & GPU memory consumption. - Very fast and with low memory consumption. Requires only 1 WebGL call (attribute per - instance). +- Rendering. Very fast. May be faster than architecture (A). See it's + description to learn more. - - Visual sorting of instances (depth management). - Complex. Requires sorting of all attribute buffers connected with a particular instance. For - big buffers (many instances) it may require significant CPU -> GPU data upload. For example, - taking the last element to the front, would require shifting all attributes in all buffers, - which basically would mean uploading all data to the GPU from scratch for that particular - geometry. Also, this would require keeping instance IDs in some kind of `Rc>`, - as during sorting, the instance IDs will change, so all sprites would need to be updated. +- Changing attribute & GPU memory consumption. 4 times slower and 4 times more + memory hungry than architecture (A). Requires setting each attribute for each + vertex (4 WebGL calls). During drawing, vertexes are re-used by using indexed + geometry rendering. +- Visual sorting of instances (depth management). The same issues as in + architecture (A). Even more CPU -> GPU heavy, as the attribute count is + bigger. - #### B. Drawing non-instanced, indexed geometry. +#### C. Drawing non-instanced, non-indexed geometry. Using indexing for sorting. - - Rendering. - Very fast. May be faster than architecture (A). See it's description to learn more. +- Rendering. Very fast. May be faster than architecture (A). See it's + description to learn more. - - Changing attribute & GPU memory consumption. - 4 times slower and 4 times more memory hungry than architecture (A). Requires setting each - attribute for each vertex (4 WebGL calls). During drawing, vertexes are re-used by using - indexed geometry rendering. +- Changing attribute & GPU memory consumption. 6 times slower and 6 times more + memory hungry than architecture (A). Requires setting each attribute for each + vertex (6 WebGL calls). During drawing, vertexes are not re-used, and thus we + need to set attributes for each vertex of each triangle. - - Visual sorting of instances (depth management). - The same issues as in architecture (A). Even more CPU -> GPU heavy, as the attribute count - is bigger. +- Visual sorting of instances (depth management). Simple. We can re-use index + buffer to sort the geometry by telling GPU in what order it should render each + of the vertexes. Unlike previous architectures, this would not require to + create any more internally mutable state regarding attribute index management + (the indexes will not change during sorting). + However, sorting for the needs of memory compression (removing unused memory + for sparse attrib arrays) would still require re-uploading sorted data to GPU, + just as in architecture (A). - #### C. Drawing non-instanced, non-indexed geometry. Using indexing for sorting. +#### D. Keeping all attribute values in a texture and passing index buffer to the shader. - - Rendering. - Very fast. May be faster than architecture (A). See it's description to learn more. +This is a very different architecture to what is currently implemented and might +require very complex refactoring in order to be even tested and benchmarked +properly. To learn more about the idea, follow the link: +https://stackoverflow.com/a/65376034/889902. - - Changing attribute & GPU memory consumption. - 6 times slower and 6 times more memory hungry than architecture (A). Requires setting each - attribute for each vertex (6 WebGL calls). During drawing, vertexes are not re-used, and thus - we need to set attributes for each vertex of each triangle. +- Rendering. Fast. May be slower than architecture (A). Needs real benchmarks. - - Visual sorting of instances (depth management). - Simple. We can re-use index buffer to sort the geometry by telling GPU in what order it - should render each of the vertexes. Unlike previous architectures, this would not require to - create any more internally mutable state regarding attribute index management (the indexes - will not change during sorting). +- Changing attribute & GPU memory consumption. Changing attribute would require + 2 WebGL calls: the `bindTexture`, and `texParameterf` (or similar). + Performance of this solution is questionable, but in real life, it may be as + fast as architecture (A). The memory consumption should be fine as well, as + WebGL textures behave like C++ Vectors, so even if we allocate the texture of + max size, it will occupy only the needed space. This will also limit the + number of instances on the stage, but the limit will be big enough (assuming + max texture od 2048px x 2048px and 20 float attributes per shader, this will + allow us to render over 200 000 shapes). Also, this architecture would allow + us to pass more attributes to shaders than it is currently possible, which on + the other hand, would probably negatively affect the fragment shader + performance. - However, sorting for the needs of memory compression (removing unused memory for sparse - attrib arrays) would still require re-uploading sorted data to GPU, just as in architecture - (A). +- Visual sorting of instances (depth management). Simple. Next to the attribute + texture, we can pass index buffer to the shader, which will dictate what + initial offset in the texture should be used. This would allow for the fastest + sorting mechanism of all of the above architectures. + However, sorting for the needs of memory compression (removing unused memory + for sparse attrib arrays) would still require re-uploading sorted data to GPU, + just as in architecture (A). - #### D. Keeping all attribute values in a texture and passing index buffer to the shader. +#### E. Using the depth-buffer for sorting. - This is a very different architecture to what is currently implemented and might require very - complex refactoring in order to be even tested and benchmarked properly. To learn more about the - idea, follow the link: https://stackoverflow.com/a/65376034/889902. +As with architecture (C), this is a very different architecture to what is +currently implemented and might require very complex refactoring in order to be +even tested and benchmarked properly. This architecture, however, is the most +common architecture among all WebGL / OpenGL applications, but it is not really +well suitable for SDF-based shapes rendering, as it requires anti-aliasing to be +done by multisampling, which is not needed with SDF-based rasterization. It +lowers the quality and drastically increases the rendering time (in the case of +4x4 multisampling, the rendering time is 16x bigger than the time of +architecture (A)). - - Rendering. - Fast. May be slower than architecture (A). Needs real benchmarks. +There is one additional thread to consider here, namely, with some browsers, +systems, and GPU combinations, the super-sampling anti-aliasing is not +accessible in WebGL. In such situations we could use a post-processing +anti-aliasing techniques, such as [FXAA][1] or [SMAA][2], however, the resulting +image quality will be even worse. We could also use custom multi-sampled render +buffers for implementing [multi-sampled depth buffers][3]. [1] +https://github.com/mitsuhiko/webgl-meincraft/blob/master/assets/shaders/fxaa.glsl +[2] +http://www.iryoku.com/papers/SMAA-Enhanced-Subpixel-Morphological-Antialiasing.pdf +[3] +https://stackoverflow.com/questions/50613696/whats-the-purpose-of-multisample-renderbuffers - - Changing attribute & GPU memory consumption. - Changing attribute would require 2 WebGL calls: the `bindTexture`, and `texParameterf` (or - similar). Performance of this solution is questionable, but in real life, it may be as fast - as architecture (A). The memory consumption should be fine as well, as WebGL textures behave - like C++ Vectors, so even if we allocate the texture of max size, it will occupy only the - needed space. This will also limit the number of instances on the stage, but the limit will - be big enough (assuming max texture od 2048px x 2048px and 20 float attributes per shader, - this will allow us to render over 200 000 shapes). Also, this architecture would allow us to - pass more attributes to shaders than it is currently possible, which on the other hand, - would probably negatively affect the fragment shader performance. +- Rendering. May be 9x - 16x slower than architecture (A), depending on + multi-sampling level. Also, the final image quality and edge sharpness will be + lower. There is, however, an open question, whether there is an SDF-suitable + depth-buffer sorting technique which would not cause such downsides (maybe + involving SDF-based depth buffer). Currently, we don't know of any such + technique. - - Visual sorting of instances (depth management). - Simple. Next to the attribute texture, we can pass index buffer to the shader, which will - dictate what initial offset in the texture should be used. This would allow for the fastest - sorting mechanism of all of the above architectures. +- Changing attribute & GPU memory consumption. Fast with low memory consumption. + The same as with architecture (A), (B), or (C). - However, sorting for the needs of memory compression (removing unused memory for sparse - attrib arrays) would still require re-uploading sorted data to GPU, just as in architecture - (A). +- Visual sorting of instances (depth management). Simple and fast. Much faster + than any other architecture listed before, as it does not require upfront + CPU-side buffer sorting. +#### F. Using depth-peeling / dual depth-peeling algorithms. - #### E. Using the depth-buffer for sorting. +As with architecture (C), this is a very different architecture to what is +currently implemented and might require very complex refactoring in order to be +even tested and benchmarked properly. The idea is to render the scene multiple +times, as long as some objects do overlap, by "peeling" the top-most (and +bottom-most) layers every time. See the [Interactive Order-Independent +Transparency][1], the [Order Independent Transparency with Dual Depth +Peeling][2], and the [sample WebGL implementation][3] to learn more. - As with architecture (C), this is a very different architecture to what is currently - implemented and might require very complex refactoring in order to be even tested and - benchmarked properly. This architecture, however, is the most common architecture among all - WebGL / OpenGL applications, but it is not really well suitable for SDF-based shapes rendering, - as it requires anti-aliasing to be done by multisampling, which is not needed with SDF-based - rasterization. It lowers the quality and drastically increases the rendering time (in the case - of 4x4 multisampling, the rendering time is 16x bigger than the time of architecture (A)). +[1] https://my.eng.utah.edu/~cs5610/handouts/order_independent_transparency.pdf +[2] +http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.193.3485&rep=rep1&type=pdf +[3] +https://medium.com/@shrekshao_71662/dual-depth-peeling-implementation-in-webgl-11baa061ba4b - There is one additional thread to consider here, namely, with some browsers, systems, and GPU - combinations, the super-sampling anti-aliasing is not accessible in WebGL. In such situations we - could use a post-processing anti-aliasing techniques, such as [FXAA][1] or [SMAA][2], however, - the resulting image quality will be even worse. We could also use custom multi-sampled render - buffers for implementing [multi-sampled depth buffers][3]. - [1] https://github.com/mitsuhiko/webgl-meincraft/blob/master/assets/shaders/fxaa.glsl - [2] http://www.iryoku.com/papers/SMAA-Enhanced-Subpixel-Morphological-Antialiasing.pdf - [3] https://stackoverflow.com/questions/50613696/whats-the-purpose-of-multisample-renderbuffers +- Rendering. May be several times slower than architecture (A) due to the need + to render the scene by peeling components. However, in contrast to the + architecture (D), the final image quality should be as good as with + architecture (A), (B), or (C). - - Rendering. - May be 9x - 16x slower than architecture (A), depending on multi-sampling level. Also, the - final image quality and edge sharpness will be lower. There is, however, an open question, - whether there is an SDF-suitable depth-buffer sorting technique which would not cause such - downsides (maybe involving SDF-based depth buffer). Currently, we don't know of any such - technique. +- Changing attribute & GPU memory consumption. Fast with low memory consumption. + The same as with architecture (A), (B), or (C). - - Changing attribute & GPU memory consumption. - Fast with low memory consumption. The same as with architecture (A), (B), or (C). - - - Visual sorting of instances (depth management). - Simple and fast. Much faster than any other architecture listed before, as it does not - require upfront CPU-side buffer sorting. - - - #### F. Using depth-peeling / dual depth-peeling algorithms. - - As with architecture (C), this is a very different architecture to what is currently - implemented and might require very complex refactoring in order to be even tested and - benchmarked properly. The idea is to render the scene multiple times, as long as some objects - do overlap, by "peeling" the top-most (and bottom-most) layers every time. See the - [Interactive Order-Independent Transparency][1], the - [Order Independent Transparency with Dual Depth Peeling][2], and the - [sample WebGL implementation][3] to learn more. - - [1] https://my.eng.utah.edu/~cs5610/handouts/order_independent_transparency.pdf - [2] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.193.3485&rep=rep1&type=pdf - [3] https://medium.com/@shrekshao_71662/dual-depth-peeling-implementation-in-webgl-11baa061ba4b - - - Rendering. - May be several times slower than architecture (A) due to the need to render the scene by - peeling components. However, in contrast to the architecture (D), the final image quality - should be as good as with architecture (A), (B), or (C). - - - Changing attribute & GPU memory consumption. - Fast with low memory consumption. The same as with architecture (A), (B), or (C). - - - Visual sorting of instances (depth management). - Simple and fast. As fast as architecture (E), as it does not require upfront CPU-side buffer - sorting. \ No newline at end of file +- Visual sorting of instances (depth management). Simple and fast. As fast as + architecture (E), as it does not require upfront CPU-side buffer sorting. diff --git a/gui/src/rust/ensogl/doc/coordinates.md b/gui/src/rust/ensogl/doc/coordinates.md index aec9cc4564..c375dc39af 100644 --- a/gui/src/rust/ensogl/doc/coordinates.md +++ b/gui/src/rust/ensogl/doc/coordinates.md @@ -1,14 +1,10 @@ - - ## Spaces - **Object Space** Local object coordinates. - - **World Space `(world_matrix * object_space)`** The position relatively to the origin of the world (point `(0,0)` below). - - **Eye Space** The position relatively to the placement of the camera. ```rust @@ -16,13 +12,16 @@ let eye_space = view_matrix * world_space; ``` - - **Clip Space** - The position inside of the Normalized Device Coordinates (NDC) cube. In perspective projection, a 3D point in a truncated - pyramid frustum (eye coordinates) is mapped to the NDC cube. The range of x-coordinate from `[l,r]` to `[-1,1]`, the - y-coordinate from `[b,t]` to `[-1,1]` and the z-coordinate from `[-n,-f]` to `[-1,1]`. Note that the eye coordinates are defined - in the right-handed coordinate system, but NDC uses the left-handed coordinate system. That is, the camera at the origin is - looking along -Z axis in eye space, but it is looking along +Z axis in NDC. + The position inside of the Normalized Device Coordinates (NDC) cube. In + perspective projection, a 3D point in a truncated pyramid frustum (eye + coordinates) is mapped to the NDC cube. The range of x-coordinate from `[l,r]` + to `[-1,1]`, the y-coordinate from `[b,t]` to `[-1,1]` and the z-coordinate + from `[-n,-f]` to `[-1,1]`. Note that the eye coordinates are defined in the + right-handed coordinate system, but NDC uses the left-handed coordinate + system. That is, the camera at the origin is looking along -Z axis in eye + space, but it is looking along +Z axis in NDC. + ```rust let clip_space = projection_matrix * eye_space; ``` @@ -31,7 +30,6 @@ - ## Examples ```rust @@ -81,9 +79,10 @@ println!("world_space2: {:?}", world_space2); println!("object_space2: {:?}", object_space2); ``` - ## Sources + Images and fragments used here are parts of the following articles: + - https://webglfundamentals.org/webgl/lessons/webgl-3d-camera.html - http://www.songho.ca/opengl/gl_transform.html - http://www.songho.ca/opengl/gl_projectionmatrix.html diff --git a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/bubbleVisualization.js b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/bubbleVisualization.js index 8f69309db8..30d6fbb743 100644 --- a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/bubbleVisualization.js +++ b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/bubbleVisualization.js @@ -11,18 +11,14 @@ class BubbleVisualization extends Visualization { const svgElem = document.createElementNS(xmlns, 'svg') svgElem.setAttributeNS(null, 'class', 'vis-svg') - svgElem.setAttributeNS( - null, - 'viewBox', - 0 + ' ' + 0 + ' ' + width + ' ' + height - ) + svgElem.setAttributeNS(null, 'viewBox', 0 + ' ' + 0 + ' ' + width + ' ' + height) svgElem.setAttributeNS(null, 'width', '100%') svgElem.setAttributeNS(null, 'height', '100%') svgElem.setAttributeNS(null, 'transform', 'matrix(1 0 0 -1 0 0)') this.dom.appendChild(svgElem) - data.forEach((data) => { + data.forEach(data => { const bubble = document.createElementNS(xmlns, 'circle') bubble.setAttributeNS(null, 'stroke', 'black') bubble.setAttributeNS(null, 'fill', 'red') diff --git a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js index 00eef414ad..6f185a83c0 100644 --- a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js +++ b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/geoMap.js @@ -110,11 +110,7 @@ class GeoMapVisualization extends Visualization { const mapElem = document.createElement('div') this.mapId = makeId() mapElem.setAttributeNS(null, 'id', this.mapId) - mapElem.setAttributeNS( - null, - 'style', - 'width:' + width + 'px;height: ' + height + 'px;' - ) + mapElem.setAttributeNS(null, 'style', 'width:' + width + 'px;height: ' + height + 'px;') this.dom.appendChild(mapElem) this.mapElem = mapElem } @@ -126,8 +122,7 @@ class GeoMapVisualization extends Visualization { let labelColor = LABEL_LIGHT_COLOR let labelOutline = LABEL_LIGHT_OUTLINE if (document.getElementById('root').classList.contains('dark-theme')) { - defaultMapStyle = - 'mapbox://styles/enso-org/ckiu0o0in2fpp19rpk0jfvg2s' + defaultMapStyle = 'mapbox://styles/enso-org/ckiu0o0in2fpp19rpk0jfvg2s' accentColor = DARK_ACCENT_COLOR labelBackgroundColor = LABEL_DARK_BACKGROUND labelColor = LABEL_DARK_COLOR @@ -229,8 +224,8 @@ class GeoMapVisualization extends Visualization { makeScatterLayer() { return new deck.ScatterplotLayer({ data: this.dataPoints, - getFillColor: (d) => d.color, - getRadius: (d) => d.radius, + getFillColor: d => d.color, + getRadius: d => d.radius, pickable: this.showingLabels, }) } @@ -323,15 +318,11 @@ class GeoMapVisualization extends Visualization { /** * Extract the visualisation data from a full configuration object. */ -function extractVisualizationDataFromFullConfig( - parsedData, - preparedDataPoints, - accentColor -) { +function extractVisualizationDataFromFullConfig(parsedData, preparedDataPoints, accentColor) { if (parsedData.type === SCATTERPLOT_LAYER && parsedData.data.length) { pushPoints(parsedData.data, preparedDataPoints, accentColor) } else if (ok(parsedData.layers)) { - parsedData.layers.forEach((layer) => { + parsedData.layers.forEach(layer => { if (layer.type === SCATTERPLOT_LAYER) { let dataPoints = layer.data || [] pushPoints(dataPoints, preparedDataPoints, accentColor) @@ -345,11 +336,7 @@ function extractVisualizationDataFromFullConfig( /** * Extract the visualisation data from a dataframe. */ -function extractVisualizationDataFromDataFrame( - parsedData, - preparedDataPoints, - accentColor -) { +function extractVisualizationDataFromDataFrame(parsedData, preparedDataPoints, accentColor) { const geoPoints = parsedData.df_latitude.map(function (lat, i) { const lon = parsedData.df_longitude[i] let label = ok(parsedData.df_label) ? parsedData.df_label[i] : undefined @@ -372,17 +359,9 @@ function isDataFrame(data) { */ function extractDataPoints(parsedData, preparedDataPoints, accentColor) { if (isDataFrame(parsedData)) { - extractVisualizationDataFromDataFrame( - parsedData, - preparedDataPoints, - accentColor - ) + extractVisualizationDataFromDataFrame(parsedData, preparedDataPoints, accentColor) } else { - extractVisualizationDataFromFullConfig( - parsedData, - preparedDataPoints, - accentColor - ) + extractVisualizationDataFromFullConfig(parsedData, preparedDataPoints, accentColor) } } @@ -394,11 +373,9 @@ function extractDataPoints(parsedData, preparedDataPoints, accentColor) { * optionally `radius`, `color` and `label`. */ function pushPoints(dataPoints, targetList, accentColor) { - dataPoints.forEach((geoPoint) => { + dataPoints.forEach(geoPoint => { let position = [geoPoint.longitude, geoPoint.latitude] - let radius = isNaN(geoPoint.radius) - ? DEFAULT_POINT_RADIUS - : geoPoint.radius + let radius = isNaN(geoPoint.radius) ? DEFAULT_POINT_RADIUS : geoPoint.radius let color = ok(geoPoint.color) ? geoPoint.color : accentColor let label = ok(geoPoint.label) ? geoPoint.label : '' targetList.push({ position, color, radius, label }) @@ -412,7 +389,7 @@ function pushPoints(dataPoints, targetList, accentColor) { function calculateExtent(dataPoints) { const xs = [] const ys = [] - dataPoints.forEach((e) => { + dataPoints.forEach(e => { xs.push(e.position[0]) ys.push(e.position[1]) }) diff --git a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/histogram.js b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/histogram.js index e052892d46..17adf79357 100644 --- a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/histogram.js +++ b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/histogram.js @@ -4,9 +4,9 @@ loadScript('https://d3js.org/d3.v4.min.js') loadStyle('https://fontlibrary.org/face/dejavu-sans-mono') let shortcuts = { - zoomIn: (e) => (e.ctrlKey || e.metaKey) && e.key === 'z', - showAll: (e) => (e.ctrlKey || e.metaKey) && e.key === 'a', - debugPreprocessor: (e) => (e.ctrlKey || e.metaKey) && e.key === 'd', + zoomIn: e => (e.ctrlKey || e.metaKey) && e.key === 'z', + showAll: e => (e.ctrlKey || e.metaKey) && e.key === 'a', + debugPreprocessor: e => (e.ctrlKey || e.metaKey) && e.key === 'd', } const LABEL_STYLE = 'font-family: DejaVuSansMonoBook; font-size: 10px;' @@ -86,9 +86,7 @@ class Histogram extends Visualization { if (isUpdate) { this._axisSpec = ok(data.axis) ? data.axis : this._axisSpec this._focus = ok(data.focus) ? data.focus : this._focus - this._dataValues = ok(data.data.values) - ? data.data.values - : this.data + this._dataValues = ok(data.data.values) ? data.data.values : this.data this._bins = ok(data.bins) ? data.bins : this._bins } else { this._axisSpec = data.axis @@ -187,19 +185,13 @@ class Histogram extends Visualization { .append('g') .attr( 'transform', - 'translate(' + - this.canvas.margin.left + - ',' + - this.canvas.margin.top + - ')' + 'translate(' + this.canvas.margin.left + ',' + this.canvas.margin.top + ')' ) this.yAxis = this.svg.append('g').attr('style', LABEL_STYLE) this.xAxis = this.svg.append('g').attr('style', LABEL_STYLE) - this.plot = this.svg - .append('g') - .attr('clip-path', 'url(#hist-clip-path)') + this.plot = this.svg.append('g').attr('clip-path', 'url(#hist-clip-path)') // Create clip path const defs = this.svg.append('defs') @@ -225,7 +217,7 @@ class Histogram extends Visualization { } initDebugShortcut() { - document.addEventListener('keydown', (e) => { + document.addEventListener('keydown', e => { if (shortcuts.debugPreprocessor(e)) { this.setPreprocessor('x -> "[1,2,3,4]"') e.preventDefault() @@ -250,10 +242,7 @@ class Histogram extends Visualization { let scroll_wheel = 0 switch (d3.event.type) { case 'mousedown': - return ( - d3.event.button === right_button || - d3.event.button === mid_button - ) + return d3.event.button === right_button || d3.event.button === mid_button case 'wheel': return d3.event.button === scroll_wheel default: @@ -310,10 +299,7 @@ class Histogram extends Visualization { // The brush element must be child of zoom element - this is only way we found to have both // zoom and brush events working at the same time. See https://stackoverflow.com/a/59757276 . - const brushElem = zoom.zoomElem - .append('g') - .attr('class', brushClass) - .call(brush) + const brushElem = zoom.zoomElem.append('g').attr('class', brushClass).call(brush) const self = this @@ -334,7 +320,7 @@ class Histogram extends Visualization { self.rescale(self.scale, true) } - const zoomInKeyEvent = (event) => { + const zoomInKeyEvent = event => { if (shortcuts.zoomIn(event)) { zoomIn() endBrushing() @@ -363,9 +349,7 @@ class Histogram extends Visualization { } let endEvents = ['click', 'auxclick', 'contextmenu', 'scroll'] - endEvents.forEach((e) => - document.addEventListener(e, endBrushing, false) - ) + endEvents.forEach(e => document.addEventListener(e, endBrushing, false)) } /** @@ -383,7 +367,7 @@ class Histogram extends Visualization { .duration(animation_duration) .attr( 'transform', - (d) => + d => 'translate(' + scale.x(d.x0) + ',' + @@ -417,10 +401,7 @@ class Histogram extends Visualization { } } - const x = d3 - .scaleLinear() - .domain(domain_x) - .range([0, this.canvas.inner.width]) + const x = d3.scaleLinear().domain(domain_x).range([0, this.canvas.inner.width]) this.xAxis .attr('transform', 'translate(0,' + this.canvas.inner.height + ')') @@ -428,21 +409,18 @@ class Histogram extends Visualization { const histogram = d3 .histogram() - .value((d) => d) + .value(d => d) .domain(x.domain()) .thresholds(x.ticks(this.binCount())) const bins = histogram(dataPoints) const y = d3.scaleLinear().range([this.canvas.inner.height, 0]) - y.domain([0, d3.max(bins, (d) => d.length)]) + y.domain([0, d3.max(bins, d => d.length)]) - const yAxisTicks = y.ticks().filter((tick) => Number.isInteger(tick)) + const yAxisTicks = y.ticks().filter(tick => Number.isInteger(tick)) - const yAxis = d3 - .axisLeft(y) - .tickValues(yAxisTicks) - .tickFormat(d3.format('d')) + const yAxis = d3.axisLeft(y).tickValues(yAxisTicks).tickFormat(d3.format('d')) this.yAxis.call(yAxis) @@ -458,12 +436,9 @@ class Histogram extends Visualization { .enter() .append('rect') .attr('x', 1) - .attr( - 'transform', - (d) => 'translate(' + x(d.x0) + ',' + y(d.length) + ')' - ) - .attr('width', (d) => x(d.x1) - x(d.x0)) - .attr('height', (d) => this.canvas.inner.height - y(d.length)) + .attr('transform', d => 'translate(' + x(d.x0) + ',' + y(d.length) + ')') + .attr('width', d => x(d.x1) - x(d.x0)) + .attr('height', d => this.canvas.inner.height - y(d.length)) .style('fill', accentColor) items.exit().remove() @@ -496,28 +471,15 @@ class Histogram extends Visualization { const fontStyle = '10px DejaVuSansMonoBook' if (axis.x.label !== undefined) { this.xAxisLabel - .attr( - 'y', - canvas.inner.height + - canvas.margin.bottom - - X_AXIS_LABEL_WIDTH / 2.0 - ) - .attr( - 'x', - canvas.inner.width / 2.0 + - this.textWidth(axis.x.label, fontStyle) / 2 - ) + .attr('y', canvas.inner.height + canvas.margin.bottom - X_AXIS_LABEL_WIDTH / 2.0) + .attr('x', canvas.inner.width / 2.0 + this.textWidth(axis.x.label, fontStyle) / 2) .text(axis.x.label) } // Note: y axis is rotated by 90 degrees, so x/y is switched. if (axis.y.label !== undefined) { this.yAxisLabel .attr('y', -canvas.margin.left + Y_AXIS_LABEL_WIDTH) - .attr( - 'x', - -canvas.inner.height / 2 + - this.textWidth(axis.y.label, fontStyle) / 2 - ) + .attr('x', -canvas.inner.height / 2 + this.textWidth(axis.y.label, fontStyle) / 2) .text(axis.y.label) } } @@ -546,7 +508,7 @@ class Histogram extends Visualization { let xMin = dataPoints[0] let xMax = dataPoints[0] - dataPoints.forEach((value) => { + dataPoints.forEach(value => { if (value < xMin) { xMin = value } @@ -602,11 +564,7 @@ class Histogram extends Visualization { createOuterContainerWithStyle(width, height) { const divElem = document.createElementNS(null, 'div') divElem.setAttributeNS(null, 'class', 'vis-histogram') - divElem.setAttributeNS( - null, - 'viewBox', - 0 + ' ' + 0 + ' ' + width + ' ' + height - ) + divElem.setAttributeNS(null, 'viewBox', 0 + ' ' + 0 + ' ' + width + ' ' + height) divElem.setAttributeNS(null, 'width', '100%') divElem.setAttributeNS(null, 'height', '100%') @@ -692,10 +650,7 @@ class Histogram extends Visualization { const self = this const reset_zoom_and_pan = () => { - zoom.zoomElem - .transition() - .duration(0) - .call(zoom.zoom.transform, d3.zoomIdentity) + zoom.zoomElem.transition().duration(0).call(zoom.zoom.transform, d3.zoomIdentity) let domain_x = [ extremesAndDeltas.xMin - extremesAndDeltas.paddingX, @@ -707,7 +662,7 @@ class Histogram extends Visualization { self.rescale(self.scale, true) } - document.addEventListener('keydown', (e) => { + document.addEventListener('keydown', e => { if (shortcuts.showAll(e)) { reset_zoom_and_pan() } diff --git a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js index 86543dbd50..34040d3e31 100644 --- a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js +++ b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js @@ -2,8 +2,8 @@ loadScript('https://d3js.org/d3.v4.min.js') loadStyle('https://fontlibrary.org/face/dejavu-sans-mono') let shortcuts = { - zoomIn: (e) => (e.ctrlKey || e.metaKey) && e.key === 'z', - showAll: (e) => (e.ctrlKey || e.metaKey) && event.key === 'a', + zoomIn: e => (e.ctrlKey || e.metaKey) && e.key === 'z', + showAll: e => (e.ctrlKey || e.metaKey) && event.key === 'a', } const label_style = 'font-family: DejaVuSansMonoBook; font-size: 10px;' @@ -52,10 +52,7 @@ class ScatterPlot extends Visualization { this.dom.removeChild(this.dom.lastChild) } - const divElem = this.createDivElem( - this.canvasWidth(), - this.canvasHeight() - ) + const divElem = this.createDivElem(this.canvasWidth(), this.canvasHeight()) this.dom.appendChild(divElem) let parsedData = this.parseData(data) @@ -67,10 +64,7 @@ class ScatterPlot extends Visualization { .attr('width', this.canvasWidth()) .attr('height', this.canvasHeight()) .append('g') - .attr( - 'transform', - 'translate(' + this.margin.left + ',' + this.margin.top + ')' - ) + .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')') let extremesAndDeltas = this.getExtremesAndDeltas(this.dataPoints) let scaleAndAxis = this.createAxes( @@ -81,13 +75,7 @@ class ScatterPlot extends Visualization { svg, focus ) - this.createLabels( - this.axis, - svg, - this.box_width, - this.margin, - this.box_height - ) + this.createLabels(this.axis, svg, this.box_width, this.margin, this.box_height) let scatter = this.createScatter( svg, this.box_width, @@ -148,10 +136,8 @@ class ScatterPlot extends Visualization { this.dataPoints = this.extractValues(parsedData) this.margin = this.getMargins(this.axis) - this.box_width = - this.canvasWidth() - this.margin.left - this.margin.right - this.box_height = - this.canvasHeight() - this.margin.top - this.margin.bottom + this.box_width = this.canvasWidth() - this.margin.left - this.margin.right + this.box_height = this.canvasHeight() - this.margin.top - this.margin.bottom } extractValues(data) { @@ -186,15 +172,7 @@ class ScatterPlot extends Visualization { /** * Adds panning and zooming functionality to the visualization. */ - addPanAndZoom( - box_width, - box_height, - svg, - margin, - scaleAndAxis, - scatter, - points - ) { + addPanAndZoom(box_width, box_height, svg, margin, scaleAndAxis, scatter, points) { let zoomClass = 'zoom' let minScale = 0.5 let maxScale = 20 @@ -207,10 +185,7 @@ class ScatterPlot extends Visualization { let scroll_wheel = 0 switch (d3.event.type) { case 'mousedown': - return ( - d3.event.button === right_button || - d3.event.button === mid_button - ) + return d3.event.button === right_button || d3.event.button === mid_button case 'wheel': return d3.event.button === scroll_wheel default: @@ -241,27 +216,20 @@ class ScatterPlot extends Visualization { let new_xScale = d3.event.transform.rescaleX(scaleAndAxis.xScale) let new_yScale = d3.event.transform.rescaleY(scaleAndAxis.yScale) - scaleAndAxis.xAxis.call( - d3.axisBottom(new_xScale).ticks(box_width / x_axis_label_width) - ) + scaleAndAxis.xAxis.call(d3.axisBottom(new_xScale).ticks(box_width / x_axis_label_width)) scaleAndAxis.yAxis.call(d3.axisLeft(new_yScale)) scatter .selectAll('path') .attr( 'transform', - (d) => - 'translate(' + - new_xScale(d.x) + - ',' + - new_yScale(d.y) + - ')' + d => 'translate(' + new_xScale(d.x) + ',' + new_yScale(d.y) + ')' ) if (points.labels === visilbe_points) { scatter .selectAll('text') - .attr('x', (d) => new_xScale(d.x) + point_label_padding_x) - .attr('y', (d) => new_yScale(d.y) + point_label_padding_y) + .attr('x', d => new_xScale(d.x) + point_label_padding_x) + .attr('y', d => new_yScale(d.y) + point_label_padding_y) } } @@ -275,8 +243,7 @@ class ScatterPlot extends Visualization { let current_transform = d3.zoomTransform(scatter) let delta_multiplier = 0.01 if (d3.event.ctrlKey) { - current_transform.k = - current_transform.k - d3.event.deltaY * delta_multiplier + current_transform.k = current_transform.k - d3.event.deltaY * delta_multiplier } scatter.attr('transform', current_transform) } @@ -290,15 +257,7 @@ class ScatterPlot extends Visualization { * Brush is a tool which enables user to select points, and zoom into selection via * keyboard shortcut or button event. */ - addBrushing( - box_width, - box_height, - scatter, - scaleAndAxis, - selectedZoomBtn, - points, - zoom - ) { + addBrushing(box_width, box_height, scatter, scaleAndAxis, selectedZoomBtn, points, zoom) { let extent let brushClass = 'brush' @@ -312,10 +271,7 @@ class ScatterPlot extends Visualization { // The brush element must be child of zoom element - this is only way we found to have both zoom and brush // events working at the same time. See https://stackoverflow.com/a/59757276 . - let brushElem = zoom.zoomElem - .append('g') - .attr('class', brushClass) - .call(brush) + let brushElem = zoom.zoomElem.append('g').attr('class', brushClass).call(brush) let self = this @@ -337,7 +293,7 @@ class ScatterPlot extends Visualization { self.zoomingHelper(scaleAndAxis, box_width, scatter, points) } - const zoomInKeyEvent = (event) => { + const zoomInKeyEvent = event => { if (shortcuts.zoomIn(event)) { zoomIn() endBrushing() @@ -366,9 +322,7 @@ class ScatterPlot extends Visualization { } let endEvents = ['click', 'auxclick', 'contextmenu', 'scroll'] - endEvents.forEach((e) => - document.addEventListener(e, endBrushing, false) - ) + endEvents.forEach(e => document.addEventListener(e, endBrushing, false)) } /** @@ -378,11 +332,7 @@ class ScatterPlot extends Visualization { scaleAndAxis.xAxis .transition() .duration(animation_duration) - .call( - d3 - .axisBottom(scaleAndAxis.xScale) - .ticks(box_width / x_axis_label_width) - ) + .call(d3.axisBottom(scaleAndAxis.xScale).ticks(box_width / x_axis_label_width)) scaleAndAxis.yAxis .transition() .duration(animation_duration) @@ -394,12 +344,7 @@ class ScatterPlot extends Visualization { .duration(animation_duration) .attr( 'transform', - (d) => - 'translate(' + - scaleAndAxis.xScale(d.x) + - ',' + - scaleAndAxis.yScale(d.y) + - ')' + d => 'translate(' + scaleAndAxis.xScale(d.x) + ',' + scaleAndAxis.yScale(d.y) + ')' ) if (points.labels === visilbe_points) { @@ -407,28 +352,15 @@ class ScatterPlot extends Visualization { .selectAll('text') .transition() .duration(animation_duration) - .attr( - 'x', - (d) => scaleAndAxis.xScale(d.x) + point_label_padding_x - ) - .attr( - 'y', - (d) => scaleAndAxis.yScale(d.y) + point_label_padding_y - ) + .attr('x', d => scaleAndAxis.xScale(d.x) + point_label_padding_x) + .attr('y', d => scaleAndAxis.yScale(d.y) + point_label_padding_y) } } /** * Creates a plot object and populates it with given data. */ - createScatter( - svg, - box_width, - box_height, - points, - dataPoints, - scaleAndAxis - ) { + createScatter(svg, box_width, box_height, points, dataPoints, scaleAndAxis) { let clip = svg .append('defs') .append('svg:clipPath') @@ -452,20 +384,13 @@ class ScatterPlot extends Visualization { .append('path') .attr( 'd', - symbol - .type(this.matchShape()) - .size((d) => (d.size || 1.0) * sizeScaleMultiplier) + symbol.type(this.matchShape()).size(d => (d.size || 1.0) * sizeScaleMultiplier) ) .attr( 'transform', - (d) => - 'translate(' + - scaleAndAxis.xScale(d.x) + - ',' + - scaleAndAxis.yScale(d.y) + - ')' + d => 'translate(' + scaleAndAxis.xScale(d.x) + ',' + scaleAndAxis.yScale(d.y) + ')' ) - .style('fill', (d) => '#' + (d.color || '000000')) + .style('fill', d => '#' + (d.color || '000000')) .style('opacity', 0.5) if (points.labels === visilbe_points) { @@ -474,15 +399,9 @@ class ScatterPlot extends Visualization { .data(dataPoints) .enter() .append('text') - .text((d) => d.label) - .attr( - 'x', - (d) => scaleAndAxis.xScale(d.x) + point_label_padding_x - ) - .attr( - 'y', - (d) => scaleAndAxis.yScale(d.y) + point_label_padding_y - ) + .text(d => d.label) + .attr('x', d => scaleAndAxis.xScale(d.x) + point_label_padding_x) + .attr('y', d => scaleAndAxis.yScale(d.y) + point_label_padding_y) .attr('style', label_style) .attr('fill', 'black') } @@ -494,7 +413,7 @@ class ScatterPlot extends Visualization { * Helper function to match d3 shape from string. */ matchShape() { - return (d) => { + return d => { if (d.shape === 'cross') { return d3.symbolCross } @@ -524,10 +443,7 @@ class ScatterPlot extends Visualization { svg.append('text') .attr('text-anchor', 'end') .attr('style', label_style) - .attr( - 'x', - margin.left + this.getTextWidth(axis.x.label, fontStyle) / 2 - ) + .attr('x', margin.left + this.getTextWidth(axis.x.label, fontStyle) / 2) .attr('y', box_height + margin.top + padding_y) .text(axis.x.label) } @@ -541,9 +457,7 @@ class ScatterPlot extends Visualization { .attr('y', -margin.left + padding_y) .attr( 'x', - -margin.top - - box_height / 2 + - this.getTextWidth(axis.y.label, fontStyle) / 2 + -margin.top - box_height / 2 + this.getTextWidth(axis.y.label, fontStyle) / 2 ) .text(axis.y.label) } @@ -585,10 +499,7 @@ class ScatterPlot extends Visualization { } yScale.domain(domain_y).range([box_height, 0]) - let yAxis = svg - .append('g') - .attr('style', label_style) - .call(d3.axisLeft(yScale)) + let yAxis = svg.append('g').attr('style', label_style).call(d3.axisLeft(yScale)) return { xScale: xScale, yScale: yScale, xAxis: xAxis, yAxis: yAxis } } @@ -611,11 +522,7 @@ class ScatterPlot extends Visualization { ] if (focus !== undefined) { - if ( - focus.x !== undefined && - focus.y !== undefined && - focus.zoom !== undefined - ) { + if (focus.x !== undefined && focus.y !== undefined && focus.zoom !== undefined) { let padding_x = extremesAndDeltas.dx * (1 / (2 * focus.zoom)) let padding_y = extremesAndDeltas.dy * (1 / (2 * focus.zoom)) domain_x = [focus.x - padding_x, focus.x + padding_x] @@ -638,7 +545,7 @@ class ScatterPlot extends Visualization { let yMin = dataPoints[0].y let yMax = dataPoints[0].y - dataPoints.forEach((d) => { + dataPoints.forEach(d => { if (d.x < xMin) { xMin = d.x } @@ -691,11 +598,7 @@ class ScatterPlot extends Visualization { createDivElem(width, height) { const divElem = document.createElementNS(null, 'div') divElem.setAttributeNS(null, 'class', 'vis-scatterplot') - divElem.setAttributeNS( - null, - 'viewBox', - 0 + ' ' + 0 + ' ' + width + ' ' + height - ) + divElem.setAttributeNS(null, 'viewBox', 0 + ' ' + 0 + ' ' + width + ' ' + height) divElem.setAttributeNS(null, 'width', '100%') divElem.setAttributeNS(null, 'height', '100%') divElem.setAttributeNS(null, 'transform', 'matrix(1 0 0 -1 0 0)') @@ -773,14 +676,7 @@ class ScatterPlot extends Visualization { /** * Creates a button to fit all points on plot. */ - createButtonFitAll( - scaleAndAxis, - scatter, - points, - extremesAndDeltas, - zoom, - box_width - ) { + createButtonFitAll(scaleAndAxis, scatter, points, extremesAndDeltas, zoom, box_width) { const btn = this.createBtnHelper() let text = document.createTextNode('Fit all') @@ -788,10 +684,7 @@ class ScatterPlot extends Visualization { let self = this const unzoom = () => { - zoom.zoomElem - .transition() - .duration(0) - .call(zoom.zoom.transform, d3.zoomIdentity) + zoom.zoomElem.transition().duration(0).call(zoom.zoom.transform, d3.zoomIdentity) let domain_x = [ extremesAndDeltas.xMin - extremesAndDeltas.paddingX, @@ -808,7 +701,7 @@ class ScatterPlot extends Visualization { self.zoomingHelper(scaleAndAxis, box_width, scatter, points) } - document.addEventListener('keydown', (e) => { + document.addEventListener('keydown', e => { if (shortcuts.showAll(e)) { unzoom() } diff --git a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js index 56479b60d4..0ba675a20a 100644 --- a/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js +++ b/gui/src/rust/ide/view/graph-editor/src/builtin/visualization/java_script/table.js @@ -28,15 +28,12 @@ class TableVisualization extends Visualization { } function hasExactlyKeys(keys, obj) { - return ( - Object.keys(obj).length === keys.length && - keys.every((k) => obj.hasOwnProperty(k)) - ) + return Object.keys(obj).length === keys.length && keys.every(k => obj.hasOwnProperty(k)) } function getAtNestedKey(data, key) { let res = data - key.forEach((k) => (res = res[k])) + key.forEach(k => (res = res[k])) return res } @@ -48,14 +45,10 @@ class TableVisualization extends Visualization { let first = getAtNestedKey(data[0], key) if (!(first instanceof Object)) return [key] let firstKeys = Object.keys(first) - let isNestable = data.every((obj) => - hasExactlyKeys(firstKeys, getAtNestedKey(obj, key)) - ) + let isNestable = data.every(obj => hasExactlyKeys(firstKeys, getAtNestedKey(obj, key))) if (isNestable) { - let withNests = firstKeys.map((k) => key.concat([k])) - let furtherNestings = withNests.map((k) => - generateNestings(data, k) - ) + let withNests = firstKeys.map(k => key.concat([k])) + let furtherNestings = withNests.map(k => generateNestings(data, k)) return [].concat.apply([], furtherNestings) } else { return [key] @@ -66,7 +59,7 @@ class TableVisualization extends Visualization { let isList = Array.isArray(data) && data[0] if (!isList || !(typeof data[0] === 'object')) return false let firstKeys = Object.keys(data[0]) - return data.every((obj) => hasExactlyKeys(firstKeys, obj)) + return data.every(obj => hasExactlyKeys(firstKeys, obj)) } function genObjectMatrix(data, level) { @@ -74,15 +67,15 @@ class TableVisualization extends Visualization { let keys = Object.keys(data[0]) let nests = [].concat.apply( [], - keys.map((k) => generateNestings(data, [k])) + keys.map(k => generateNestings(data, [k])) ) - nests.forEach((key) => { + nests.forEach(key => { result += '' + repNestedKey(key) + '' }) result += '' data.forEach((row, ix) => { result += '' + ix + '' - nests.forEach((k) => { + nests.forEach(k => { result += toTableCell(getAtNestedKey(row, k), level) }) result += '' @@ -96,7 +89,7 @@ class TableVisualization extends Visualization { let firstIsArray = Array.isArray(data[0]) if (!firstIsArray) return false let firstLen = data[0].length - return data.every((d) => d.length === firstLen) + return data.every(d => d.length === firstLen) } function genMatrix(data, level, header) { @@ -122,7 +115,7 @@ class TableVisualization extends Visualization { table.forEach((row, ix) => { result += '' + ix + '' - row.forEach((d) => { + row.forEach(d => { result += toTableCell(d, level) }) result += '' @@ -133,12 +126,7 @@ class TableVisualization extends Visualization { function genGenericTable(data, level) { let result = '' data.forEach((point, ix) => { - result += - '' + - ix + - '' + - toTableCell(point, level) + - '' + result += '' + ix + '' + toTableCell(point, level) + '' }) return tableOf(result, level) } @@ -146,11 +134,11 @@ class TableVisualization extends Visualization { function genRowObjectTable(data, level) { let keys = Object.keys(data) let result = '' - keys.forEach((key) => { + keys.forEach(key => { result += '' + key + '' }) result += '' - keys.forEach((key) => { + keys.forEach(key => { result += toTableCell(data[key], level) }) result += '' @@ -165,11 +153,7 @@ class TableVisualization extends Visualization { } else { if (data === undefined || data === null) data = '' let res = data.toString() - return ( - '' + - (res === '' ? 'N/A' : res) + - '' - ) + return '' + (res === '' ? 'N/A' : res) + '' } } @@ -280,11 +264,7 @@ class TableVisualization extends Visualization { if (document.getElementById('root').classList.contains('dark-theme')) { style = style_dark } - const table = genTable( - parsedData.data || parsedData, - 0, - parsedData.header - ) + const table = genTable(parsedData.data || parsedData, 0, parsedData.header) tabElem.innerHTML = style + table }