├── .editorconfig ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTES.md ├── README.md ├── angular.json ├── apps ├── .gitkeep ├── dev-server │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── data │ │ │ ├── 2d-histogram.ts │ │ │ ├── bar.ts │ │ │ ├── candlestick.ts │ │ │ ├── line-stream.ts │ │ │ ├── sankey.ts │ │ │ ├── scatter.ts │ │ │ └── table.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── web-e2e │ ├── .eslintrc.json │ ├── cypress.json │ ├── project.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ └── tsconfig.json └── web │ ├── .browserslistrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.module.ts │ │ ├── components │ │ │ ├── app │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.spec.ts │ │ │ │ └── app.component.ts │ │ │ ├── plot │ │ │ │ ├── plot.component.css │ │ │ │ ├── plot.component.html │ │ │ │ └── plot.component.ts │ │ │ ├── plots │ │ │ │ ├── plots.component.css │ │ │ │ ├── plots.component.html │ │ │ │ ├── plots.component.spec.ts │ │ │ │ └── plots.component.ts │ │ │ └── tutorial │ │ │ │ ├── tutorial.component.css │ │ │ │ ├── tutorial.component.html │ │ │ │ └── tutorial.component.ts │ │ └── services │ │ │ ├── plots.service.ts │ │ │ └── socket.service.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── github-light-32px.png │ │ ├── github-light-64px.png │ │ ├── nodeplotlib_64x64_transparent.png │ │ └── plotly.svg │ ├── custom-theme.scss │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.editor.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── decorate-angular-cli.js ├── img ├── animation-next.gif ├── animation.gif ├── nodeplotlib_128x128.png ├── nodeplotlib_256x256.png ├── nodeplotlib_512x512.png └── nodeplotlib_64x64.png ├── jest.config.ts ├── jest.preset.js ├── libs └── nodeplotlib │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ ├── interfaces │ │ ├── index.ts │ │ └── plot.ts │ │ ├── nodeplotlib.ts │ │ ├── server │ │ ├── plots │ │ │ ├── plots.gateway.ts │ │ │ └── plots.service.ts │ │ ├── server.module.ts │ │ └── services │ │ │ ├── bridge-service.spec.ts │ │ │ └── bridge.service.ts │ │ └── utils │ │ ├── get-port.spec.ts │ │ ├── get-port.ts │ │ ├── open-window.spec.ts │ │ └── open-window.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── demo │ ├── README.md │ ├── candlestick.js │ ├── scatter.js │ ├── scatter.ts │ └── stream.ts ├── generators │ └── .gitkeep ├── tsconfig.tools.json └── util │ └── copy-files.ts └── tsconfig.base.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | }, 23 | "plugins": [], 24 | "extends": [] 25 | }, 26 | { 27 | "files": ["*.ts", "*.tsx"], 28 | "extends": ["plugin:@nrwl/nx/typescript"], 29 | "rules": {}, 30 | "plugins": [] 31 | }, 32 | { 33 | "files": ["*.js", "*.jsx"], 34 | "extends": ["plugin:@nrwl/nx/javascript"], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - uses: nrwl/nx-set-shas@v2 18 | - run: npm ci 19 | - run: npx nx workspace-lint 20 | - run: npx nx format:check 21 | - run: npx nx run-many --target=lint --all 22 | - run: npx nx run-many --target=test --all 23 | - run: npx nx run dev-server:build 24 | - run: npm run build:prod 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.angular/cache 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "angular.ng-template", 4 | "nrwl.angular-console", 5 | "esbenp.prettier-vscode", 6 | "firsttris.vscode-jest-runner", 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at flemke.dev@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution guidelines 2 | 3 | Contributions are very welcome. Please stick to the [code of conduct](./CODE_OF_CONDUCT.md) and, if possible, follow the issue templates. 4 | 5 | 6 | ### Commit message guidelines 7 | 8 | Please use the following template for your commit messages. 9 | 10 | ``` 11 | type(scope): message 12 | 13 | message-body 14 | 15 | BREAKING CHANGE 16 | Closes #4746 17 | ``` 18 | 19 | The message body can be multiple lines without any empty lines in between. If there is a breaking change please add the `BREAKING CHANGE` note. 20 | If there is a related issue to this commit, please add a line containing `Closes/Fixes/Related to #ISSUENUMBER`. The type may be 21 | one of the following 22 | 23 | - feature (if new features are introduced) 24 | - fix (if a bug was fixed and no features are introduced) 25 | - package (for changes related to npm/travis configuration) 26 | - misc (anything that does not fit in the above) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Felix Lemke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | These are notes for the stream implementation of Nodeplotlib. 2 | 3 | ## General 4 | 5 | - A plot window (**apps/web**) tries to connect to the server via a realtime api (e.g. websockets). 6 | - The server recognizes the count of connected apps. 7 | - If the user executes the `plot` function several times, it will only open a window if there is no 8 | open connection to a **apps/web**. 9 | 10 | ## Server lifecycle 11 | 12 | - The server starts with the execution of the `plot` function if there is no active server running. 13 | - The server stops if all **apps/web** are disconnected (and there were connections before). 14 | 15 | ## The plot function 16 | 17 | - The plot function can either handle a `Plot` or an `Observable`. 18 | - It creates an `Observable` by using Rxjs' `of` observable constructor. 19 | - The plot streams are saved in a Plots Set. 20 | - If there is an active **apps/web** that listens to the server, it subscribes to all Plots in the Set. 21 | - It does not submit a whole "plots" object, but rather submits all plots one by one. The reason is 22 | realtime data, for which only the updated plot should be transmitted. 23 | - If all **apps/web** are disconnected, it should close the observable subscriptions of the plots and close 24 | the server as mentioned in the **server lifecycle** section. 25 | 26 | ## The stack function 27 | 28 | - Is the `stack` function really needed? Stack served the purpose that only one window opens which 29 | could display several plots. 30 | 31 | ## The clear function 32 | 33 | - The `clear` function is also probably not needed. Just close all windows and it should close the 34 | subscriptions to the plots and streams. 35 | 36 | ## Backlog 37 | 38 | - The user can remove plots from the frontend. If that happened it submits a message to the 39 | backend so that the subscription can be cancelled and the plot stream can be removed from the plots set. 40 | 41 | ## Frontend only 42 | 43 | - The user has the possibility to rearrange plots per drag and drop. 44 | - The user can resize the individual plot windows. 45 | 46 | ## Development 47 | 48 | To start the app for development purposes run 49 | 50 | ``` 51 | npm run build web -- --watch 52 | npm start dev-server 53 | ``` 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodePlotLib 2 | 3 | [![NodeJS CI](https://github.com/ngfelixl/nodeplotlib/workflows/Node.js%20CI/badge.svg)](https://github.com/ngfelixl/nodeplotlib/actions?query=workflow%3A%22Node.js+CI%22) 4 | [![npm](https://img.shields.io/npm/v/nodeplotlib?color=#00f800)](https://npmjs.com/package/nodeplotlib) 5 | [![npm](https://img.shields.io/npm/dt/nodeplotlib.svg)](https://npmjs.com/package/nodeplotlib) 6 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 7 | 8 | [![Animation (View on Github)](https://raw.githubusercontent.com/ngfelixl/nodeplotlib/master/img/animation-next.gif)](https://raw.githubusercontent.com/ngfelixl/nodeplotlib/master/img/animation-next.gif) 9 | 10 | This readme contains all the necessary information for the development. 11 | 12 | ## [Go to the user docs](./libs/nodeplotlib/README.md) 13 | 14 | ## Installation 15 | 16 | ### Npmjs 17 | 18 | ```sh 19 | npm install nodeplotlib 20 | # or 21 | yarn add nodeplotlib 22 | ``` 23 | 24 | ### Repository 25 | 26 | Create a fork of this repository. Then clone it 27 | 28 | ``` 29 | git clone https://github.com/{{USERNAME}}/nodeplotlib 30 | cd nodeplotlib 31 | npm i 32 | ``` 33 | 34 | ## Serving the app for development 35 | 36 | Serving in development mode prevents the app to open a new browser window on changes. 37 | You can open the app at the specified port, e.g. `http://localhost:4201`. 38 | 39 | ``` 40 | npx nx run web:build -- --watch 41 | 42 | NODEPLOTLIB_PORT=4201 npx nx run dev-server:serve 43 | // or on Windows cmd 44 | set "NODEPLOTLIB_PORT=4201" && npx nx run dev-server:serve 45 | // or on Windows Powershell 46 | $env:NODEPLOTLIB_PORT = "4201" ; npx nx run dev-server:serve 47 | ``` 48 | 49 | ## Build for production 50 | 51 | To build for production three steps are necessary. Build the web app, build 52 | the library, and finally copy the web app files to the libraries dist folder. 53 | All steps are bundled in the following script: 54 | 55 | ``` 56 | npm run build:prod 57 | ``` 58 | 59 | All dist files are located in **/dist/libs/nodeplotlib** 60 | 61 | ## Release Guide 62 | 63 | This is a note for maintainers only. There are several steps to follow before you 64 | can publish the new version to npm. 65 | 66 | 1. Bump the version number using semver. Use "rc" for release candidates as the _preid_, e.g. 67 | `1.0.0-rc1`. Update the version number in the root [package.json](./package.json) 68 | and in the libs [package.json](./libs/nodeplotlib/package.json). 69 | 2. If everything is committed and in place on "master" for non-release candidates, 70 | or on "release/..." for release candidates. Create a tag with that version number, 71 | e.g. `v1.0.0-rc1` or `v1.0.1`, and push it to the repository. 72 | 3. Run `npm run build:prod` 73 | 4. Navigate to **/dist/libs/nodeplotlib** 74 | 5. Run `npm pack` 75 | 6. Make sure to be logged in to npmjs. Then run 76 | - `npm publish {{filename}}.tgz` or 77 | - `npm publish {{filename}}.tgz --tag test` for release candidates. 78 | 7. Create a release on Github for non-release cadidate versions. 79 | 80 | ## Behind the scenes 81 | 82 | The lib launches a webserver and opens new tabs for every plot located at 83 | `http://localhost:{{PORT}}`, where `PORT` is a free port determined by the express 84 | server itself. At this address the Angular application will be served temporarily. 85 | The server and the app set up a connection via socket.io. This way a realtime 86 | transmission is possible. 87 | 88 | ## Contributing 89 | 90 | Contributions in all forms are welcome. 91 | 92 | ## Contributors 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/workspace-schema.json", 3 | "version": 2, 4 | "projects": { 5 | "dev-server": "apps/dev-server", 6 | "nodeplotlib": "libs/nodeplotlib", 7 | "web": "apps/web", 8 | "web-e2e": "apps/web-e2e" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/.gitkeep -------------------------------------------------------------------------------- /apps/dev-server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/dev-server/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'dev-server', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]s$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'js', 'html'], 15 | coverageDirectory: '../../coverage/apps/dev-server', 16 | }; 17 | -------------------------------------------------------------------------------- /apps/dev-server/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/dev-server/src", 4 | "projectType": "application", 5 | "architect": { 6 | "build": { 7 | "executor": "@nrwl/node:webpack", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/apps/dev-server", 11 | "main": "apps/dev-server/src/main.ts", 12 | "tsConfig": "apps/dev-server/tsconfig.app.json", 13 | "assets": [ 14 | "apps/dev-server/src/assets", 15 | { 16 | "glob": "**/*", 17 | "input": "dist/apps/web", 18 | "output": "web" 19 | } 20 | ] 21 | }, 22 | "configurations": { 23 | "production": { 24 | "optimization": true, 25 | "extractLicenses": true, 26 | "inspect": false, 27 | "fileReplacements": [ 28 | { 29 | "replace": "apps/dev-server/src/environments/environment.ts", 30 | "with": "apps/dev-server/src/environments/environment.prod.ts" 31 | } 32 | ] 33 | } 34 | } 35 | }, 36 | "serve": { 37 | "executor": "@nrwl/node:node", 38 | "options": { 39 | "buildTarget": "dev-server:build" 40 | } 41 | }, 42 | "lint": { 43 | "executor": "@nrwl/linter:eslint", 44 | "outputs": ["{options.outputFile}"], 45 | "options": { 46 | "lintFilePatterns": ["apps/dev-server/**/*.ts"] 47 | } 48 | }, 49 | "test": { 50 | "executor": "@nrwl/jest:jest", 51 | "outputs": ["coverage/apps/dev-server"], 52 | "options": { 53 | "jestConfig": "apps/dev-server/jest.config.ts", 54 | "passWithNoTests": true 55 | } 56 | } 57 | }, 58 | "implicitDependencies": ["web"] 59 | } 60 | -------------------------------------------------------------------------------- /apps/dev-server/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/dev-server/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/dev-server/src/data/2d-histogram.ts: -------------------------------------------------------------------------------- 1 | import { Layout, Plot } from '@npl/nodeplotlib'; 2 | 3 | function normal() { 4 | let x = 0, 5 | y = 0, 6 | rds; 7 | do { 8 | x = Math.random() * 2 - 1; 9 | y = Math.random() * 2 - 1; 10 | rds = x * x + y * y; 11 | } while (rds == 0 || rds > 1); 12 | const c = Math.sqrt((-2 * Math.log(rds)) / rds); // Box-Muller transform 13 | return x * c; // throw away extra sample y * c 14 | } 15 | 16 | const N = 2000; 17 | const a = -1; 18 | const b = 1.2; 19 | 20 | const step = (b - a) / (N - 1); 21 | const t = new Array(N), 22 | x = new Array(N), 23 | y = new Array(N); 24 | 25 | for (let i = 0; i < N; i++) { 26 | t[i] = a + step * i; 27 | x[i] = Math.pow(t[i], 3) + 0.3 * normal(); 28 | y[i] = Math.pow(t[i], 6) + 0.3 * normal(); 29 | } 30 | 31 | const trace1: Plot = { 32 | x: x, 33 | y: y, 34 | mode: 'markers', 35 | name: 'points', 36 | marker: { 37 | color: 'rgb(102,0,0)', 38 | size: 2, 39 | opacity: 0.4, 40 | }, 41 | type: 'scatter', 42 | }; 43 | const trace2: Plot = { 44 | x: x, 45 | y: y, 46 | name: 'density', 47 | ncontours: 20, 48 | colorscale: 'Hot', 49 | reversescale: true, 50 | showscale: false, 51 | type: 'histogram2dcontour', 52 | } as Plot; 53 | const trace3: Plot = { 54 | x: x, 55 | name: 'x density', 56 | marker: { color: 'rgb(102,0,0)' }, 57 | yaxis: 'y2', 58 | type: 'histogram', 59 | }; 60 | const trace4: Plot = { 61 | y: y, 62 | name: 'y density', 63 | marker: { color: 'rgb(102,0,0)' }, 64 | xaxis: 'x2', 65 | type: 'histogram', 66 | }; 67 | export const data = [trace1, trace2, trace3, trace4]; 68 | export const layout: Layout = { 69 | showlegend: false, 70 | autosize: false, 71 | width: 600, 72 | height: 550, 73 | margin: { t: 50 }, 74 | hovermode: 'closest', 75 | bargap: 0, 76 | xaxis: { 77 | domain: [0, 0.85], 78 | showgrid: false, 79 | zeroline: false, 80 | }, 81 | yaxis: { 82 | domain: [0, 0.85], 83 | showgrid: false, 84 | zeroline: false, 85 | }, 86 | xaxis2: { 87 | domain: [0.85, 1], 88 | showgrid: false, 89 | zeroline: false, 90 | }, 91 | yaxis2: { 92 | domain: [0.85, 1], 93 | showgrid: false, 94 | zeroline: false, 95 | }, 96 | }; 97 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/bar.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | 3 | export const data: Plot[] = [ 4 | { 5 | type: 'bar', 6 | x: [20, 14, 23], 7 | y: ['giraffes', 'orangutans', 'monkeys'], 8 | orientation: 'h', 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/candlestick.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | 3 | const trace1 = { 4 | x: [ 5 | '2017-01-04', 6 | '2017-01-05', 7 | '2017-01-06', 8 | '2017-01-09', 9 | '2017-01-10', 10 | '2017-01-11', 11 | '2017-01-12', 12 | '2017-01-13', 13 | '2017-01-17', 14 | '2017-01-18', 15 | '2017-01-19', 16 | '2017-01-20', 17 | '2017-01-23', 18 | '2017-01-24', 19 | '2017-01-25', 20 | '2017-01-26', 21 | '2017-01-27', 22 | '2017-01-30', 23 | '2017-01-31', 24 | '2017-02-01', 25 | '2017-02-02', 26 | '2017-02-03', 27 | '2017-02-06', 28 | '2017-02-07', 29 | '2017-02-08', 30 | '2017-02-09', 31 | '2017-02-10', 32 | '2017-02-13', 33 | '2017-02-14', 34 | '2017-02-15', 35 | ], 36 | close: [ 37 | 116.019997, 116.610001, 117.910004, 118.989998, 119.110001, 119.75, 119.25, 38 | 119.040001, 120, 119.989998, 119.779999, 120, 120.080002, 119.970001, 39 | 121.879997, 121.940002, 121.949997, 121.629997, 121.349998, 128.75, 40 | 128.529999, 129.080002, 130.289993, 131.529999, 132.039993, 132.419998, 41 | 132.119995, 133.289993, 135.020004, 135.509995, 42 | ], 43 | decreasing: { line: { color: '#7F7F7F' } }, 44 | high: [ 45 | 116.510002, 116.860001, 118.160004, 119.43, 119.379997, 119.93, 119.300003, 46 | 119.620003, 120.239998, 120.5, 120.089996, 120.449997, 120.809998, 47 | 120.099998, 122.099998, 122.440002, 122.349998, 121.629997, 121.389999, 48 | 130.490005, 129.389999, 129.190002, 130.5, 132.089996, 132.220001, 49 | 132.449997, 132.940002, 133.820007, 135.089996, 136.270004, 50 | ], 51 | increasing: { line: { color: '#17BECF' } }, 52 | line: { color: 'rgba(31,119,180,1)' }, 53 | low: [ 54 | 115.75, 115.809998, 116.470001, 117.940002, 118.300003, 118.599998, 55 | 118.209999, 118.809998, 118.220001, 119.709999, 119.370003, 119.730003, 56 | 119.769997, 119.5, 120.279999, 121.599998, 121.599998, 120.660004, 57 | 120.620003, 127.010002, 127.779999, 128.160004, 128.899994, 130.449997, 58 | 131.220001, 131.119995, 132.050003, 132.75, 133.25, 134.619995, 59 | ], 60 | open: [ 61 | 115.849998, 115.919998, 116.779999, 117.949997, 118.769997, 118.739998, 62 | 118.900002, 119.110001, 118.339996, 120, 119.400002, 120.449997, 120, 63 | 119.550003, 120.419998, 121.669998, 122.139999, 120.93, 121.150002, 64 | 127.029999, 127.980003, 128.309998, 129.130005, 130.539993, 131.350006, 65 | 131.649994, 132.460007, 133.080002, 133.470001, 135.520004, 66 | ], 67 | type: 'candlestick', 68 | xaxis: 'x', 69 | yaxis: 'y', 70 | } as Plot; 71 | 72 | export const data = [trace1]; 73 | 74 | export const layout = { 75 | dragmode: 'zoom', 76 | margin: { 77 | r: 10, 78 | t: 25, 79 | b: 40, 80 | l: 60, 81 | }, 82 | showlegend: false, 83 | xaxis: { 84 | autorange: true, 85 | domain: [0, 1], 86 | range: ['2017-01-03 12:00', '2017-02-15 12:00'], 87 | rangeslider: { range: ['2017-01-03 12:00', '2017-02-15 12:00'] }, 88 | title: 'Date', 89 | type: 'date', 90 | }, 91 | yaxis: { 92 | autorange: true, 93 | domain: [0, 1], 94 | range: [114.609999778, 137.410004222], 95 | type: 'linear', 96 | }, 97 | }; 98 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/line-stream.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | import { interval, Observable, map } from 'rxjs'; 3 | 4 | export const stream$: Observable = interval(100).pipe( 5 | map((index) => { 6 | const data: Plot = { 7 | x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 8 | y: Array(10) 9 | .fill(0) 10 | .map((_, i) => Math.sin(index + i)), 11 | type: 'scatter', 12 | }; 13 | return [data]; 14 | }) 15 | ); 16 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/sankey.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | 3 | export const data = [ 4 | { 5 | type: 'sankey', 6 | orientation: 'h', 7 | node: { 8 | pad: 15, 9 | thickness: 30, 10 | line: { 11 | color: 'black', 12 | width: 0.5, 13 | }, 14 | label: ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'], 15 | color: ['blue', 'blue', 'blue', 'blue', 'blue', 'blue'], 16 | }, 17 | 18 | link: { 19 | source: [0, 1, 0, 2, 3, 3], 20 | target: [2, 3, 3, 4, 4, 5], 21 | value: [8, 4, 2, 8, 4, 2], 22 | }, 23 | } as Plot, 24 | ]; 25 | 26 | export const layout = { 27 | title: 'Basic Sankey', 28 | font: { 29 | size: 10, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/scatter.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | 3 | const trace1: Plot = { 4 | x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 5 | y: [1, 2, 1, 2, 1, 2, 1, 2, 1, 2], 6 | type: 'scatter', 7 | }; 8 | export const data = [trace1]; 9 | export const layout = {}; 10 | -------------------------------------------------------------------------------- /apps/dev-server/src/data/table.ts: -------------------------------------------------------------------------------- 1 | import { Plot } from '@npl/nodeplotlib'; 2 | 3 | const values = [ 4 | ['Salaries', 'Office', 'Merchandise', 'Legal', 'TOTAL'], 5 | [1200000, 20000, 80000, 2000, 12120000], 6 | [1300000, 20000, 70000, 2000, 130902000], 7 | [1300000, 20000, 120000, 2000, 131222000], 8 | [1400000, 20000, 90000, 2000, 14102000], 9 | ]; 10 | 11 | const headerColor = 'grey'; 12 | const rowEvenColor = 'lightgrey'; 13 | const rowOddColor = 'white'; 14 | 15 | export const data = [ 16 | { 17 | type: 'table', 18 | header: { 19 | values: [ 20 | ['EXPENSES'], 21 | ['Q1'], 22 | ['Q2'], 23 | ['Q3'], 24 | ['Q4'], 25 | ], 26 | align: 'center', 27 | line: { width: 1, color: 'black' }, 28 | fill: { color: headerColor }, 29 | font: { family: 'Arial', size: 12, color: 'white' }, 30 | }, 31 | cells: { 32 | values: values, 33 | align: 'center', 34 | line: { color: 'black', width: 1 }, 35 | fill: { 36 | color: [ 37 | [rowOddColor, rowEvenColor, rowOddColor, rowEvenColor, rowOddColor], 38 | ], 39 | }, 40 | font: { family: 'Arial', size: 11, color: ['black'] }, 41 | }, 42 | } as Plot, 43 | ]; 44 | -------------------------------------------------------------------------------- /apps/dev-server/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/dev-server/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/dev-server/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import { plot } from '@npl/nodeplotlib'; 7 | import { 8 | data as histogram, 9 | layout as histogramLayout, 10 | } from './data/2d-histogram'; 11 | import { data as bar } from './data/bar'; 12 | import { stream$ } from './data/line-stream'; 13 | import { data as sankey, layout as sankeyLayout } from './data/sankey'; 14 | import { data as scatter, layout as scatterPlotLayout } from './data/scatter'; 15 | import { data as table } from './data/table'; 16 | 17 | plot(stream$); 18 | plot(scatter, scatterPlotLayout); 19 | plot(bar); 20 | plot(sankey, sankeyLayout); 21 | plot(histogram, histogramLayout); 22 | plot(table); 23 | -------------------------------------------------------------------------------- /apps/dev-server/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2015" 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/dev-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/dev-server/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/web-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["src/plugins/index.js"], 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": "off", 13 | "no-undef": "off" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/web-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/web-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/web-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/web-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/web-e2e/src", 4 | "projectType": "application", 5 | "architect": { 6 | "e2e": { 7 | "executor": "@nrwl/cypress:cypress", 8 | "options": { 9 | "cypressConfig": "apps/web-e2e/cypress.json", 10 | "devServerTarget": "web:serve:development", 11 | "tsConfig": "apps/web-e2e/tsconfig.json" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "web:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nrwl/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["apps/web-e2e/**/*.{js,ts}"] 24 | } 25 | } 26 | }, 27 | "tags": [], 28 | "implicitDependencies": ["web"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/web-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/web-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('web', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to web!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/web-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.js"], 14 | "angularCompilerOptions": { 15 | "strictInjectionParameters": true, 16 | "strictInputAccessModifiers": true, 17 | "strictTemplates": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "npl", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "npl", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/web/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'web', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | globals: { 7 | 'ts-jest': { 8 | tsconfig: '/tsconfig.spec.json', 9 | stringifyContentPathRegex: '\\.(html|svg)$', 10 | }, 11 | }, 12 | coverageDirectory: '../../coverage/apps/web', 13 | transform: { 14 | '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular', 15 | }, 16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 17 | snapshotSerializers: [ 18 | 'jest-preset-angular/build/serializers/no-ng-attributes', 19 | 'jest-preset-angular/build/serializers/ng-snapshot', 20 | 'jest-preset-angular/build/serializers/html-comment', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /apps/web/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "projectType": "application", 4 | "sourceRoot": "apps/web/src", 5 | "prefix": "npl", 6 | "targets": { 7 | "build": { 8 | "executor": "@angular-devkit/build-angular:browser", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/apps/web", 12 | "index": "apps/web/src/index.html", 13 | "main": "apps/web/src/main.ts", 14 | "polyfills": "apps/web/src/polyfills.ts", 15 | "tsConfig": "apps/web/tsconfig.app.json", 16 | "assets": ["apps/web/src/favicon.ico", "apps/web/src/assets"], 17 | "styles": ["apps/web/src/custom-theme.scss", "apps/web/src/styles.css"], 18 | "scripts": [] 19 | }, 20 | "configurations": { 21 | "production": { 22 | "budgets": [ 23 | { 24 | "type": "initial", 25 | "maximumWarning": "500kb", 26 | "maximumError": "1mb" 27 | }, 28 | { 29 | "type": "anyComponentStyle", 30 | "maximumWarning": "2kb", 31 | "maximumError": "4kb" 32 | } 33 | ], 34 | "fileReplacements": [ 35 | { 36 | "replace": "apps/web/src/environments/environment.ts", 37 | "with": "apps/web/src/environments/environment.prod.ts" 38 | } 39 | ], 40 | "outputHashing": "none" 41 | }, 42 | "development": { 43 | "buildOptimizer": false, 44 | "optimization": false, 45 | "vendorChunk": true, 46 | "extractLicenses": false, 47 | "sourceMap": true, 48 | "namedChunks": true 49 | } 50 | }, 51 | "defaultConfiguration": "production" 52 | }, 53 | "serve": { 54 | "executor": "@angular-devkit/build-angular:dev-server", 55 | "configurations": { 56 | "production": { 57 | "browserTarget": "web:build:production" 58 | }, 59 | "development": { 60 | "browserTarget": "web:build:development" 61 | } 62 | }, 63 | "defaultConfiguration": "development" 64 | }, 65 | "extract-i18n": { 66 | "executor": "@angular-devkit/build-angular:extract-i18n", 67 | "options": { 68 | "browserTarget": "web:build" 69 | } 70 | }, 71 | "lint": { 72 | "executor": "@nrwl/linter:eslint", 73 | "options": { 74 | "lintFilePatterns": ["apps/web/src/**/*.ts", "apps/web/src/**/*.html"] 75 | } 76 | }, 77 | "test": { 78 | "executor": "@nrwl/jest:jest", 79 | "outputs": ["coverage/apps/web"], 80 | "options": { 81 | "jestConfig": "apps/web/jest.config.ts", 82 | "passWithNoTests": true 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /apps/web/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { 3 | ActivatedRouteSnapshot, 4 | BaseRouteReuseStrategy, 5 | DetachedRouteHandle, 6 | Route, 7 | RouteReuseStrategy, 8 | RouterModule, 9 | } from '@angular/router'; 10 | import { PlotsComponent } from './components/plots/plots.component'; 11 | import { TutorialComponent } from './components/tutorial/tutorial.component'; 12 | 13 | const routes: Route[] = [ 14 | { path: 'plots', component: PlotsComponent }, 15 | { path: 'tutorial', component: TutorialComponent }, 16 | { path: '**', redirectTo: 'plots' }, 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [RouterModule.forRoot(routes)], 21 | exports: [RouterModule], 22 | }) 23 | export class AppRoutingModule {} 24 | 25 | export class AppRoutesReuseStrategy implements RouteReuseStrategy { 26 | private cache: { [key: string]: DetachedRouteHandle } = {}; 27 | 28 | shouldDetach(route: ActivatedRouteSnapshot): boolean { 29 | return true; 30 | } 31 | 32 | store(route: ActivatedRouteSnapshot, handler: DetachedRouteHandle): void { 33 | if (handler) { 34 | this.cache[this.getUrl(route)] = handler; 35 | } 36 | } 37 | 38 | shouldAttach(route: ActivatedRouteSnapshot): boolean { 39 | return !!this.cache[this.getUrl(route)]; 40 | } 41 | 42 | shouldReuseRoute( 43 | future: ActivatedRouteSnapshot, 44 | current: ActivatedRouteSnapshot 45 | ): boolean { 46 | return future.routeConfig === current.routeConfig; 47 | } 48 | 49 | retrieve(route: ActivatedRouteSnapshot) { 50 | if (!route.routeConfig || route.routeConfig.loadChildren) { 51 | return null; 52 | } 53 | return this.cache[this.getUrl(route)]; 54 | } 55 | 56 | private getUrl(route: ActivatedRouteSnapshot): string { 57 | return route.routeConfig?.path ?? ''; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/web/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { MatToolbarModule } from '@angular/material/toolbar'; 5 | import { BrowserModule } from '@angular/platform-browser'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { AppRoutesReuseStrategy, AppRoutingModule } from './app-routing.module'; 8 | import { AppComponent } from './components/app/app.component'; 9 | import { TutorialComponent } from './components/tutorial/tutorial.component'; 10 | import { MatSidenavModule } from '@angular/material/sidenav'; 11 | import { MatListModule } from '@angular/material/list'; 12 | import { PlotComponent } from './components/plot/plot.component'; 13 | import { PlotsComponent } from './components/plots/plots.component'; 14 | import { DragDropModule } from '@angular/cdk/drag-drop'; 15 | import { MatCardModule } from '@angular/material/card'; 16 | import { MatIconModule } from '@angular/material/icon'; 17 | import { MatTooltipModule } from '@angular/material/tooltip'; 18 | import { RouteReuseStrategy } from '@angular/router'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | AppComponent, 23 | TutorialComponent, 24 | PlotComponent, 25 | PlotsComponent, 26 | ], 27 | imports: [ 28 | AppRoutingModule, 29 | BrowserAnimationsModule, 30 | BrowserModule, 31 | MatButtonModule, 32 | MatToolbarModule, 33 | HttpClientModule, 34 | MatSidenavModule, 35 | MatListModule, 36 | DragDropModule, 37 | MatCardModule, 38 | MatIconModule, 39 | MatTooltipModule, 40 | ], 41 | providers: [ 42 | { provide: RouteReuseStrategy, useClass: AppRoutesReuseStrategy }, 43 | ], 44 | bootstrap: [AppComponent], 45 | }) 46 | export class AppModule {} 47 | -------------------------------------------------------------------------------- /apps/web/src/app/components/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: stretch; 10 | align-items: stretch; 11 | } 12 | 13 | main { 14 | flex: 1 1 0; 15 | display: flex; 16 | flex-direction: column; 17 | align-items: stretch; 18 | justify-content: stretch; 19 | } 20 | 21 | mat-toolbar { 22 | flex: 0 0 auto; 23 | justify-content: space-between; 24 | } 25 | 26 | .info-icons { 27 | display: flex; 28 | gap: 8px; 29 | } 30 | 31 | .info-icons img { 32 | width: 24px; 33 | height: 24px; 34 | } 35 | 36 | .green { 37 | color: #00ff2a; 38 | } 39 | -------------------------------------------------------------------------------- /apps/web/src/app/components/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 19 | 22 | 26 | 33 | 34 | 35 |
36 | 37 |
38 | -------------------------------------------------------------------------------- /apps/web/src/app/components/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { SocketService } from '../../services/socket.service'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [AppComponent], 12 | imports: [], 13 | providers: [{ provide: SocketService, useValue: {} }], 14 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 15 | }).compileComponents(); 16 | 17 | fixture = TestBed.createComponent(AppComponent); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(fixture).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /apps/web/src/app/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SocketService } from '../../services/socket.service'; 3 | 4 | @Component({ 5 | selector: 'npl-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'], 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export class AppComponent { 11 | constructor(private socketService: SocketService) {} 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plot/plot.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/app/components/plot/plot.component.css -------------------------------------------------------------------------------- /apps/web/src/app/components/plot/plot.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plot/plot.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | ChangeDetectionStrategy, 4 | Component, 5 | ElementRef, 6 | Input, 7 | OnChanges, 8 | SimpleChanges, 9 | ViewChild, 10 | } from '@angular/core'; 11 | import { PlotData } from '@npl/nodeplotlib'; 12 | 13 | // eslint-disable-next-line 14 | declare const Plotly: any; 15 | 16 | @Component({ 17 | selector: 'npl-plot', 18 | templateUrl: './plot.component.html', 19 | styleUrls: ['./plot.component.css'], 20 | changeDetection: ChangeDetectionStrategy.OnPush, 21 | }) 22 | export class PlotComponent implements AfterViewInit, OnChanges { 23 | @Input() plotData!: PlotData; 24 | @ViewChild('plotContainer', { static: false }) plotContainer!: ElementRef; 25 | 26 | ngAfterViewInit() { 27 | Plotly.newPlot( 28 | this.plotContainer.nativeElement, 29 | this.plotData.data, 30 | { ...(this.plotData.layout ?? {}), autosize: true }, 31 | { ...(this.plotData.config ?? {}), responsive: true } 32 | ); 33 | } 34 | 35 | ngOnChanges(simpleChanges: SimpleChanges) { 36 | if (simpleChanges.plotData) { 37 | Plotly.react( 38 | this.plotContainer.nativeElement, 39 | this.plotData.data, 40 | { ...(this.plotData.layout ?? {}), autosize: true }, 41 | { ...(this.plotData.config ?? {}), responsive: true } 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plots/plots.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(500px, 1fr)); 4 | grid-gap: 8px; 5 | margin: 12px; 6 | grid-template-rows: auto; 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plots/plots.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plots/plots.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { PlotData } from '@npl/nodeplotlib'; 5 | import { Subject } from 'rxjs'; 6 | import { PlotsService } from '../../services/plots.service'; 7 | import { PlotsComponent } from './plots.component'; 8 | 9 | const PLOTS: PlotData[] = [ 10 | { 11 | id: 1, 12 | data: [{ x: [1, 2, 3], y: [2, 3, 4] }], 13 | layout: { 14 | title: 'Test Plot 1', 15 | }, 16 | }, 17 | { 18 | id: 2, 19 | data: [{ x: [1, 2, 3], y: [2, 3, 4] }], 20 | layout: { 21 | title: 'Test Plot 2', 22 | }, 23 | }, 24 | ]; 25 | 26 | @Component({ 27 | selector: 'npl-plot', 28 | template: '', 29 | }) 30 | export class PlotComponent { 31 | @Input() plotData!: PlotData; 32 | } 33 | 34 | describe('PlotsComponent', () => { 35 | let component: PlotsComponent; 36 | let fixture: ComponentFixture; 37 | let plotsServiceMock: PlotsService; 38 | let plots$: Subject; 39 | 40 | beforeEach(async () => { 41 | plots$ = new Subject(); 42 | plotsServiceMock = { 43 | plots$, 44 | } as unknown as PlotsService; 45 | 46 | await TestBed.configureTestingModule({ 47 | declarations: [PlotsComponent, PlotComponent], 48 | providers: [{ provide: PlotsService, useValue: plotsServiceMock }], 49 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 50 | }).compileComponents(); 51 | }); 52 | 53 | beforeEach(() => { 54 | fixture = TestBed.createComponent(PlotsComponent); 55 | component = fixture.componentInstance; 56 | fixture.detectChanges(); 57 | }); 58 | 59 | it('should create', () => { 60 | expect(component).toBeTruthy(); 61 | }); 62 | 63 | it('should render a empty list if there are no emitted values', () => { 64 | const plots = fixture.debugElement.queryAll(By.directive(PlotComponent)); 65 | 66 | expect(plots.length).toBe(0); 67 | }); 68 | 69 | it('should render a plot if there is one element', () => { 70 | plots$.next([PLOTS[0]]); 71 | 72 | fixture.detectChanges(); 73 | 74 | const plots = fixture.debugElement.queryAll(By.directive(PlotComponent)); 75 | expect(plots.length).toBe(1); 76 | }); 77 | 78 | it('should render several plots', () => { 79 | plots$.next(PLOTS); 80 | 81 | fixture.detectChanges(); 82 | 83 | const plots = fixture.debugElement.queryAll(By.directive(PlotComponent)); 84 | expect(plots.length).toBe(2); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /apps/web/src/app/components/plots/plots.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { PlotData } from '@npl/nodeplotlib'; 3 | import { PlotsService } from '../../services/plots.service'; 4 | 5 | @Component({ 6 | selector: 'npl-plots', 7 | templateUrl: './plots.component.html', 8 | styleUrls: ['./plots.component.css'], 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | }) 11 | export class PlotsComponent { 12 | plots$ = this.plotsService.plots$; 13 | 14 | constructor(private plotsService: PlotsService) {} 15 | 16 | trackById(_: number, plot: PlotData) { 17 | return plot.id; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/app/components/tutorial/tutorial.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | margin: 18px; 4 | } 5 | -------------------------------------------------------------------------------- /apps/web/src/app/components/tutorial/tutorial.component.html: -------------------------------------------------------------------------------- 1 |

Tutorials

2 | 3 | The tutorials sections will be tackled once the application is done. It will 4 | contain the following topics: 5 | 6 |
    7 |
  • Basic plot commands
  • 8 |
  • The Plot and the Layout interfaces
  • 9 |
  • How to plot streams in realtime
  • 10 |
11 | -------------------------------------------------------------------------------- /apps/web/src/app/components/tutorial/tutorial.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'npl-tutorial', 5 | templateUrl: './tutorial.component.html', 6 | styleUrls: ['./tutorial.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | }) 9 | export class TutorialComponent {} 10 | -------------------------------------------------------------------------------- /apps/web/src/app/services/plots.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { PlotData } from '@npl/nodeplotlib'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { SocketService } from './socket.service'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class PlotsService { 9 | private plotDataMap$ = new BehaviorSubject>(new Map()); 10 | plots$ = this.plotDataMap$.pipe( 11 | map((plotDataMap) => Array.from(plotDataMap.values())) 12 | ); 13 | 14 | constructor(private socketService: SocketService) { 15 | this.socketService.listen('plotdata', (data) => { 16 | const plots = this.plotDataMap$.value; 17 | plots.set(data.id, data); 18 | this.plotDataMap$.next(plots); 19 | }); 20 | this.socketService.emit('readplots'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/src/app/services/socket.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy } from '@angular/core'; 2 | import { io, Socket } from 'socket.io-client'; 3 | import { environment } from '../../environments/environment'; 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class SocketService implements OnDestroy { 7 | private socket: Socket; 8 | 9 | constructor() { 10 | this.socket = io(environment.socketIoEndpoint, { transports: ['polling'] }); 11 | 12 | this.socket.on('connect', () => { 13 | console.log('[Nodeplotlib] connected'); 14 | }); 15 | 16 | this.socket.on('disconnect', () => { 17 | console.log('[Nodeplotlib] disconnected'); 18 | }); 19 | } 20 | 21 | listen(eventName: string, cb: (data: T) => void) { 22 | return this.socket.on(eventName, cb); 23 | } 24 | 25 | emit(eventName: string, data?: T) { 26 | this.socket.emit(eventName, data); 27 | } 28 | 29 | ngOnDestroy() { 30 | this.socket?.disconnect(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/web/src/assets/github-light-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/assets/github-light-32px.png -------------------------------------------------------------------------------- /apps/web/src/assets/github-light-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/assets/github-light-64px.png -------------------------------------------------------------------------------- /apps/web/src/assets/nodeplotlib_64x64_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/assets/nodeplotlib_64x64_transparent.png -------------------------------------------------------------------------------- /apps/web/src/assets/plotly.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/custom-theme.scss: -------------------------------------------------------------------------------- 1 | // Custom Theming for Angular Material 2 | // For more information: https://material.angular.io/guide/theming 3 | @use '@angular/material' as mat; 4 | // Plus imports for other components in your app. 5 | 6 | // Include the common styles for Angular Material. We include this here so that you only 7 | // have to load a single css file for Angular Material in your app. 8 | // Be sure that you only ever include this mixin once! 9 | @include mat.core(); 10 | 11 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 12 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 13 | // hue. Available color palettes: https://material.io/design/color/ 14 | $web-primary: mat.define-palette(mat.$blue-grey-palette, 900); 15 | $web-accent: mat.define-palette(mat.$light-green-palette, A200, A100, A400); 16 | 17 | // The warn palette is optional (defaults to red). 18 | $web-warn: mat.define-palette(mat.$red-palette); 19 | 20 | // Create the theme object. A theme consists of configurations for individual 21 | // theming systems such as "color" or "typography". 22 | $web-theme: mat.define-light-theme( 23 | ( 24 | color: ( 25 | primary: $web-primary, 26 | accent: $web-accent, 27 | warn: $web-warn, 28 | ), 29 | ) 30 | ); 31 | 32 | // Include theme styles for core and each component used in your app. 33 | // Alternatively, you can import and @include the theme mixins for each component 34 | // that you are using. 35 | @include mat.all-component-themes($web-theme); 36 | -------------------------------------------------------------------------------- /apps/web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | socketIoEndpoint: '', 4 | }; 5 | -------------------------------------------------------------------------------- /apps/web/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | socketIoEndpoint: '', 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /apps/web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/apps/web/src/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nodeplotlib 6 | 7 | 8 | 12 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /apps/web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/web/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | /*************************************************************************************************** 51 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /apps/web/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, 4 | body { 5 | height: 100%; 6 | } 7 | body { 8 | margin: 0; 9 | font-family: Roboto, 'Helvetica Neue', sans-serif; 10 | } 11 | 12 | .brand .mat-button-wrapper { 13 | display: flex; 14 | align-items: center; 15 | gap: 12px; 16 | font-size: 1.2em; 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().resetTestEnvironment(); 10 | getTestBed().initTestEnvironment( 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting(), 13 | { teardown: { destroyAfterEach: false } } 14 | ); 15 | -------------------------------------------------------------------------------- /apps/web/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./tsconfig.editor.json" 14 | } 15 | ], 16 | "compilerOptions": { 17 | "forceConsistentCasingInFileNames": true, 18 | "strict": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "target": "es2020" 22 | }, 23 | "angularCompilerOptions": { 24 | "strictInjectionParameters": true, 25 | "strictInputAccessModifiers": true, 26 | "strictTemplates": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/web/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /decorate-angular-cli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching 3 | * and faster execution of tasks. 4 | * 5 | * It does this by: 6 | * 7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. 8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI 9 | * - Updating the package.json postinstall script to give you control over this script 10 | * 11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. 12 | * Every command you run should work the same when using the Nx CLI, except faster. 13 | * 14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, 15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. 16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI. 17 | * 18 | * To opt out of this patch: 19 | * - Replace occurrences of nx with ng in your package.json 20 | * - Remove the script from your postinstall script in your package.json 21 | * - Delete and reinstall your node_modules 22 | */ 23 | 24 | const fs = require('fs'); 25 | const os = require('os'); 26 | const cp = require('child_process'); 27 | const isWindows = os.platform() === 'win32'; 28 | let output; 29 | try { 30 | output = require('@nrwl/workspace').output; 31 | } catch (e) { 32 | console.warn( 33 | 'Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.' 34 | ); 35 | process.exit(0); 36 | } 37 | 38 | /** 39 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still 40 | * invoke the Nx CLI and get the benefits of computation caching. 41 | */ 42 | function symlinkNgCLItoNxCLI() { 43 | try { 44 | const ngPath = './node_modules/.bin/ng'; 45 | const nxPath = './node_modules/.bin/nx'; 46 | if (isWindows) { 47 | /** 48 | * This is the most reliable way to create symlink-like behavior on Windows. 49 | * Such that it works in all shells and works with npx. 50 | */ 51 | ['', '.cmd', '.ps1'].forEach((ext) => { 52 | if (fs.existsSync(nxPath + ext)) 53 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); 54 | }); 55 | } else { 56 | // If unix-based, symlink 57 | cp.execSync(`ln -sf ./nx ${ngPath}`); 58 | } 59 | } catch (e) { 60 | output.error({ 61 | title: 62 | 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + 63 | e.message, 64 | }); 65 | throw e; 66 | } 67 | } 68 | 69 | try { 70 | symlinkNgCLItoNxCLI(); 71 | require('nx/src/adapter/decorate-cli').decorateCli(); 72 | output.log({ 73 | title: 'Angular CLI has been decorated to enable computation caching.', 74 | }); 75 | } catch (e) { 76 | output.error({ 77 | title: 'Decoration of the Angular CLI did not complete successfully', 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /img/animation-next.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/animation-next.gif -------------------------------------------------------------------------------- /img/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/animation.gif -------------------------------------------------------------------------------- /img/nodeplotlib_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/nodeplotlib_128x128.png -------------------------------------------------------------------------------- /img/nodeplotlib_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/nodeplotlib_256x256.png -------------------------------------------------------------------------------- /img/nodeplotlib_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/nodeplotlib_512x512.png -------------------------------------------------------------------------------- /img/nodeplotlib_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/img/nodeplotlib_64x64.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nrwl/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /libs/nodeplotlib/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /libs/nodeplotlib/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/nodeplotlib/README.md: -------------------------------------------------------------------------------- 1 | # NodePlotLib 2 | 3 | [![NodeJS CI](https://github.com/ngfelixl/nodeplotlib/workflows/Node.js%20CI/badge.svg)](https://github.com/ngfelixl/nodeplotlib/actions?query=workflow%3A%22Node.js+CI%22) 4 | [![npm](https://img.shields.io/npm/v/nodeplotlib?color=#00f800)](https://npmjs.com/package/nodeplotlib) 5 | [![npm](https://img.shields.io/npm/dt/nodeplotlib.svg)](https://npmjs.com/package/nodeplotlib) 6 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 7 | 8 | [![Animation (View on Github)](https://raw.githubusercontent.com/ngfelixl/nodeplotlib/master/img/animation-next.gif)](https://raw.githubusercontent.com/ngfelixl/nodeplotlib/master/img/animation-next.gif) 9 | 10 | Library to create plots directly in TypeScript or JavaScript in NodeJS on top of [plotly.js](https://plot.ly/javascript/) 11 | without any front-end preparations. Inspired by matplotlib. 12 | 13 | ## Installation 14 | 15 | ```sh 16 | npm install nodeplotlib 17 | # or 18 | yarn add nodeplotlib 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### Creating a simple plot 24 | 25 | Use with TypeScript/JavaScript: 26 | 27 | ```typescript 28 | import { plot, Plot } from 'nodeplotlib'; 29 | 30 | const data: Plot[] = [ 31 | { 32 | x: [1, 3, 4, 5], 33 | y: [3, 12, 1, 4], 34 | type: 'scatter', 35 | }, 36 | ]; 37 | 38 | plot(data); 39 | ``` 40 | 41 | ### Creating a stream that plots data in realtime 42 | 43 | NodePlotLib makes use of the popular [RxJS](https://rxjs.dev) library, 44 | which provides functionality for streams, stream creator functions (e.g. from interval or from event), 45 | and tons of operators to modify your stream. 46 | 47 | In this example we create a stream based on an interval which triggers every 100ms. Then we modify 48 | the output of the interval (which is just a counter) to be an actual `Plot` using RxJS' `map` operator. 49 | The output will be a `sin` function. 50 | 51 | ```typescript 52 | import { plot, Plot } from 'nodeplotlib'; 53 | import { interval, map } from 'rxjs'; 54 | 55 | const stream$: Observable = interval(100).pipe( 56 | map(createSinusPlotFromNumber) 57 | ); 58 | 59 | function createSinusPlotFromNumber(num: number): Plot[] { 60 | const data: Plot[] = [ 61 | { 62 | x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 63 | y: Array(10) 64 | .fill(0) 65 | .map((_, i) => Math.sin(num + i)), 66 | type: 'scatter', 67 | }, 68 | ]; 69 | return data; 70 | } 71 | ``` 72 | 73 | As you can see, providing a function for a dynamic plot seems to be a good idea. 74 | The functions content looks almost the same as the "non-stream" version. Simple as 75 | that, you can just put the created Observable as an argument in the plot function: 76 | 77 | ```typescript 78 | plot(stream$); 79 | ``` 80 | 81 | ### API overview 82 | 83 | There are three exports. The `plot` function and types for the Plot and for the Layout. 84 | 85 | ```typescript 86 | import { plot, Plot, Layout, Config } from 'nodeplotlib'; 87 | ``` 88 | 89 | The `plot` function has the following structure 90 | 91 | ```typescript 92 | function plot( 93 | data: Plot[] | Observable, 94 | layout?: Layout, 95 | config?: Config 96 | ): void; 97 | ``` 98 | 99 | It does not return a Subscription for the Observables because you just need to close 100 | the listening browser window to unsubscribe from all Obserables. 101 | 102 | ## Examples 103 | 104 | In this section there are some examples to getting started. See the full plotly 105 | [cheatsheet](https://images.plot.ly/plotly-documentation/images/plotly_js_cheat_sheet.pdf?_ga=2.2676214.711017137.1550402185-1513144731.1549064935). 106 | 107 | #### Line Plots 108 | 109 | ```typescript 110 | const trace1: Plot = { x: [1, 2], y: [1, 2], type: 'scatter' }; 111 | const trace2: Plot = { x: [3, 4], y: [9, 16], type: 'scatter' }; 112 | plot([trace1, trace2]); 113 | ``` 114 | 115 | #### Bar Charts 116 | 117 | ```typescript 118 | const trace: Plot = { x: [1, 2], y: [1, 2], type: 'bar' }; 119 | plot([trace]); 120 | ``` 121 | 122 | #### 3D Line Plots 123 | 124 | ```typescript 125 | const trace: Plot = { 126 | x: [9, 8, 5, 1], 127 | y: [1, 2, 4, 8], 128 | z: [11, 8, 15, 3], 129 | type: 'scatter3d', 130 | }; 131 | plot([trace]); 132 | ``` 133 | 134 | #### 3D Surface Plots 135 | 136 | ```typescript 137 | const trace: Plot = { 138 | colorscale: 'Viridis', 139 | z: [ 140 | [3, 5, 7, 9], 141 | [21, 13, 8, 5], 142 | ], 143 | }; 144 | plot([trace]); 145 | ``` 146 | 147 | #### Radial Plots 148 | 149 | In order to style the plot, one is able to pass in the `layout` parameter, which internally 150 | is typeof `Partial` from plotly's `Layout`. See the full layout documentation 151 | [here](https://plot.ly/javascript/#layout-options). 152 | 153 | With this parameter one is able to define styles like _title_, _axis labels_, 154 | _subplots_ and many more. 155 | 156 | ```typescript 157 | const data: Plot[] = [ 158 | { 159 | type: 'scatterpolar', 160 | r: [1.5, 10, 39, 31, 15, 1.5], 161 | theta: ['A', 'B', 'C', 'D', 'E', 'A'], 162 | fill: 'toself', 163 | name: 'Group B', 164 | }, 165 | ]; 166 | 167 | const layout: Layout = { 168 | polar: { 169 | radialaxis: { 170 | visible: true, 171 | range: [0, 50], 172 | }, 173 | }, 174 | }; 175 | 176 | plot(data, layout); 177 | ``` 178 | 179 | ## Plot types 180 | 181 | | Simple charts | Advanced charts | 3D Plots | 182 | | --------------- | ---------------- | -------- | 183 | | Scatter | 2d density plots | Scatter | 184 | | Line | Histograms | Surface | 185 | | Bar | Box-plots | Lines | 186 | | Pie charts | Contour plots | | 187 | | Sankey diagrams | Heatmaps | | 188 | | Tables | Radar charts | | 189 | 190 | ## Contributing 191 | 192 | Contributions in all forms are welcome. 193 | 194 | ## Developers guide 195 | 196 | You can find the developers guide in the repositories root 197 | [README.md](https://github.com/ngfelixl/nodeplotlib). 198 | 199 | ## Contributors 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /libs/nodeplotlib/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'nodeplotlib', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]sx?$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../coverage/libs/nodeplotlib', 16 | }; 17 | -------------------------------------------------------------------------------- /libs/nodeplotlib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeplotlib", 3 | "version": "1.1.3", 4 | "description": "NodeJS frontend-less plotting lib using plotly.js inspired by matplotlib", 5 | "author": "Felix Lemke (https://felixlemke.dev)", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/ngfelixl/nodeplotlib.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/ngfelixl/nodeplotlib/issues" 13 | }, 14 | "homepage": "https://github.com/ngfelixl/nodeplotlib#readme", 15 | "keywords": [ 16 | "plot", 17 | "nodejs", 18 | "plotlyjs", 19 | "science", 20 | "easy-to-use", 21 | "statistics", 22 | "browser", 23 | "localhost" 24 | ], 25 | "dependencies": { 26 | "@types/plotly.js": "^1.54.17", 27 | "@nestjs/platform-express": "^9.0.0", 28 | "@nestjs/platform-socket.io": "^9.0.0" 29 | }, 30 | "peerDependencies": {} 31 | } 32 | -------------------------------------------------------------------------------- /libs/nodeplotlib/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "root": "libs/nodeplotlib", 4 | "sourceRoot": "libs/nodeplotlib/src", 5 | "projectType": "library", 6 | "architect": { 7 | "build": { 8 | "builder": "@nrwl/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "main": "libs/nodeplotlib/src/index.ts", 12 | "outputPath": "dist/libs/nodeplotlib", 13 | "tsConfig": "libs/nodeplotlib/tsconfig.lib.json", 14 | "generatePackageJson": true, 15 | "extractLicenses": true 16 | } 17 | }, 18 | "lint": { 19 | "builder": "@nrwl/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["libs/nodeplotlib/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "builder": "@nrwl/jest:jest", 27 | "outputs": ["coverage/libs/nodeplotlib"], 28 | "options": { 29 | "jestConfig": "libs/nodeplotlib/jest.config.ts", 30 | "passWithNoTests": true 31 | } 32 | } 33 | }, 34 | "tags": [] 35 | } 36 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/index.ts: -------------------------------------------------------------------------------- 1 | // Exports required for the actual nodeplotlib 2 | export { Plot, Layout, Config } from './lib/interfaces/plot'; 3 | export { plot } from './lib/nodeplotlib'; 4 | 5 | // Exports required for the web app 6 | export { PlotData } from './lib/interfaces/plot'; 7 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { Layout, Plot, PlotData, PlotDataStream } from './plot'; 2 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/interfaces/plot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Layout as PlotlyLayout, 3 | PlotData as PlotlyPlotData, 4 | Config as PlotlyConfig, 5 | } from 'plotly.js'; 6 | import { Observable } from 'rxjs'; 7 | 8 | export type Plot = Partial; 9 | export type Layout = Partial; 10 | export type Config = Partial; 11 | 12 | export interface PlotDataStream { 13 | id: number; 14 | data: Observable; 15 | layout: Observable; 16 | config: Config | undefined; 17 | } 18 | 19 | export interface PlotData { 20 | id: number; 21 | data: Plot[]; 22 | layout?: Layout; 23 | config?: Config; 24 | } 25 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/nodeplotlib.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { Layout, Plot, PlotDataStream } from './interfaces'; 4 | import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; 5 | import { PlotsService } from './server/plots/plots.service'; 6 | import { ServerModule } from './server/server.module'; 7 | import { BridgeService } from './server/services/bridge.service'; 8 | import { getPort } from './utils/get-port'; 9 | import { Config } from './interfaces/plot'; 10 | let app: INestApplication | null = null; 11 | let plotsService: PlotsService; 12 | let bridgeService: BridgeService; 13 | 14 | // This variable is used to determine if the nestjs app is running 15 | // or starting. Because it is "async" and the plot function is not, 16 | // we need to make sure that we do not bootstrap the app twice in the 17 | // same macro-task. 18 | let appRuns = false; 19 | let shutdownSubscription: Subscription; 20 | const plotsBuffer$ = new BehaviorSubject[]>([]); 21 | const port = getPort(); 22 | 23 | /** 24 | * Plots the given data with the given layout. This function 25 | * starts a server if one is not already running. 26 | * @param data 27 | * @param layout 28 | * @param cb 29 | */ 30 | export function plot( 31 | data: Plot[] | Observable, 32 | layout?: Layout, 33 | config?: Config 34 | ) { 35 | bootstrap(port); 36 | const bufferedPlots = plotsBuffer$.value; 37 | 38 | const streamData$: Observable = 39 | data instanceof Observable ? data : of(data); 40 | plotsBuffer$.next([ 41 | ...bufferedPlots, 42 | { data: streamData$, layout: of(layout), config }, 43 | ]); 44 | } 45 | 46 | async function bootstrap(port: number) { 47 | if (appRuns) { 48 | console.log('[Nodeplotlib] App is already up and running'); 49 | return; 50 | } 51 | appRuns = true; 52 | app = await NestFactory.create(ServerModule); 53 | plotsService = app.get(PlotsService); 54 | bridgeService = app.get(BridgeService); 55 | await app.listen(port); 56 | 57 | const actualPort = app.getHttpServer().address().port; 58 | bridgeService.setPort(actualPort); 59 | plotsService.setBuffer(plotsBuffer$); 60 | console.log( 61 | '[Nodeplotlib] Server running at', 62 | `http://localhost:${actualPort}` 63 | ); 64 | 65 | shutdownSubscription = bridgeService.shutdown$.subscribe(shutdown); 66 | } 67 | 68 | async function shutdown() { 69 | console.log('[Nodeplotlib] Server shutting down'); 70 | shutdownSubscription?.unsubscribe(); 71 | appRuns = false; 72 | 73 | if (app) { 74 | await app.close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/server/plots/plots.gateway.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OnGatewayConnection, 3 | OnGatewayDisconnect, 4 | SubscribeMessage, 5 | WebSocketGateway, 6 | } from '@nestjs/websockets'; 7 | import { PlotData } from '../../interfaces'; 8 | import { combineLatest, merge, Observable } from 'rxjs'; 9 | import { map, share, switchMap } from 'rxjs/operators'; 10 | import { BridgeService } from '../services/bridge.service'; 11 | import { PlotsService } from './plots.service'; 12 | 13 | @WebSocketGateway({ transports: ['polling'] }) 14 | export class PlotsGateway implements OnGatewayConnection, OnGatewayDisconnect { 15 | private clientMap = new Map(); 16 | private plotDataStream$: Observable; 17 | 18 | constructor( 19 | private bridgeService: BridgeService, 20 | private plotsService: PlotsService 21 | ) { 22 | this.plotDataStream$ = this.plotsService.plotIds$.pipe( 23 | switchMap((plotIds) => 24 | merge( 25 | ...Array.from(plotIds).map((id) => { 26 | const plotDataStream = this.plotsService.plotEntities.get(id); 27 | if (!plotDataStream) { 28 | return new Observable(); 29 | } 30 | return combineLatest([ 31 | plotDataStream.data, 32 | plotDataStream.layout, 33 | ]).pipe( 34 | map(([data, layout]) => ({ 35 | id, 36 | data, 37 | layout, 38 | config: plotDataStream.config, 39 | })) 40 | ); 41 | }) 42 | ) 43 | ), 44 | share() 45 | ); 46 | } 47 | 48 | @SubscribeMessage('readplots') 49 | handleEvent() { 50 | return this.plotDataStream$.pipe( 51 | map((plotData) => ({ 52 | event: 'plotdata', 53 | data: plotData, 54 | })) 55 | ); 56 | } 57 | 58 | handleConnection(client: WebSocket) { 59 | console.log('[Nodeplotlib] client connected'); 60 | this.clientMap.set(client, Date.now()); 61 | } 62 | 63 | handleDisconnect(client: WebSocket) { 64 | console.log('[Nodeplotlib] client disconnected'); 65 | this.clientMap.delete(client); 66 | 67 | if (this.clientMap.size === 0) { 68 | this.bridgeService.shutdown$.next(null); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/server/plots/plots.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PlotDataStream } from '../../interfaces'; 3 | import { BehaviorSubject, Observable, Subscription } from 'rxjs'; 4 | import { filter } from 'rxjs/operators'; 5 | 6 | @Injectable() 7 | export class PlotsService { 8 | plotEntities = new Map(); 9 | plotIds$ = new BehaviorSubject>(new Set()); 10 | private currentPlotId = 0; 11 | private bufferSubscription?: Subscription; 12 | 13 | setBuffer(buffer$: Observable[]>) { 14 | this.bufferSubscription?.unsubscribe(); 15 | this.bufferSubscription = buffer$ 16 | .pipe(filter((buffer) => buffer.length > 0)) 17 | .subscribe((buffer) => this.readBuffer(buffer)); 18 | } 19 | 20 | addPlot(plotData: Omit) { 21 | const plot: PlotDataStream = { 22 | id: this.currentPlotId++, 23 | data: plotData.data, 24 | layout: plotData.layout, 25 | config: plotData.config, 26 | }; 27 | 28 | this.plotEntities.set(plot.id, plot); 29 | const plotIds = this.plotIds$.value; 30 | plotIds.add(plot.id); 31 | this.plotIds$.next(plotIds); 32 | } 33 | 34 | /** 35 | * Function gets executed on the main process and makes the service read 36 | * the buffered plot data. 37 | * @param buffer 38 | */ 39 | readBuffer(buffer: Omit[]) { 40 | for (const plot of buffer) { 41 | this.addPlot(plot); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/server/server.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ServeStaticModule } from '@nestjs/serve-static'; 3 | import { join } from 'path'; 4 | import { PlotsGateway } from './plots/plots.gateway'; 5 | import { PlotsService } from './plots/plots.service'; 6 | import { BridgeService } from './services/bridge.service'; 7 | 8 | @Module({ 9 | imports: [ 10 | ServeStaticModule.forRoot({ 11 | rootPath: join(__dirname, 'web'), 12 | }), 13 | ], 14 | providers: [PlotsGateway, PlotsService, BridgeService], 15 | }) 16 | export class ServerModule {} 17 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/server/services/bridge-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { BridgeService } from './bridge.service'; 2 | import { openWindow } from '../../utils/open-window'; 3 | jest.mock('../../utils/open-window'); 4 | 5 | describe('BridgeService', () => { 6 | it('should emit a shutdown$ event if the shutdown function is called', (done) => { 7 | const bridgeService = new BridgeService(); 8 | bridgeService.shutdown$.subscribe(() => { 9 | done(); 10 | }); 11 | bridgeService.shutdown(); 12 | }); 13 | 14 | it('call the openWindow function with the url if a port is set', () => { 15 | const bridgeService = new BridgeService(); 16 | bridgeService.setPort(1234); 17 | expect(openWindow).toHaveBeenCalledWith(`http://localhost:1234`); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/server/services/bridge.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Subject } from 'rxjs'; 3 | import { openWindow } from '../../utils/open-window'; 4 | 5 | /** 6 | * The BridgeService is used to build a bridge between the 7 | * NestJS server and the outside of the NestJS server. 8 | * The two purposes are to get the port of the express server 9 | * inside of the NestJS server and to provide a shutdown$ 10 | * steam so that the NestJS server can trigger a full shutdown. 11 | * 12 | * @see nodeplotlib.ts 13 | */ 14 | @Injectable() 15 | export class BridgeService { 16 | shutdown$ = new Subject(); 17 | port$ = new Subject(); 18 | 19 | constructor() { 20 | this.port$.subscribe((port) => { 21 | openWindow(`http://localhost:${port}`); 22 | }); 23 | } 24 | 25 | setPort(port: number) { 26 | this.port$.next(port); 27 | } 28 | 29 | shutdown() { 30 | this.shutdown$.next(null); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/utils/get-port.spec.ts: -------------------------------------------------------------------------------- 1 | import { getPort } from './get-port'; 2 | 3 | describe('getPort', () => { 4 | it('should return 0 if NODEPLOTLIB_PORT is undefined', () => { 5 | process.env.NODEPLOTLIB_PORT = undefined; 6 | expect(getPort()).toBe(0); 7 | }); 8 | 9 | it('should return 0 if NODEPLOTLIB_PORT is null', () => { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | process.env.NODEPLOTLIB_PORT = null as any; 12 | expect(getPort()).toBe(0); 13 | }); 14 | 15 | it('should return 0 if NODEPLOTLIB_PORT is empty string', () => { 16 | process.env.NODEPLOTLIB_PORT = ''; 17 | expect(getPort()).toBe(0); 18 | }); 19 | 20 | it('should return 0 if NODEPLOTLIB_PORT is 0', () => { 21 | process.env.NODEPLOTLIB_PORT = '0'; 22 | expect(getPort()).toBe(0); 23 | }); 24 | 25 | it('should return the number if NODEPLOTLIB_PORT is a number and not 0', () => { 26 | process.env.NODEPLOTLIB_PORT = '123'; 27 | expect(getPort()).toBe(123); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/utils/get-port.ts: -------------------------------------------------------------------------------- 1 | export function getPort(): number { 2 | const portAsString = process.env.NODEPLOTLIB_PORT; 3 | const port = Number(portAsString); 4 | if (isNaN(port)) { 5 | return 0; 6 | } 7 | return port; 8 | } 9 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/utils/open-window.spec.ts: -------------------------------------------------------------------------------- 1 | import { openWindow } from './open-window'; 2 | import { exec } from 'child_process'; 3 | import { type } from 'os'; 4 | jest.mock('child_process'); 5 | jest.mock('os'); 6 | 7 | describe('openWindow', () => { 8 | let execMock: jest.MockedFunction; 9 | let typeMock: jest.MockedFunction; 10 | 11 | beforeEach(() => { 12 | execMock = exec as jest.MockedFunction; 13 | typeMock = type as jest.MockedFunction; 14 | }); 15 | 16 | afterEach(() => { 17 | delete process.env.NODEPLOTLIB_PORT; 18 | }); 19 | 20 | it('should not call the exec function if NODEPLOTLIB_PORT is set', () => { 21 | process.env.NODEPLOTLIB_PORT = '123'; 22 | openWindow('location'); 23 | expect(execMock).not.toBeCalled(); 24 | }); 25 | 26 | it('should not call the exec function if NODEPLOTLIB_PORT is set to ""', () => { 27 | process.env.NODEPLOTLIB_PORT = ''; 28 | openWindow('location'); 29 | expect(execMock).not.toBeCalled(); 30 | }); 31 | 32 | it('should call the exec function for linux correctly', () => { 33 | typeMock.mockImplementation(() => 'Linux'); 34 | openWindow('location'); 35 | expect(execMock).toBeCalledWith('xdg-open location'); 36 | }); 37 | 38 | it('should call the exec function for windows correctly', () => { 39 | typeMock.mockImplementation(() => 'Windows_NT'); 40 | openWindow('location'); 41 | expect(execMock).toBeCalledWith('start location'); 42 | }); 43 | 44 | it('should call the exec function for mac correctly', () => { 45 | typeMock.mockImplementation(() => 'Darwin'); 46 | openWindow('location'); 47 | expect(execMock).toBeCalledWith('open location'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /libs/nodeplotlib/src/lib/utils/open-window.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { type } from 'os'; 3 | 4 | export function openWindow(location: string) { 5 | if (process.env.NODEPLOTLIB_PORT) { 6 | return; 7 | } 8 | 9 | switch (type()) { 10 | case 'Linux': 11 | exec(`xdg-open ${location}`); 12 | break; 13 | case 'Darwin': 14 | exec(`open ${location}`); 15 | break; 16 | case 'Windows_NT': 17 | exec(`start ${location}`); 18 | break; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libs/nodeplotlib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/nodeplotlib/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /libs/nodeplotlib/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "npmScope": "npl", 4 | "affected": { 5 | "defaultBase": "main" 6 | }, 7 | "implicitDependencies": { 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | ".eslintrc.json": "*" 13 | }, 14 | "tasksRunnerOptions": { 15 | "default": { 16 | "runner": "nx/tasks-runners/default", 17 | "options": { 18 | "cacheableOperations": ["build", "lint", "test", "e2e"] 19 | } 20 | } 21 | }, 22 | "targetDefaults": { 23 | "build": { 24 | "dependsOn": ["^build"] 25 | } 26 | }, 27 | "generators": { 28 | "@nrwl/angular:application": { 29 | "style": "css", 30 | "linter": "eslint", 31 | "unitTestRunner": "jest", 32 | "e2eTestRunner": "cypress" 33 | }, 34 | "@nrwl/angular:library": { 35 | "linter": "eslint", 36 | "unitTestRunner": "jest" 37 | }, 38 | "@nrwl/angular:component": { 39 | "style": "css" 40 | } 41 | }, 42 | "defaultProject": "web" 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeplotlib", 3 | "version": "1.1.3", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "nx", 7 | "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2020 browser module main", 8 | "format:write": "nx format:write", 9 | "build": "nx build", 10 | "build:prod": "nx run web:build && nx run nodeplotlib:build && ts-node ./tools/util/copy-files.ts", 11 | "demo": "node ./tools/demo/candlestick.js" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "14.1.1", 16 | "@angular/cdk": "14.1.1", 17 | "@angular/common": "14.1.1", 18 | "@angular/compiler": "14.1.1", 19 | "@angular/core": "14.1.1", 20 | "@angular/forms": "14.1.1", 21 | "@angular/material": "14.1.1", 22 | "@angular/platform-browser": "14.1.1", 23 | "@angular/platform-browser-dynamic": "14.1.1", 24 | "@angular/router": "14.1.1", 25 | "@nestjs/common": "9.0.8", 26 | "@nestjs/core": "9.0.8", 27 | "@nestjs/microservices": "9.0.8", 28 | "@nestjs/platform-express": "9.0.8", 29 | "@nestjs/platform-socket.io": "9.0.8", 30 | "@nestjs/serve-static": "3.0.0", 31 | "@nestjs/websockets": "9.0.8", 32 | "@ngrx/effects": "~14.0.1", 33 | "@ngrx/entity": "~14.0.1", 34 | "@ngrx/store": "~14.0.1", 35 | "@nrwl/angular": "14.5.4", 36 | "@types/plotly.js": "^1.54.17", 37 | "express": "^4.17.1", 38 | "reflect-metadata": "^0.1.13", 39 | "rxjs": "^7.4.0", 40 | "socket.io-client": "^4.4.0", 41 | "tslib": "^2.0.0", 42 | "zone.js": "~0.11.4" 43 | }, 44 | "devDependencies": { 45 | "@angular-devkit/build-angular": "14.1.1", 46 | "@angular-eslint/eslint-plugin": "~14.0.0", 47 | "@angular-eslint/eslint-plugin-template": "~14.0.0", 48 | "@angular-eslint/template-parser": "~14.0.0", 49 | "@angular/cli": "~14.1.0", 50 | "@angular/compiler-cli": "14.1.1", 51 | "@angular/language-service": "14.1.1", 52 | "@nestjs/schematics": "9.0.1", 53 | "@nestjs/testing": "9.0.8", 54 | "@ngrx/eslint-plugin": "^14.0.1", 55 | "@nrwl/cli": "14.5.4", 56 | "@nrwl/cypress": "14.5.4", 57 | "@nrwl/eslint-plugin-nx": "14.5.4", 58 | "@nrwl/jest": "14.5.4", 59 | "@nrwl/linter": "14.5.4", 60 | "@nrwl/nest": "14.5.4", 61 | "@nrwl/node": "14.5.4", 62 | "@nrwl/workspace": "14.5.4", 63 | "@types/express": "^4.17.13", 64 | "@types/jest": "27.4.1", 65 | "@types/node": "16.11.7", 66 | "@types/request": "^2.48.7", 67 | "@typescript-eslint/eslint-plugin": "^5.29.0", 68 | "@typescript-eslint/parser": "^5.29.0", 69 | "copy-webpack-plugin": "^6.3.2", 70 | "cypress": "^9.1.0", 71 | "eslint": "~8.15.0", 72 | "eslint-config-prettier": "8.1.0", 73 | "eslint-plugin-cypress": "^2.10.3", 74 | "fs-extra": "^10.0.0", 75 | "jest": "27.5.1", 76 | "jest-preset-angular": "~11.1.2", 77 | "nx": "14.5.4", 78 | "plotly.js-dist": "^2.5.1", 79 | "prettier": "^2.6.2", 80 | "request": "^2.88.2", 81 | "ts-jest": "27.1.4", 82 | "ts-node": "~10.8.0", 83 | "typescript": "~4.7.2" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tools/demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo plots 2 | 3 | These plots are meant to run with the compiled version of nodeplotlib. You can run them 4 | with the following commands: 5 | 6 | ``` 7 | node ./tools/demo/scatter.js 8 | node ./tools/demo/candlestick.js 9 | npx ts-node ./tools/demo/scatter.ts 10 | ``` 11 | 12 | For further demos you can have a look at the **dev-server** app. In the _/data_ folder 13 | you'll find demos for several plot types. 14 | -------------------------------------------------------------------------------- /tools/demo/candlestick.js: -------------------------------------------------------------------------------- 1 | const npl = require('../../dist/libs/nodeplotlib'); 2 | 3 | const trace1 = { 4 | x: [ 5 | '2017-01-04', 6 | '2017-01-05', 7 | '2017-01-06', 8 | '2017-01-09', 9 | '2017-01-10', 10 | '2017-01-11', 11 | '2017-01-12', 12 | '2017-01-13', 13 | '2017-01-17', 14 | '2017-01-18', 15 | '2017-01-19', 16 | '2017-01-20', 17 | '2017-01-23', 18 | '2017-01-24', 19 | '2017-01-25', 20 | '2017-01-26', 21 | '2017-01-27', 22 | '2017-01-30', 23 | '2017-01-31', 24 | '2017-02-01', 25 | '2017-02-02', 26 | '2017-02-03', 27 | '2017-02-06', 28 | '2017-02-07', 29 | '2017-02-08', 30 | '2017-02-09', 31 | '2017-02-10', 32 | '2017-02-13', 33 | '2017-02-14', 34 | '2017-02-15', 35 | ], 36 | close: [ 37 | 116.019997, 116.610001, 117.910004, 118.989998, 119.110001, 119.75, 119.25, 38 | 119.040001, 120, 119.989998, 119.779999, 120, 120.080002, 119.970001, 39 | 121.879997, 121.940002, 121.949997, 121.629997, 121.349998, 128.75, 40 | 128.529999, 129.080002, 130.289993, 131.529999, 132.039993, 132.419998, 41 | 132.119995, 133.289993, 135.020004, 135.509995, 42 | ], 43 | decreasing: { line: { color: '#7F7F7F' } }, 44 | high: [ 45 | 116.510002, 116.860001, 118.160004, 119.43, 119.379997, 119.93, 119.300003, 46 | 119.620003, 120.239998, 120.5, 120.089996, 120.449997, 120.809998, 47 | 120.099998, 122.099998, 122.440002, 122.349998, 121.629997, 121.389999, 48 | 130.490005, 129.389999, 129.190002, 130.5, 132.089996, 132.220001, 49 | 132.449997, 132.940002, 133.820007, 135.089996, 136.270004, 50 | ], 51 | increasing: { line: { color: '#17BECF' } }, 52 | line: { color: 'rgba(31,119,180,1)' }, 53 | low: [ 54 | 115.75, 115.809998, 116.470001, 117.940002, 118.300003, 118.599998, 55 | 118.209999, 118.809998, 118.220001, 119.709999, 119.370003, 119.730003, 56 | 119.769997, 119.5, 120.279999, 121.599998, 121.599998, 120.660004, 57 | 120.620003, 127.010002, 127.779999, 128.160004, 128.899994, 130.449997, 58 | 131.220001, 131.119995, 132.050003, 132.75, 133.25, 134.619995, 59 | ], 60 | open: [ 61 | 115.849998, 115.919998, 116.779999, 117.949997, 118.769997, 118.739998, 62 | 118.900002, 119.110001, 118.339996, 120, 119.400002, 120.449997, 120, 63 | 119.550003, 120.419998, 121.669998, 122.139999, 120.93, 121.150002, 64 | 127.029999, 127.980003, 128.309998, 129.130005, 130.539993, 131.350006, 65 | 131.649994, 132.460007, 133.080002, 133.470001, 135.520004, 66 | ], 67 | type: 'candlestick', 68 | xaxis: 'x', 69 | yaxis: 'y', 70 | }; 71 | 72 | const data = [trace1]; 73 | 74 | const layout = { 75 | dragmode: 'zoom', 76 | margin: { 77 | r: 10, 78 | t: 25, 79 | b: 40, 80 | l: 60, 81 | }, 82 | showlegend: false, 83 | xaxis: { 84 | autorange: true, 85 | domain: [0, 1], 86 | range: ['2017-01-03 12:00', '2017-02-15 12:00'], 87 | rangeslider: { range: ['2017-01-03 12:00', '2017-02-15 12:00'] }, 88 | title: 'Date', 89 | type: 'date', 90 | }, 91 | yaxis: { 92 | autorange: true, 93 | domain: [0, 1], 94 | range: [114.609999778, 137.410004222], 95 | type: 'linear', 96 | }, 97 | }; 98 | 99 | npl.plot(data, layout); 100 | -------------------------------------------------------------------------------- /tools/demo/scatter.js: -------------------------------------------------------------------------------- 1 | import npl from '../../dist/libs/nodeplotlib'; 2 | npl.plot([{ x: [0], y: [1], type: 'scatter' }]); 3 | -------------------------------------------------------------------------------- /tools/demo/scatter.ts: -------------------------------------------------------------------------------- 1 | import npl from '../../dist/libs/nodeplotlib'; 2 | 3 | npl.plot([{ x: [0, 1], y: [1, 1], type: 'scatter' }]); 4 | -------------------------------------------------------------------------------- /tools/demo/stream.ts: -------------------------------------------------------------------------------- 1 | import { plot, Plot } from '../../dist/libs/nodeplotlib'; 2 | import { interval, map, Observable } from 'rxjs'; 3 | 4 | const stream$: Observable = interval(100).pipe( 5 | map(createSinusPlotFromNumber) 6 | ); 7 | 8 | function createSinusPlotFromNumber(num: number): Plot[] { 9 | const data: Plot[] = [ 10 | { 11 | x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12 | y: Array(10) 13 | .fill(0) 14 | .map((_, i) => Math.sin(num + i)), 15 | type: 'scatter', 16 | }, 17 | ]; 18 | return data; 19 | } 20 | 21 | plot(stream$); 22 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngfelixl/nodeplotlib/3f7c89b398a09f2d1d1e638197ed8c980e65c8a6/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tools/util/copy-files.ts: -------------------------------------------------------------------------------- 1 | import { copySync } from 'fs-extra'; 2 | 3 | copySync('dist/apps/web', 'dist/libs/nodeplotlib/src/lib/server/web'); 4 | copySync('libs/nodeplotlib/README.md', 'dist/libs/nodeplotlib/README.md'); 5 | copySync('LICENSE', 'dist/libs/nodeplotlib/LICENSE'); 6 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@npl/nodeplotlib": ["libs/nodeplotlib/src/index.ts"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | --------------------------------------------------------------------------------