Original commit: 0ff9e3f62b
This commit is contained in:
Wojciech Daniło 2021-03-05 16:22:19 +01:00 committed by GitHub
parent 3a04557ac5
commit 87a19bec3d
14 changed files with 536 additions and 692 deletions

View File

@ -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

View File

@ -1,4 +0,0 @@
trailingComma = "es5"
tabWidth = 4
semi = false
singleQuote = true

15
gui/.prettierrc.yaml Normal file
View 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"

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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')

View File

@ -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])
})

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
}