mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 21:41:34 +03:00
CI Fixes (https://github.com/enso-org/ide/pull/1304)
Original commit: 0ff9e3f62b
This commit is contained in:
parent
3a04557ac5
commit
87a19bec3d
50
gui/.github/workflows/gui-ci.yml
vendored
50
gui/.github/workflows/gui-ci.yml
vendored
@ -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
|
||||
|
@ -1,4 +0,0 @@
|
||||
trailingComma = "es5"
|
||||
tabWidth = 4
|
||||
semi = false
|
||||
singleQuote = true
|
15
gui/.prettierrc.yaml
Normal file
15
gui/.prettierrc.yaml
Normal file
@ -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"
|
@ -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.
|
||||
|
||||
<br/>![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
|
||||
|
@ -21,7 +21,6 @@
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
|
||||
### Overview
|
||||
|
||||
<p>
|
||||
@ -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).
|
||||
|
||||
<br/>
|
||||
|
||||
### 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).
|
||||
|
||||
<br/>
|
||||
|
||||
### 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.
|
||||
<br/>
|
||||
|
||||
### 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.
|
||||
|
||||
<br/>
|
||||
|
||||
### 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
|
||||
|
@ -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.**
|
||||
|
||||
<!-- MarkdownTOC levels="2" autolink="true" -->
|
||||
@ -21,16 +22,18 @@ This document outlines the security policy for Enso and its libraries.
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
## 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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
[<gpu> Buffer]
|
||||
[<gpu> WebGL Context]
|
||||
[<cpu> AttributeScope]
|
||||
[<cpu> Attribute]
|
||||
[<cpu> Mesh]
|
||||
[<cpu> Material]
|
||||
[<cpu> Symbol]
|
||||
[<cpu> SymbolRegistry]
|
||||
[<cpu> World]
|
||||
[<cpu> Scene]
|
||||
[<cpu> View]
|
||||
[<cpu> SpriteSystem]
|
||||
[<cpu> Sprite]
|
||||
[<cpu> ShapeSystem]
|
||||
[<dyn> ShapeView]
|
||||
[<usr> *Shape]
|
||||
[<usr> *ShapeSystem]
|
||||
[<usr> *Component]
|
||||
[<cpu> Application]
|
||||
|
||||
[<gpu> Buffer]
|
||||
[<gpu> WebGL Context]
|
||||
[<cpu> AttributeScope]
|
||||
[<cpu> Attribute]
|
||||
[<cpu> Mesh]
|
||||
[<cpu> Material]
|
||||
[<cpu> Symbol]
|
||||
[<cpu> SymbolRegistry]
|
||||
[<cpu> World]
|
||||
[<cpu> Scene]
|
||||
[<cpu> View]
|
||||
[<cpu> SpriteSystem]
|
||||
[<cpu> Sprite]
|
||||
[<cpu> ShapeSystem]
|
||||
[<dyn> ShapeView]
|
||||
[<usr> *Shape]
|
||||
[<usr> *ShapeSystem]
|
||||
[<usr> *Component]
|
||||
[<cpu> 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<Cell<ID>>` 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<Cell<ID>>` 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<Cell<usize>>`, 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<Cell<usize>>`,
|
||||
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.
|
||||
- Visual sorting of instances (depth management). Simple and fast. As fast as
|
||||
architecture (E), as it does not require upfront CPU-side buffer sorting.
|
||||
|
@ -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).
|
||||
<img width="400" src="https://user-images.githubusercontent.com/1623053/85816645-37e00280-b76c-11ea-9831-e6ae7378830e.png"/>
|
||||
|
||||
- **Eye Space**
|
||||
The position relatively to the placement of the camera.
|
||||
```rust
|
||||
@ -16,13 +12,16 @@
|
||||
let eye_space = view_matrix * world_space;
|
||||
```
|
||||
<img width="400" src="https://user-images.githubusercontent.com/1623053/85816908-d40a0980-b76c-11ea-8be6-6c982b1d8ce5.png"/>
|
||||
|
||||
- **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 @@
|
||||
<img width="600" src="https://user-images.githubusercontent.com/1623053/85817751-22b8a300-b76f-11ea-8f18-f3e78f3139c1.png"/>
|
||||
<img width="600" src="https://user-images.githubusercontent.com/1623053/85817783-3e23ae00-b76f-11ea-8972-c90f1eb6ba1e.png"/>
|
||||
|
||||
|
||||
## 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
|
||||
|
@ -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')
|
||||
|
@ -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])
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 += '<th>' + repNestedKey(key) + '</th>'
|
||||
})
|
||||
result += '</tr>'
|
||||
data.forEach((row, ix) => {
|
||||
result += '<tr><th>' + ix + '</th>'
|
||||
nests.forEach((k) => {
|
||||
nests.forEach(k => {
|
||||
result += toTableCell(getAtNestedKey(row, k), level)
|
||||
})
|
||||
result += '</tr>'
|
||||
@ -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 += '<tr><th>' + ix + '</th>'
|
||||
row.forEach((d) => {
|
||||
row.forEach(d => {
|
||||
result += toTableCell(d, level)
|
||||
})
|
||||
result += '</tr>'
|
||||
@ -133,12 +126,7 @@ class TableVisualization extends Visualization {
|
||||
function genGenericTable(data, level) {
|
||||
let result = ''
|
||||
data.forEach((point, ix) => {
|
||||
result +=
|
||||
'<tr><th>' +
|
||||
ix +
|
||||
'</th>' +
|
||||
toTableCell(point, level) +
|
||||
'</tr>'
|
||||
result += '<tr><th>' + ix + '</th>' + toTableCell(point, level) + '</tr>'
|
||||
})
|
||||
return tableOf(result, level)
|
||||
}
|
||||
@ -146,11 +134,11 @@ class TableVisualization extends Visualization {
|
||||
function genRowObjectTable(data, level) {
|
||||
let keys = Object.keys(data)
|
||||
let result = '<tr>'
|
||||
keys.forEach((key) => {
|
||||
keys.forEach(key => {
|
||||
result += '<th>' + key + '</th>'
|
||||
})
|
||||
result += '</tr><tr>'
|
||||
keys.forEach((key) => {
|
||||
keys.forEach(key => {
|
||||
result += toTableCell(data[key], level)
|
||||
})
|
||||
result += '</tr>'
|
||||
@ -165,11 +153,7 @@ class TableVisualization extends Visualization {
|
||||
} else {
|
||||
if (data === undefined || data === null) data = ''
|
||||
let res = data.toString()
|
||||
return (
|
||||
'<td class="plaintext">' +
|
||||
(res === '' ? 'N/A' : res) +
|
||||
'</td>'
|
||||
)
|
||||
return '<td class="plaintext">' + (res === '' ? 'N/A' : res) + '</td>'
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user