├── .github └── workflows │ ├── cd.yml │ ├── ci.yml │ ├── cicd.yml │ ├── doc.yml │ └── init.yml ├── .gitignore ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── assets │ ├── api-overview.svg │ ├── favicon.svg │ ├── javascripts │ │ ├── analytics │ │ │ └── head.js │ │ ├── csv2js.js │ │ ├── extlinks.js │ │ ├── highlight.js │ │ └── thumbs.js │ ├── logo-white.svg │ ├── readme-example.gif │ ├── stylesheets │ │ ├── gallery.css │ │ ├── highlight.css │ │ └── vizzu.css │ └── vizzu-story.gif ├── dev │ └── index.md ├── examples │ ├── _basic.md │ ├── _basic.png │ ├── _basic │ │ └── main.js │ ├── linkedinpoll.md │ ├── linkedinpoll.png │ ├── linkedinpoll │ │ ├── linkedinpoll.csv │ │ └── main.js │ ├── population.md │ ├── population.png │ ├── population │ │ ├── main.js │ │ └── population.csv │ ├── proglangs.md │ ├── proglangs.png │ ├── proglangs │ │ ├── main.js │ │ └── proglangs.csv │ ├── titanic.md │ ├── titanic.png │ ├── titanic │ │ ├── main.js │ │ └── titanic.csv │ ├── trumptwitter.md │ ├── trumptwitter.png │ ├── trumptwitter │ │ ├── main.js │ │ └── trumptwitter.csv │ ├── usbudget.md │ ├── usbudget.png │ └── usbudget │ │ ├── main.js │ │ └── usbudget.csv ├── installation.md ├── reference │ └── README.md └── tutorial │ ├── building_blocks.md │ ├── data.md │ ├── index.md │ └── initialization.md ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── pdm.lock ├── pyproject.toml ├── rollup.config.cjs ├── src ├── AnimationQueue.js ├── controllers │ └── slider.js ├── example │ ├── data.js │ ├── index.html │ ├── index.js │ ├── loadinganim.svg │ └── style.js ├── vizzu-controller.js ├── vizzu-player.js └── vizzu-story.d.ts ├── tests ├── assets │ ├── chart-params │ │ ├── animOptions.js │ │ ├── config.js │ │ ├── data.js │ │ ├── filter.js │ │ └── style.js │ ├── data-sets │ │ ├── basic.mjs │ │ ├── nolabel.mjs │ │ └── olympics.mjs │ ├── data-tests │ │ └── label │ │ │ ├── basic.cjs │ │ │ ├── index.cjs │ │ │ ├── nolabel.cjs │ │ │ └── olympics.cjs │ ├── mocks │ │ ├── vizzu-attribute.js │ │ ├── vizzu-cdn.js │ │ ├── vizzu-window.js │ │ └── vizzu.js │ └── slides │ │ ├── asset-functions.js │ │ ├── more-slides.js │ │ ├── one-slide-more-steps.js │ │ ├── one-slide-one-empty-step.js │ │ ├── one-slide-one-step.js │ │ └── zero-slide.js └── vizzu-player │ ├── e2e │ ├── jest.config.cjs │ └── vp.data.label.test.cjs │ └── unit │ ├── jest.config.js │ ├── vp.animationqueue.test.js │ ├── vp.attributes.test.js │ └── vp.slides.test.js └── tools ├── ci ├── markdown_format.py ├── std_check.py └── version.py ├── docs ├── config.py ├── deploy.py ├── examples │ └── gen_examples.py ├── mkdocs.yml ├── overrides │ ├── main.html │ └── mike │ │ └── redirect.html ├── pages │ └── gen_pages.py └── reference │ ├── gen_reference.cjs │ ├── gen_reference.py │ └── tsconfig.json └── modules ├── node.py └── vizzu.py /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_call: 7 | 8 | jobs: 9 | publish: 10 | if: ${{ (github.event_name == 'release' && github.event.action == 'published') }} 11 | 12 | runs-on: ubuntu-24.04 13 | 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 1 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '22' 24 | cache: 'npm' 25 | cache-dependency-path: 'package-lock.json' 26 | 27 | - name: Set up npm 28 | run: | 29 | npm ci 30 | 31 | - name: Get workflow ID 32 | id: workflow_id 33 | run: | 34 | workflow_data=$(curl -s -X GET \ 35 | -H "Accept: application/vnd.github.v3+json" \ 36 | -H "Authorization: Bearer ${{ secrets.VIZZUHQ_GITHUB_API }}" \ 37 | "https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows") 38 | workflow_id=$(echo $workflow_data | jq -r '.workflows[] | select(.name == "CI-CD") | .id') 39 | echo "workflow_id=${workflow_id}" >> $GITHUB_OUTPUT 40 | 41 | - name: Get run ID 42 | id: run_id 43 | run: | 44 | run_data=$(curl -s -X GET \ 45 | -H "Accept: application/vnd.github.v3+json" \ 46 | -H "Authorization: Bearer ${{ secrets.VIZZUHQ_GITHUB_API }}" \ 47 | "https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/${{ steps.workflow_id.outputs.workflow_id }}/runs?branch=main") 48 | run_id=$(echo $run_data | jq -r '.workflow_runs[0].id') 49 | echo "run_id=${run_id}" >> $GITHUB_OUTPUT 50 | 51 | - name: Cache package 52 | uses: actions/cache@v4 53 | with: 54 | path: | 55 | README.md 56 | build/ 57 | dist/ 58 | key: package_${{ steps.run_id.outputs.run_id }} 59 | 60 | - name: Publish package 61 | run: | 62 | npm config set registry=https://registry.npmjs.org/ 63 | npm config set //registry.npmjs.org/:_authToken=${NPM_API_TOKEN} 64 | npm publish 65 | env: 66 | NPM_API_TOKEN: ${{ secrets.NPM_API_TOKEN }} 67 | 68 | - name: Upload package 69 | run: | 70 | echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token 71 | gh release upload ${{ github.event.release.tag_name }} build/*tgz --clobber 72 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | check_src: 8 | runs-on: ubuntu-24.04 9 | 10 | env: 11 | PUPPETEER_CACHE_DIR: ${{ github.workspace }}/node_modules/.chromium 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 1 18 | 19 | - name: Set up Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '22' 23 | cache: 'npm' 24 | cache-dependency-path: 'package-lock.json' 25 | 26 | - name: Set up npm 27 | run: | 28 | npm ci 29 | 30 | - name: Format 31 | run: | 32 | npm run format:src 33 | 34 | - name: Lint 35 | run: | 36 | npm run lint:src 37 | 38 | - name: Type 39 | run: | 40 | npm run type:src 41 | 42 | - name: Test 43 | run: | 44 | npm test 45 | 46 | check_docs: 47 | runs-on: ubuntu-24.04 48 | 49 | steps: 50 | - name: Checkout repo 51 | uses: actions/checkout@v4 52 | with: 53 | fetch-depth: 1 54 | 55 | - name: Set up Node.js 56 | uses: actions/setup-node@v4 57 | with: 58 | node-version: '22' 59 | cache: 'npm' 60 | cache-dependency-path: 'package-lock.json' 61 | 62 | - name: Set up npm 63 | run: | 64 | npm ci 65 | 66 | - name: Set up Python 67 | uses: actions/setup-python@v5 68 | with: 69 | python-version: '3.13' 70 | 71 | - name: Cache venv 72 | uses: actions/cache@v4 73 | with: 74 | path: .venv 75 | key: venv_ubuntu24_${{ hashFiles('pdm.lock') }} 76 | 77 | - name: Format 78 | run: | 79 | source .venv/bin/activate 80 | npm run format:docs 81 | 82 | - name: Lint 83 | run: | 84 | source .venv/bin/activate 85 | npm run lint:docs 86 | 87 | check_tools: 88 | runs-on: ubuntu-24.04 89 | 90 | steps: 91 | - name: Checkout repo 92 | uses: actions/checkout@v4 93 | with: 94 | fetch-depth: 1 95 | 96 | - name: Set up Node.js 97 | uses: actions/setup-node@v4 98 | with: 99 | node-version: '22' 100 | cache: 'npm' 101 | cache-dependency-path: 'package-lock.json' 102 | 103 | - name: Set up npm 104 | run: | 105 | npm ci 106 | 107 | - name: Set up Python 108 | uses: actions/setup-python@v5 109 | with: 110 | python-version: '3.13' 111 | 112 | - name: Cache venv 113 | uses: actions/cache@v4 114 | with: 115 | path: .venv 116 | key: venv_ubuntu24_${{ hashFiles('pdm.lock') }} 117 | 118 | - name: Format 119 | run: | 120 | source .venv/bin/activate 121 | npm run format:tools 122 | 123 | - name: Lint 124 | run: | 125 | source .venv/bin/activate 126 | npm run lint:tools 127 | 128 | - name: Type 129 | run: | 130 | source .venv/bin/activate 131 | npm run type:tools 132 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yml: -------------------------------------------------------------------------------- 1 | name: CI-CD 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | init: 11 | uses: ./.github/workflows/init.yml 12 | 13 | ci: 14 | uses: ./.github/workflows/ci.yml 15 | needs: init 16 | 17 | doc: 18 | uses: ./.github/workflows/doc.yml 19 | needs: ci 20 | 21 | cd: 22 | uses: ./.github/workflows/cd.yml 23 | secrets: inherit 24 | needs: doc 25 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Doc 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | workflow_call: 8 | 9 | jobs: 10 | build: 11 | if: ${{ !((github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'workflow_dispatch') }} 12 | 13 | runs-on: ubuntu-24.04 14 | 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 1 20 | 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '22' 25 | cache: 'npm' 26 | cache-dependency-path: 'package-lock.json' 27 | 28 | - name: Set up npm 29 | run: | 30 | npm ci 31 | 32 | - name: Set up Python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.13' 36 | 37 | - name: Cache venv 38 | uses: actions/cache@v4 39 | with: 40 | path: .venv 41 | key: venv_ubuntu24_${{ hashFiles('pdm.lock') }} 42 | 43 | - name: Build documentation 44 | run: | 45 | source .venv/bin/activate 46 | npm run build-docs 47 | 48 | deploy: 49 | if: ${{ ((github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'workflow_dispatch') }} 50 | 51 | runs-on: ubuntu-24.04 52 | 53 | steps: 54 | - name: Checkout repo 55 | uses: actions/checkout@v4 56 | with: 57 | fetch-depth: 1 58 | 59 | - name: Set up Node.js 60 | uses: actions/setup-node@v4 61 | with: 62 | node-version: '22' 63 | cache: 'npm' 64 | cache-dependency-path: 'package-lock.json' 65 | 66 | - name: Set up npm 67 | run: | 68 | npm ci 69 | 70 | - name: Set up Python 71 | uses: actions/setup-python@v5 72 | with: 73 | python-version: '3.13' 74 | 75 | - name: Cache venv 76 | uses: actions/cache@v4 77 | with: 78 | path: .venv 79 | key: venv_ubuntu24_${{ hashFiles('pdm.lock') }} 80 | 81 | - name: Configure Git 82 | run: | 83 | git config --global user.name "${{ secrets.VIZZUHQ_GITHUB_USER }}" 84 | git config --global user.email "${{ secrets.VIZZUHQ_GITHUB_EMAIL }}" 85 | 86 | - name: Deploy documentation 87 | run: | 88 | source .venv/bin/activate 89 | git fetch origin gh-pages || echo "gh-pages does not exist" 90 | npm run deploy-docs 91 | git push origin gh-pages 92 | -------------------------------------------------------------------------------- /.github/workflows/init.yml: -------------------------------------------------------------------------------- 1 | name: Init 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | init_ubuntu24: 8 | runs-on: ubuntu-24.04 9 | 10 | env: 11 | PUPPETEER_CACHE_DIR: ${{ github.workspace }}/node_modules/.chromium 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 1 18 | 19 | - name: Set up Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '22' 23 | cache: 'npm' 24 | cache-dependency-path: 'package-lock.json' 25 | 26 | - name: Set up npm 27 | run: | 28 | npm ci 29 | 30 | - name: Set up Python 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: '3.13' 34 | 35 | - name: Cache venv 36 | id: venv_ubuntu24 37 | uses: actions/cache@v4 38 | with: 39 | path: .venv 40 | key: venv_ubuntu24_${{ hashFiles('pdm.lock') }} 41 | 42 | - name: Set up venv 43 | if: steps.venv_ubuntu24.outputs.cache-hit != 'true' 44 | run: | 45 | python3.13 -m venv ".venv" 46 | source .venv/bin/activate 47 | pip install pdm==2.22.3 48 | pdm install 49 | 50 | - name: Build package 51 | run: | 52 | source .venv/bin/activate 53 | npm run build 54 | 55 | - name: Cache package 56 | uses: actions/cache@v4 57 | with: 58 | path: | 59 | README.md 60 | build/ 61 | dist/ 62 | key: package_${{ github.run_id }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | build 4 | dist 5 | 6 | .coverage 7 | 8 | __pycache__ 9 | 10 | .venv 11 | .pdm-python 12 | 13 | site 14 | node_modules 15 | tmp 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | See [Code of Conduct](https://lib.vizzuhq.com/latest/CODE_OF_CONDUCT/) of the 4 | `Vizzu` community. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Issues 4 | 5 | You can find our open issues in the project's 6 | [issue tracker](https://github.com/vizzuhq/vizzu-story-js/issues). Please let us 7 | know if you find any issues or have any feature requests there. 8 | 9 | ## Contributing 10 | 11 | If you want to contribute to the project, your help is very welcome. Just fork 12 | the project, make your changes and send us a pull request. You can find the 13 | detailed description of how to do this in 14 | [Github's guide to contributing to projects](https://docs.github.com/en/get-started/quickstart/contributing-to-projects). 15 | 16 | Our [Roadmap page](https://github.com/vizzuhq/.github/wiki/Roadmap) is a 17 | comprehensive list of tasks we want to do in the future. It is a good place to 18 | start if you want to contribute to `Vizzu`. In case you have something else in 19 | mind, that's awesome and we are very interested in hearing about it. 20 | 21 | ## CI-CD 22 | 23 | ### Development environment 24 | 25 | For contributing to the project, it is recommended to use `Node.js` `22`. 26 | However, for the documentation we are also using `Python`. If you plan to 27 | contribute to this part of the project, you will need `Python`, preferably 28 | version `3.13`. 29 | 30 | The following steps demonstrate how to set up the development environment on an 31 | `Ubuntu` `24.04` operating system. However, the process can be adapted for other 32 | operating systems as well. 33 | 34 | To start using the `Vizzu-Story` development environment, you need to install 35 | the development dependencies. 36 | 37 | ```sh 38 | npm install 39 | ``` 40 | 41 | If you want to work with the documantation too, you need to set up the `Python` 42 | development environment. 43 | 44 | ```sh 45 | python3.13 -m venv ".venv" 46 | source .venv/bin/activate 47 | pip install pdm==2.22.3 48 | pdm install 49 | ``` 50 | 51 | The development requirements are installed based on the `package-lock.json` and 52 | `pdm.lock` files. To update the development requirements, you can use the 53 | command `npm run lock`. 54 | 55 | **Note:** For all available `npm` scripts, run `npm run --list`. 56 | 57 | For better development practices, you can set up `pre-commit` and `pre-push` 58 | hooks in your local `Git` repository. The `pre-commit` hook will format the code 59 | automatically, and the `pre-push` hook will run the CI steps before pushing your 60 | changes. 61 | 62 | ```sh 63 | npx husky install 64 | ``` 65 | 66 | **Note:** The provided `pre-commit` and `pre-push` hook configuration file is 67 | tailored for `Ubuntu` `24.04`. If you intend to use another operating system, 68 | you may need to create a custom configuration file suitable for that 69 | environment. 70 | 71 | ### CI 72 | 73 | The CI pipeline includes code formatting checks, code analysis, typing 74 | validation, and unit tests for the `Vizzu-Story` project. 75 | 76 | To run the entire CI pipeline, execute the following `npm` script: 77 | 78 | ```sh 79 | npm run ci 80 | ``` 81 | 82 | However, if you want to run the CI steps on specific parts of the project, you 83 | can use the following scripts: `ci:src`, `ci:docs`, or `ci:tools`. 84 | 85 | #### Formatting 86 | 87 | You can check the code's formatting using the `format` script: 88 | 89 | ```sh 90 | npm run format 91 | ``` 92 | 93 | If you need to fix any formatting issues, you can use the `fix-format` script: 94 | 95 | ```sh 96 | npm run fix-format 97 | ``` 98 | 99 | If you wish to format specific parts of the project, you can use the following 100 | scripts: `format:src`, `format:docs`, `format:tools`, or `fix-format:src`, 101 | `fix-format:docs`, `fix-format:tools`. 102 | 103 | #### Code analyses 104 | 105 | To perform code analyses, you can use the `lint` script: 106 | 107 | ```sh 108 | npm run lint 109 | ``` 110 | 111 | If you need to run code analyses for specific parts of the project, you can 112 | utilize the following scripts: `lint:src`, `lint:docs`, or `lint:tools`. 113 | 114 | #### Typing 115 | 116 | For type checking, you can use the `type` script: 117 | 118 | ```sh 119 | npm run type 120 | ``` 121 | 122 | If you want to check specific parts of the project, you can use the following 123 | scripts: `type:src` or `type:tools`. 124 | 125 | #### Testing 126 | 127 | The project is tested using the `jest` testing framework. To run the tests, you 128 | can use the `test` script: 129 | 130 | ```sh 131 | npm test 132 | ``` 133 | 134 | ### Documentation 135 | 136 | To build the documentation, you can use the `build-docs` script: 137 | 138 | ```sh 139 | npm run build-docs 140 | ``` 141 | 142 | You can read the online version at 143 | [vizzu-story.vizzuhq.com](https://vizzu-story.vizzuhq.com/latest/). 144 | 145 | ### Release 146 | 147 | `Vizzu-Story` is distributed on 148 | [npm](https://www.npmjs.com/package/vizzu-story). **Note:** You need to be an 149 | administrator to release the project. 150 | 151 | To release `Vizzu-Story`, follow the steps below: 152 | 153 | - You should increase the version number in `package.json`. The version bump 154 | should be in a separated commit. 155 | 156 | - Generate the release notes and publish the new release on 157 | [Releases](https://github.com/vizzuhq/vizzu-story-js/releases). 158 | 159 | **Note:** Publishing a new release will automatically trigger the `cd` workflow 160 | which builds and uploads the `Vizzu-Story` package to 161 | [npm](https://www.npmjs.com/package/vizzu-story). 162 | 163 | Before making a release, you can build and check the package using the `build` 164 | script: 165 | 166 | ```sh 167 | npm run build 168 | ``` 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Vizzu-Story 4 | 5 |

Vizzu-Story - Build and present animated data stories

6 |

7 | Documentation 8 | · Examples 9 | · Code reference 10 | · Repository 11 | · Blog 12 |

13 |

14 | 15 | [![npm version](https://badge.fury.io/js/vizzu-story.svg)](https://badge.fury.io/js/vizzu-story) 16 | [![install size](https://packagephobia.com/badge?p=vizzu-story)](https://packagephobia.com/result?p=vizzu-story) 17 | 18 | # Vizzu-Story 19 | 20 | ## About The Extension 21 | 22 | `Vizzu-Story` is an extension for the 23 | [Vizzu](https://github.com/vizzuhq/vizzu-lib) `JavaScript` library that allows 24 | users to create interactive presentations from the animated data visualizations 25 | built with `Vizzu`. 26 | 27 | The extension provides a `Web Component` that contains the presentation and adds 28 | controls for navigating between slides - predefined stages within the story. 29 | 30 | ## Installation 31 | 32 | Install via [npm](https://www.npmjs.com/package/vizzu-story): 33 | 34 | ```sh 35 | npm install vizzu-story 36 | ``` 37 | 38 | Or use it from [CDN](https://www.jsdelivr.com/package/npm/vizzu-story): 39 | 40 | ```javascript 41 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 42 | ``` 43 | 44 | ## Usage 45 | 46 | ![Example story](https://vizzu-story.vizzuhq.com/latest/assets/readme-example.gif) 47 | 48 | Create a `vizzu-player` element that will contain the rendered story: 49 | 50 | ``` 51 | 52 | ``` 53 | 54 | In a script module element import the extension from `CDN` or local install: 55 | 56 | ``` 57 | 61 | ``` 62 | 63 | Add the underlying data for the data story. You can use the same data definition 64 | formats as in the `Vizzu` library, but you must add the entire data set for the 65 | whole story in the initial step; you can not change this later. See 66 | [Data chapter](https://vizzu-story.vizzuhq.com/latest/tutorial/data/) for more 67 | details on data formats. 68 | 69 | ```javascript 70 | const data = { 71 | series: [{ 72 | name: 'Foo', 73 | values: ['Alice', 'Bob', 'Ted'] 74 | }, { 75 | name: 'Bar', 76 | values: [15, 32, 12] 77 | }, { 78 | name: 'Baz', 79 | values: [5, 3, 2] 80 | }] 81 | } 82 | ``` 83 | 84 | Create the data story by defining a sequence of slides. A slide can be a single 85 | chart corresponding to an [animate](https://lib.vizzuhq.com/latest/tutorial/) 86 | call from `Vizzu`. Or a slide can be a sequence of animation calls, in which 87 | case all of these animations will be played until the last one in the sequence, 88 | allowing for more complex transitions between slides. 89 | 90 | ```javascript 91 | const slides = [{ 92 | config: { 93 | x: 'Foo', 94 | y: 'Bar' 95 | } 96 | }, { 97 | config: { 98 | color: 'Foo', 99 | x: 'Baz', 100 | geometry: 'circle' 101 | } 102 | }] 103 | ``` 104 | 105 | Navigation controls beneath the chart will navigate between the slides. You can 106 | use the `PgUp` and `PgDn` buttons, left and right arrows to navigate between 107 | slides, and the `Home` and `End` buttons to jump to the first or last slide. 108 | 109 | On each chart, you can define the chart configuration and style with the same 110 | objects as in `Vizzu`. However, you can not modify the underlying data between 111 | the slides, only the data filter used. 112 | 113 | ```typescript 114 | interface Chart { 115 | config?: Vizzu.Config.Chart 116 | filter?: Vizzu.Data.FilterCallback | null 117 | style?: Vizzu.Styles.Chart 118 | animOptions?: Vizzu.Anim.Options 119 | } 120 | ``` 121 | 122 | Put the data and the slide list into the `story` descriptor object. Here you can 123 | also set the `story` `style` property to set the chart style used for the whole 124 | `story`. 125 | 126 | ```javascript 127 | const story = { 128 | data: data, 129 | slides: slides 130 | } 131 | ``` 132 | 133 | Then set up the created element with the configuration object: 134 | 135 | ```javascript 136 | const vp = document.querySelector('vizzu-player') 137 | vp.slides = story 138 | ``` 139 | 140 | > [Check out a live example in JSFiddle!](https://jsfiddle.net/VizzuHQ/topcmuyf/3/) 141 | 142 | ## Documentation 143 | 144 | Visit our [Documentation site](https://vizzu-story.vizzuhq.com/latest/) for more 145 | details and a step-by-step tutorial into `Vizzu-Story` or check out our 146 | [Example gallery](https://vizzu-story.vizzuhq.com/latest/examples/). 147 | 148 | ## Contributing 149 | 150 | We welcome contributions to the project; visit our 151 | [Contributing guide](https://vizzu-story.vizzuhq.com/latest/CONTRIBUTING/) for 152 | further info. 153 | 154 | ## Contact 155 | 156 | - Join our Slack: 157 | [vizzu-community.slack.com](https://join.slack.com/t/vizzu-community/shared_invite/zt-w2nqhq44-2CCWL4o7qn2Ns1EFSf9kEg) 158 | 159 | 160 | - Drop us a line at hello@vizzu.io 161 | 162 | - Follow us on Twitter: 163 | [https://twitter.com/VizzuHQ](https://twitter.com/VizzuHQ) 164 | 165 | ## License 166 | 167 | Copyright © 2022-2025 [Vizzu Inc.](https://vizzuhq.com) 168 | 169 | Released under the 170 | [Apache 2.0 License](https://vizzu-story.vizzuhq.com/latest/LICENSE/). 171 | -------------------------------------------------------------------------------- /docs/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/assets/javascripts/analytics/head.js: -------------------------------------------------------------------------------- 1 | const plausibleBasic = document.createElement('script') 2 | plausibleBasic.setAttribute('defer', '') 3 | plausibleBasic.setAttribute('data-domain', 'vizzu-story.vizzuhq.com') 4 | plausibleBasic.src = 'https://plausible.io/js/script.outbound-links.js' 5 | 6 | const plausibleOutboundLinkTracking = document.createElement('script') 7 | plausibleOutboundLinkTracking.textContent = 8 | 'window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }' 9 | 10 | const documentHeadElement = document.getElementsByTagName('head')[0] 11 | documentHeadElement.appendChild(plausibleBasic) 12 | documentHeadElement.appendChild(plausibleOutboundLinkTracking) 13 | -------------------------------------------------------------------------------- /docs/assets/javascripts/csv2js.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm' 2 | 3 | class Csv2Js { 4 | static csv(csv, options) { 5 | return new Promise((resolve, reject) => { 6 | if (!options) { 7 | options = {} 8 | } 9 | if (!options.dimensions) { 10 | options.dimensions = [] 11 | } 12 | if (!options.measures) { 13 | options.measures = [] 14 | } 15 | if (!options.units) { 16 | options.units = {} 17 | } 18 | const detectedDimensions = {} 19 | const data = { series: [], records: [] } 20 | const csvLoaded = d3.csv(csv) 21 | 22 | csvLoaded.then((csvData) => { 23 | for (let i = 0; i < csvData.length; i++) { 24 | const record = [] 25 | const keys = Object.keys(csvData[i]) 26 | for (const key of keys) { 27 | const numValue = +csvData[i][key] 28 | if (csvData[i][key] !== '' && !isNaN(numValue)) { 29 | record.push(numValue) 30 | } else { 31 | record.push(csvData[i][key]) 32 | detectedDimensions[key] = true 33 | } 34 | } 35 | data.records.push(record) 36 | } 37 | for (let i = 0; i < csvData.columns.length; i++) { 38 | const key = csvData.columns[i] 39 | const series = { 40 | name: key, 41 | type: 42 | options.dimensions.includes(key) || 43 | (detectedDimensions[key] && !options.measures.includes(key)) 44 | ? 'dimension' 45 | : 'measure' 46 | } 47 | if (options.units[key]) { 48 | series.unit = options.units[key] 49 | } 50 | data.series.push(series) 51 | } 52 | return resolve(data) 53 | }) 54 | }) 55 | } 56 | } 57 | 58 | export default Csv2Js 59 | -------------------------------------------------------------------------------- /docs/assets/javascripts/extlinks.js: -------------------------------------------------------------------------------- 1 | function changeTarget(links, target) { 2 | for (let i = 0; i < links.length; i++) { 3 | if (links[i].hostname !== window.location.hostname || links[i].href.includes('/assets/')) { 4 | links[i].target = target 5 | } 6 | } 7 | } 8 | 9 | document.addEventListener('DOMContentLoaded', (event) => { 10 | const target = '_blank' 11 | changeTarget(document.links, target) 12 | 13 | const iframe = document.getElementById('coviframe') 14 | if (iframe) { 15 | iframe.addEventListener('load', (event) => { 16 | changeTarget(iframe.contentWindow.document.getElementsByTagName('a'), target) 17 | }) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /docs/assets/javascripts/highlight.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', (event) => { 2 | document.querySelectorAll('pre code').forEach((el) => { 3 | if (window.location.href.includes('LICENSE')) { 4 | return 5 | } 6 | 7 | hljs.highlightElement(el) // eslint-disable-line no-undef 8 | if (window.location.href.includes('/examples/')) { 9 | hljs.lineNumbersBlock(el) // eslint-disable-line no-undef 10 | } 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /docs/assets/javascripts/thumbs.js: -------------------------------------------------------------------------------- 1 | function setupVideos() { 2 | const videos = document.querySelectorAll('video.image-gallery') 3 | videos.forEach((video) => { 4 | const playPromise = video.play() 5 | if (playPromise !== undefined) { 6 | playPromise.then((_) => { 7 | const observer = new IntersectionObserver( 8 | (entries) => { 9 | entries.forEach((entry) => { 10 | if (entry.intersectionRatio !== 1 && !video.paused) { 11 | video.pause() 12 | } else if (video.paused) { 13 | video.play() 14 | } 15 | }) 16 | }, 17 | { threshold: 0.2 } 18 | ) 19 | observer.observe(video) 20 | }) 21 | } 22 | }) 23 | } 24 | 25 | const currentScript = document.currentScript 26 | document.addEventListener('DOMContentLoaded', (event) => { 27 | const parentContainer = currentScript.nextElementSibling 28 | parentContainer.style.display = 'flex' 29 | parentContainer.style['flex-wrap'] = 'wrap' 30 | parentContainer.style.justifyContent = 'center' 31 | setupVideos() 32 | }) 33 | -------------------------------------------------------------------------------- /docs/assets/logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/assets/readme-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/assets/readme-example.gif -------------------------------------------------------------------------------- /docs/assets/stylesheets/gallery.css: -------------------------------------------------------------------------------- 1 | .image-gallery { 2 | max-width: 320px; 3 | border: 1px solid #ddd; 4 | margin: 20px; 5 | } 6 | 7 | .image-gallery-w-caption { 8 | max-width: 320px; 9 | border: 1px solid #ddd; 10 | margin: 20px; 11 | margin-bottom: 0px !important; 12 | } 13 | 14 | .image-figure { 15 | margin: 0px !important; 16 | } 17 | 18 | .image-caption { 19 | margin: 0px; 20 | margin-bottom: 20px !important; 21 | font-family: 'Roboto' !important; 22 | font-style: normal !important; 23 | } 24 | 25 | .image-center { 26 | display: block; 27 | margin-left: auto; 28 | margin-right: auto; 29 | } 30 | -------------------------------------------------------------------------------- /docs/assets/stylesheets/highlight.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | background-color: #f8f9fa !important; 3 | } 4 | 5 | .hljs { 6 | background-color: #f9f9fb00 !important; 7 | } 8 | 9 | .hljs-title { 10 | color: #f4941b !important; 11 | } 12 | 13 | .hljs-property, 14 | .hljs-attr { 15 | color: #4171cd !important; 16 | } 17 | 18 | .hljs-literal { 19 | color: #03ae71 !important; 20 | } 21 | 22 | .hljs-string { 23 | color: #f25456 !important; 24 | } 25 | 26 | .hljs-number { 27 | color: #f25456 !important; 28 | } 29 | 30 | .hljs-built_in, 31 | .hljs-keyword { 32 | color: #03ae71 !important; 33 | } 34 | 35 | .hljs-keyword { 36 | color: #03ae71 !important; 37 | } 38 | 39 | .hljs-meta { 40 | color: #d49664ff !important; 41 | } 42 | 43 | .hljs-params, 44 | .hljs-comment { 45 | color: #4c7450ff !important; 46 | } 47 | 48 | .hljs-ln-numbers { 49 | -webkit-touch-callout: none; 50 | -webkit-user-select: none; 51 | -khtml-user-select: none; 52 | -moz-user-select: none; 53 | -ms-user-select: none; 54 | user-select: none; 55 | 56 | text-align: center; 57 | color: #ccc; 58 | border-right: 1px solid #ccc; 59 | vertical-align: top; 60 | padding-right: 5px !important; 61 | } 62 | 63 | .hljs-ln-code { 64 | padding-left: 10px !important; 65 | } 66 | 67 | .snippet:focus { 68 | color: #93a49a70 !important; 69 | transition: 200ms; 70 | } 71 | -------------------------------------------------------------------------------- /docs/assets/stylesheets/vizzu.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme='vizzu'] { 2 | --md-primary-fg-color: #333; 3 | --md-primary-fg-color--dark: #666; 4 | --md-default-fg-color: #666; 5 | --md-typeset-color: #666; 6 | --md-typeset-a-color: #4171cd; 7 | } 8 | 9 | .md-typeset details.tip, 10 | .md-typeset .admonition.tip, 11 | .md-typeset details.note, 12 | .md-typeset .admonition.note, 13 | .md-typeset details.info, 14 | .md-typeset .admonition.info, 15 | .md-typeset details.example, 16 | .md-typeset .admonition.example { 17 | color: #666 !important; 18 | border-color: #ccc !important; 19 | } 20 | 21 | .md-typeset .tip > .admonition-title, 22 | .md-typeset .tip > summary { 23 | color: #666 !important; 24 | border-color: #ccc !important; 25 | background-color: #f8f9fa !important; 26 | } 27 | .md-typeset .tip > .admonition-title::before, 28 | .md-typeset .tip > summary::before { 29 | background-color: #ccc !important; 30 | -webkit-mask-image: var(--md-admonition-icon--tip); 31 | mask-image: var(--md-admonition-icon--tip); 32 | } 33 | .md-typeset .tip > summary::after { 34 | background-color: #ccc !important; 35 | -webkit-mask-image: var(--md-details-icon); 36 | mask-image: var(--md-details-icon); 37 | } 38 | 39 | .md-typeset .note > .admonition-title, 40 | .md-typeset .note > summary { 41 | color: #666 !important; 42 | border-color: #ccc !important; 43 | background-color: #f8f9fa !important; 44 | } 45 | .md-typeset .note > .admonition-title::before, 46 | .md-typeset .note > summary::before { 47 | background-color: #ccc !important; 48 | -webkit-mask-image: var(--md-admonition-icon--note); 49 | mask-image: var(--md-admonition-icon--note); 50 | } 51 | .md-typeset .note > summary::after { 52 | background-color: #ccc !important; 53 | -webkit-mask-image: var(--md-details-icon); 54 | mask-image: var(--md-details-icon); 55 | } 56 | 57 | .md-typeset .info > .admonition-title, 58 | .md-typeset .info > summary { 59 | color: #666 !important; 60 | border-color: #ccc !important; 61 | background-color: #f8f9fa !important; 62 | } 63 | .md-typeset .info > .admonition-title::before, 64 | .md-typeset .info > summary::before { 65 | background-color: #ccc !important; 66 | -webkit-mask-image: var(--md-admonition-icon--info); 67 | mask-image: var(--md-admonition-icon--info); 68 | } 69 | .md-typeset .info > summary::after { 70 | background-color: #ccc !important; 71 | -webkit-mask-image: var(--md-details-icon); 72 | mask-image: var(--md-details-icon); 73 | } 74 | 75 | .md-typeset .example > .admonition-title, 76 | .md-typeset .example > summary { 77 | color: #666 !important; 78 | border-color: #ccc !important; 79 | background-color: #f8f9fa !important; 80 | } 81 | .md-typeset .example > .admonition-title::before, 82 | .md-typeset .example > summary::before { 83 | background-color: #ccc !important; 84 | -webkit-mask-image: var(--md-admonition-icon--example); 85 | mask-image: var(--md-admonition-icon--example); 86 | } 87 | .md-typeset .example > summary::after { 88 | background-color: #ccc !important; 89 | -webkit-mask-image: var(--md-details-icon); 90 | mask-image: var(--md-details-icon); 91 | } 92 | -------------------------------------------------------------------------------- /docs/assets/vizzu-story.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/assets/vizzu-story.gif -------------------------------------------------------------------------------- /docs/dev/index.md: -------------------------------------------------------------------------------- 1 | We have compiled some information on the development of `Vizzu-Story` here. If 2 | you're interested in contributing to our open-source tool (which we highly 3 | encourage), please refer to the Contributing chapter. Our community upholds a 4 | strict Code of Conduct that we expect all members to follow. 5 | -------------------------------------------------------------------------------- /docs/examples/_basic.md: -------------------------------------------------------------------------------- 1 | # Basic example 2 | 3 | The below story shows a basic use case for `Vizzu-Story`. 4 | 5 | 6 | 7 | 8 | 9 | Create a `vizzu-player` element that will contain the rendered story. 10 | 11 | ``` 12 | 13 | ``` 14 | 15 | In a script module element: 16 | 17 | ```javascript 18 | // {% include "./_basic/main.js" %} 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/examples/_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/_basic.png -------------------------------------------------------------------------------- /docs/examples/_basic/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | 5 | // Get the created element 6 | const vp = document.querySelector('vizzu-player') 7 | 8 | // Create data object 9 | const data = { 10 | series: [ 11 | { 12 | name: 'Foo', 13 | values: ['Alice', 'Bob', 'Ted'] 14 | }, 15 | { 16 | name: 'Bar', 17 | values: [15, 32, 12] 18 | }, 19 | { 20 | name: 'Baz', 21 | values: [5, 3, 2] 22 | } 23 | ] 24 | } 25 | 26 | // Each slide here is a page in the final interactive story 27 | const slides = [ 28 | { 29 | config: { 30 | x: 'Foo', 31 | y: 'Bar' 32 | } 33 | }, 34 | { 35 | config: { 36 | color: 'Foo', 37 | x: 'Baz', 38 | geometry: 'circle' 39 | } 40 | } 41 | ] 42 | 43 | // Create story configuration object, add data and slides to it 44 | const story = { 45 | data, 46 | slides 47 | } 48 | 49 | // Set up the created element with the configuration object 50 | vp.slides = story 51 | -------------------------------------------------------------------------------- /docs/examples/linkedinpoll.md: -------------------------------------------------------------------------------- 1 | # Presentation Poll Results 2 | 3 | In August, 2022, we asked data scientists in 5 LinkedIn groups about how often 4 | they have to present the results of their analysis to business stakeholders. 5 | This is a data story about the results of that poll. 6 | 7 | 8 | 9 | 10 | 11 | Create a `vizzu-player` element that will contain the rendered story. 12 | 13 | ``` 14 | 15 | ``` 16 | 17 | In a script module element: 18 | 19 | ```javascript 20 | // {% include "./linkedinpoll/main.js" %} 21 | ``` 22 | 23 | - [Group 1](https://www.linkedin.com/groups/1859449/): AI & ML - Analytics , 24 | Data Science . SAP BI/ Analytics Cloud /Tableau /Power BI /Birst 25 | 26 | - [Group 2](https://www.linkedin.com/groups/4376214/): Artificial Intelligence, 27 | Digital Transformation Data Science, Automation, Machine Learning Analytics 28 | 29 | - [Group 3](https://www.linkedin.com/groups/6773411/): Data Scientist, Data 30 | Analyst and Data Engineer 31 | 32 | - [Group 4](https://www.linkedin.com/groups/25827/): Python Developers Community 33 | (moderated) 34 | 35 | - [Group 5](https://www.linkedin.com/groups/2064830/): Data Analytics, Data 36 | Science, Business Analytics, Business Intelligence, Data Scientist & Analyst 37 | -------------------------------------------------------------------------------- /docs/examples/linkedinpoll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/linkedinpoll.png -------------------------------------------------------------------------------- /docs/examples/linkedinpoll/linkedinpoll.csv: -------------------------------------------------------------------------------- 1 | Group,Group number,Answer,Vote percentage,Vote count,GroupVote count,GroupVote percentage,Answer percentage 2 | "Data Analytics, Data Science, Business Analytics...",Group 5,2 or more per month,39,27,70,39.0,3.8 3 | Python Developers Community (moderated),Group 4,2 or more per month,44,191,433,44.0,27 4 | "Data Scientist, Data Analyst and Data Engineer",Group 3,2 or more per month,47,66,141,47.0,9.3 5 | "Artificial Intelligence, Digital Transformation...",Group 2,2 or more per month,55,6,11,55.0,0.8 6 | "AI & ML - Analytics , Data Science...",Group 1,2 or more per month,56,29,52,56.0,4.1 7 | "Data Analytics, Data Science, Business Analytics...",Group 5,1 per month,31,22,70,31.0,3.1 8 | Python Developers Community (moderated),Group 4,1 per month,24,104,433,24.0,14.7 9 | "Data Scientist, Data Analyst and Data Engineer",Group 3,1 per month,20,28,141,20.0,4 10 | "Artificial Intelligence, Digital Transformation...",Group 2,1 per month,18,2,11,18.0,0.3 11 | "AI & ML - Analytics , Data Science...",Group 1,1 per month,12,6,52,12.0,0.8 12 | "Data Analytics, Data Science, Business Analytics...",Group 5,1-2 per quarter,14,10,70,14.0,1.4 13 | Python Developers Community (moderated),Group 4,1-2 per quarter,13,56,433,13.0,7.9 14 | "Data Scientist, Data Analyst and Data Engineer",Group 3,1-2 per quarter,16,23,141,16.0,3.3 15 | "Artificial Intelligence, Digital Transformation...",Group 2,1-2 per quarter,18,2,11,18.0,0.3 16 | "AI & ML - Analytics , Data Science...",Group 1,1-2 per quarter,17,9,52,17.0,1.3 17 | "Data Analytics, Data Science, Business Analytics...",Group 5,Never,16,11,70,16.0,1.6 18 | Python Developers Community (moderated),Group 4,Never,19,82,433,19.0,11.6 19 | "Data Scientist, Data Analyst and Data Engineer",Group 3,Never,17,24,141,17.0,3.4 20 | "Artificial Intelligence, Digital Transformation...",Group 2,Never,9,1,11,9.0,0.1 21 | "AI & ML - Analytics , Data Science...",Group 1,Never,15,8,52,15.0,1.1 -------------------------------------------------------------------------------- /docs/examples/linkedinpoll/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | 9 | // Create data object 10 | const dataLoaded = Csv2Js.csv('./linkedinpoll.csv', { 11 | units: { 'Vote percentage': '%', 'GroupVote percentage': '%', 'Answer percentage': '%' } 12 | }) 13 | dataLoaded.then((data) => { 14 | // Each slide here is a page in the final interactive story 15 | const slides = [ 16 | { 17 | style: { 18 | legend: { 19 | label: { fontSize: '1.1em' }, 20 | paddingRight: '-1em' 21 | }, 22 | plot: { 23 | marker: { label: { fontSize: '1.1em' } }, 24 | paddingLeft: '10em', 25 | xAxis: { 26 | title: { color: '#00000000' }, 27 | label: { fontSize: '1.1em' } 28 | }, 29 | yAxis: { label: { fontSize: '1.1em' } } 30 | }, 31 | logo: { width: '6em' }, 32 | fontSize: '0.8em' 33 | }, 34 | config: { 35 | x: { set: ['Vote percentage', 'Answer'] }, 36 | y: 'Group number', 37 | color: 'Answer', 38 | label: 'Vote percentage', 39 | title: 'How often do you present your findings to business stakeholders?' 40 | } 41 | }, 42 | { 43 | style: { plot: { xAxis: { label: { color: '#00000000' } } } }, 44 | config: { 45 | split: true, 46 | title: '2 or more is the most popular answer in every group' 47 | } 48 | }, 49 | { 50 | style: { 51 | plot: { 52 | marker: { label: { fontSize: '0.916667em' } } 53 | } 54 | }, 55 | config: { 56 | x: { set: ['Vote count', 'Answer'] }, 57 | label: 'Vote count', 58 | title: '61% of the votes came from one group' 59 | } 60 | }, 61 | [ 62 | { 63 | style: { plot: { yAxis: { title: { color: '#00000000' } } } }, 64 | config: { 65 | x: 'Answer', 66 | y: ['Group number', 'Vote count'], 67 | split: false, 68 | legend: 'color' 69 | } 70 | }, 71 | { 72 | style: { plot: { marker: { label: { fontSize: '1.1em' } } } }, 73 | config: { y: 'Vote count', title: 'More than 700 people voted' } 74 | } 75 | ], 76 | [ 77 | { 78 | config: { 79 | x: ['Answer percentage', 'Answer'], 80 | y: null, 81 | label: 'Answer percentage' 82 | } 83 | }, 84 | { 85 | style: { plot: { xAxis: { label: { color: '#00000000' } } } }, 86 | config: { 87 | coordSystem: 'polar', 88 | title: 'More than two-third of respondents present at least once per month' 89 | } 90 | } 91 | ] 92 | ] 93 | 94 | // Create story configuration object, add data and slides to it 95 | const story = { 96 | data, 97 | slides 98 | } 99 | 100 | // Set the size of the HTML element 101 | vp.style.cssText = 'width: 100%; height: 450px;' 102 | 103 | // Set up the created element with the configuration object 104 | vp.slides = story 105 | }) 106 | -------------------------------------------------------------------------------- /docs/examples/population.md: -------------------------------------------------------------------------------- 1 | # UN Population Forecast 2 | 3 | In this example, we explore the population of Africa between 1953-2098. On top 4 | of that, this story shows how to use the chart configuration presets. Check 5 | [Vizzu - Chart presets chapter](https://lib.vizzuhq.com/latest/tutorial/chart_presets/) 6 | and 7 | [Vizzu - Preset charts gallery](https://lib.vizzuhq.com/latest/examples/presets/) 8 | for more details on the available chart presets. 9 | 10 | 11 | 12 | 13 | 14 | Create a `vizzu-player` element that will contain the rendered story. 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | In a script module element: 21 | 22 | ```javascript 23 | // {% include "./population/main.js" %} 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/examples/population.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/population.png -------------------------------------------------------------------------------- /docs/examples/population/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | // Set the size of the HTML element 9 | vp.style.cssText = 'width: 100%; height: 400px;' 10 | // Initializing Vizzu Player 11 | const vpInitialized = vp.initializing 12 | 13 | // Create data object 14 | const dataLoaded = Csv2Js.csv('./population.csv', { dimensions: ['Year'] }) 15 | 16 | // Because using presets here, you need to 17 | // create the configuration object after initialization 18 | // (then presets are accessible via vp.Vizzu.presets) 19 | Promise.all([dataLoaded, vpInitialized]).then((results) => { 20 | const data = results[0] 21 | const chart = results[1] 22 | 23 | // Switch on the tooltip that appears 24 | // when the user hovers the mouse over a chart element 25 | chart.feature('tooltip', true) 26 | 27 | // Each slide here is a page in the final interactive story 28 | const slides = [ 29 | { 30 | filter: (record) => record.Continent === 'Africa', 31 | style: { plot: { xAxis: { label: { angle: 2.0 } } } }, 32 | config: vp.Vizzu.presets.stackedArea({ 33 | x: 'Year', 34 | y: 'Medium', 35 | stackedBy: 'Subregion', 36 | title: 'Population of Africa 1953-2098' 37 | }) 38 | }, 39 | { 40 | config: vp.Vizzu.presets.percentageArea({ 41 | x: 'Year', 42 | y: 'Medium', 43 | stackedBy: 'Subregion' 44 | }) 45 | }, 46 | { 47 | config: vp.Vizzu.presets.stream({ 48 | x: 'Year', 49 | y: 'Medium', 50 | stackedBy: 'Subregion' 51 | }) 52 | }, 53 | { 54 | config: vp.Vizzu.presets.violin({ 55 | x: 'Year', 56 | y: 'Medium', 57 | splittedBy: 'Subregion' 58 | }) 59 | } 60 | ] 61 | 62 | // Create story configuration object, add data and slides to it 63 | const story = { 64 | data, 65 | slides 66 | } 67 | 68 | // Set up the created element with the configuration object 69 | vp.slides = story 70 | }) 71 | -------------------------------------------------------------------------------- /docs/examples/proglangs.md: -------------------------------------------------------------------------------- 1 | # Popularity of Programming Languages 2 | 3 | What programming languages do data scientists use? 4 | 5 | This was one of the questions in the 6 | [State of Data Science Reports](https://www.anaconda.com/state-of-data-science-report-2022) 7 | published by Anaconda between 2020 and 2022. This data story shows the answers 8 | to this question. 9 | 10 | 11 | 12 | 13 | 14 | Create a `vizzu-player` element that will contain the rendered story. 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | In a script module element: 21 | 22 | ```javascript 23 | // {% include "./proglangs/main.js" %} 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/examples/proglangs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/proglangs.png -------------------------------------------------------------------------------- /docs/examples/proglangs/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | 9 | // Create data object 10 | const dataLoaded = Csv2Js.csv('./proglangs.csv', { dimensions: ['Year'], units: { Value: '%' } }) 11 | 12 | dataLoaded.then((data) => { 13 | // Each slide here is a page in the final interactive story 14 | const slides = [ 15 | { 16 | filter: (record) => record.Year === '2022', 17 | style: { 18 | logo: { width: '5em' }, 19 | plot: { 20 | xAxis: { title: { color: '#00000000' } }, 21 | paddingLeft: '2.5em', 22 | marker: { 23 | colorPalette: 24 | '#3DAE2BFF ' + '#00833EFF ' + '#00A19BFF ' + '#0075A9FF ' + '#003764FF', 25 | minLightness: 0, 26 | maxLightness: 0.4 27 | } 28 | }, 29 | fontSize: '0.8em' 30 | }, 31 | config: { 32 | x: ['Popularity', 'Value'], 33 | y: ['Language', 'Year', 'Lang_year'], 34 | color: 'Popularity', 35 | label: 'Value', 36 | align: 'stretch', 37 | title: 'Use of programming languages by data scientists in 2022', 38 | lightness: 'Year', 39 | legend: 'color' 40 | } 41 | }, 42 | { 43 | style: { plot: { xAxis: { label: { color: '#00000000' } } } }, 44 | config: { 45 | split: true, 46 | align: 'none', 47 | title: 'Python is always or frequently used by 58%' 48 | } 49 | }, 50 | 51 | [ 52 | { 53 | style: { plot: { xAxis: { label: { color: '#999999FF' } } } }, 54 | config: { split: false, align: 'stretch' } 55 | }, 56 | { 57 | filter: (record) => 58 | (record.Popularity === 'Always' || record.Popularity === 'Frequently') && 59 | record.Year === '2022', 60 | config: { x: { range: { max: 100 } }, align: 'none' } 61 | }, 62 | { 63 | config: { 64 | sort: 'byValue', 65 | title: 'Python & SQL are the most popular by a huge margin' 66 | } 67 | } 68 | ], 69 | [ 70 | { 71 | config: { 72 | sort: 'none', 73 | title: "Let's focus on the six languages with data since 2020" 74 | } 75 | }, 76 | { 77 | filter: (record) => 78 | (record.Popularity === 'Always' || record.Popularity === 'Frequently') && 79 | (record.Language === 'R' || 80 | record.Language === 'Python' || 81 | record.Language === 'JavaScript' || 82 | record.Language === 'Java' || 83 | record.Language === 'C#' || 84 | record.Language === 'C/C++') && 85 | record.Year === '2022' 86 | }, 87 | { 88 | config: { 89 | y: ['Lang_year', 'Year'], 90 | x: ['Popularity', 'Language', 'Value'] 91 | } 92 | } 93 | ], 94 | [ 95 | { 96 | filter: (record) => 97 | (record.Popularity === 'Always' || record.Popularity === 'Frequently') && 98 | (record.Language === 'R' || 99 | record.Language === 'Python' || 100 | record.Language === 'JavaScript' || 101 | record.Language === 'Java' || 102 | record.Language === 'C#' || 103 | record.Language === 'C/C++') && 104 | record.Year !== '2020' 105 | }, 106 | { 107 | filter: (record) => 108 | (record.Popularity === 'Always' || record.Popularity === 'Frequently') && 109 | (record.Language === 'R' || 110 | record.Language === 'Python' || 111 | record.Language === 'JavaScript' || 112 | record.Language === 'Java' || 113 | record.Language === 'C#' || 114 | record.Language === 'C/C++'), 115 | config: { 116 | title: 'C/C++, C#, Java and Javascript are gaining popularity' 117 | } 118 | } 119 | ] 120 | ] 121 | 122 | // Create story configuration object, add data and slides to it 123 | const story = { 124 | data, 125 | slides 126 | } 127 | 128 | // Set the size of the HTML element 129 | vp.style.cssText = 'width: 100%; height: 600px;' 130 | 131 | // Set up the created element with the configuration object 132 | vp.slides = story 133 | vp.initializing.then((chart) => { 134 | // Switch on the tooltip that appears 135 | // when the user hovers the mouse over a chart element 136 | chart.feature('tooltip', true) 137 | 138 | // Set a handler that prevents showing specific elements 139 | chart.on('plot-marker-label-draw', (event) => { 140 | if (event.detail.text.split('%')[0] < 5) event.preventDefault() 141 | }) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /docs/examples/proglangs/proglangs.csv: -------------------------------------------------------------------------------- 1 | Language,Popularity,Year,Lang_year,Value 2 | TypeScript,Always,2022,TypeScript '22,6 3 | TypeScript,Frequently,2022,TypeScript '22,13 4 | TypeScript,Sometimes,2022,TypeScript '22,17 5 | TypeScript,Rarely,2022,TypeScript '22,14 6 | TypeScript,Never,2022,TypeScript '22,50 7 | SQL,Always,2022,SQL '22,16 8 | SQL,Frequently,2022,SQL '22,26 9 | SQL,Sometimes,2022,SQL '22,29 10 | SQL,Rarely,2022,SQL '22,16 11 | SQL,Never,2022,SQL '22,13 12 | Rust,Always,2022,Rust '22,2 13 | Rust,Frequently,2022,Rust '22,12 14 | Rust,Sometimes,2022,Rust '22,16 15 | Rust,Rarely,2022,Rust '22,14 16 | Rust,Never,2022,Rust '22,56 17 | R,Always,2022,R '22,7 18 | R,Frequently,2022,R '22,19 19 | R,Sometimes,2022,R '22,28 20 | R,Rarely,2022,R '22,23 21 | R,Never,2022,R '22,24 22 | R,Always,2021,R '21,10 23 | R,Frequently,2021,R '21,17 24 | R,Sometimes,2021,R '21,25 25 | R,Rarely,2021,R '21,18 26 | R,Never,2021,R '21,30 27 | R,Always,2020,R '20,10 28 | R,Frequently,2020,R '20,17 29 | R,Sometimes,2020,R '20,20 30 | R,Rarely,2020,R '20,20 31 | R,Never,2020,R '20,34 32 | Python,Always,2022,Python '22,31 33 | Python,Frequently,2022,Python '22,27 34 | Python,Sometimes,2022,Python '22,22 35 | Python,Rarely,2022,Python '22,14 36 | Python,Never,2022,Python '22,6 37 | Python,Always,2021,Python '21,34 38 | Python,Frequently,2021,Python '21,29 39 | Python,Sometimes,2021,Python '21,22 40 | Python,Rarely,2021,Python '21,11 41 | Python,Never,2021,Python '21,4 42 | Python,Always,2020,Python '20,47 43 | Python,Frequently,2020,Python '20,28 44 | Python,Sometimes,2020,Python '20,16 45 | Python,Rarely,2020,Python '20,6 46 | Python,Never,2020,Python '20,4 47 | PHP,Always,2022,PHP '22,5 48 | PHP,Frequently,2022,PHP '22,13 49 | PHP,Sometimes,2022,PHP '22,20 50 | PHP,Rarely,2022,PHP '22,17 51 | PHP,Never,2022,PHP '22,45 52 | Julia,Always,2022,Julia '22,3 53 | Julia,Frequently,2022,Julia '22,12 54 | Julia,Sometimes,2022,Julia '22,17 55 | Julia,Rarely,2022,Julia '22,17 56 | Julia,Never,2022,Julia '22,51 57 | JavaScript,Always,2022,JavaScript '22,6 58 | JavaScript,Frequently,2022,JavaScript '22,20 59 | JavaScript,Sometimes,2022,JavaScript '22,26 60 | JavaScript,Rarely,2022,JavaScript '22,19 61 | JavaScript,Never,2022,JavaScript '22,28 62 | JavaScript,Always,2021,JavaScript '21,8 63 | JavaScript,Frequently,2021,JavaScript '21,16 64 | JavaScript,Sometimes,2021,JavaScript '21,24 65 | JavaScript,Rarely,2021,JavaScript '21,19 66 | JavaScript,Never,2021,JavaScript '21,33 67 | JavaScript,Always,2020,JavaScript '20,3 68 | JavaScript,Frequently,2020,JavaScript '20,12 69 | JavaScript,Sometimes,2020,JavaScript '20,20 70 | JavaScript,Rarely,2020,JavaScript '20,23 71 | JavaScript,Never,2020,JavaScript '20,42 72 | Java,Always,2022,Java '22,7 73 | Java,Frequently,2022,Java '22,18 74 | Java,Sometimes,2022,Java '22,21 75 | Java,Rarely,2022,Java '22,21 76 | Java,Never,2022,Java '22,33 77 | Java,Always,2021,Java '21,6 78 | Java,Frequently,2021,Java '21,15 79 | Java,Sometimes,2021,Java '21,22 80 | Java,Rarely,2021,Java '21,19 81 | Java,Never,2021,Java '21,38 82 | Java,Always,2020,Java '20,3 83 | Java,Frequently,2020,Java '20,7 84 | Java,Sometimes,2020,Java '20,13 85 | Java,Rarely,2020,Java '20,22 86 | Java,Never,2020,Java '20,55 87 | HTML/CSS,Always,2022,HTML/CSS '22,8 88 | HTML/CSS,Frequently,2022,HTML/CSS '22,18 89 | HTML/CSS,Sometimes,2022,HTML/CSS '22,31 90 | HTML/CSS,Rarely,2022,HTML/CSS '22,22 91 | HTML/CSS,Never,2022,HTML/CSS '22,20 92 | Go,Always,2022,Go '22,3 93 | Go,Frequently,2022,Go '22,12 94 | Go,Sometimes,2022,Go '22,18 95 | Go,Rarely,2022,Go '22,14 96 | Go,Never,2022,Go '22,53 97 | C#,Always,2022,C# '22,5 98 | C#,Frequently,2022,C# '22,14 99 | C#,Sometimes,2022,C# '22,19 100 | C#,Rarely,2022,C# '22,19 101 | C#,Never,2022,C# '22,42 102 | C#,Always,2021,C# '21,4 103 | C#,Frequently,2021,C# '21,10 104 | C#,Sometimes,2021,C# '21,19 105 | C#,Rarely,2021,C# '21,18 106 | C#,Never,2021,C# '21,49 107 | C#,Always,2020,C# '20,1 108 | C#,Frequently,2020,C# '20,3 109 | C#,Sometimes,2020,C# '20,8 110 | C#,Rarely,2020,C# '20,18 111 | C#,Never,2020,C# '20,70 112 | C/C++,Always,2022,C/C++ '22,8 113 | C/C++,Frequently,2022,C/C++ '22,18 114 | C/C++,Sometimes,2022,C/C++ '22,23 115 | C/C++,Rarely,2022,C/C++ '22,22 116 | C/C++,Never,2022,C/C++ '22,30 117 | C/C++,Always,2021,C/C++ '21,5 118 | C/C++,Frequently,2021,C/C++ '21,13 119 | C/C++,Sometimes,2021,C/C++ '21,23 120 | C/C++,Rarely,2021,C/C++ '21,25 121 | C/C++,Never,2021,C/C++ '21,34 122 | C/C++,Always,2020,C/C++ '20,2 123 | C/C++,Frequently,2020,C/C++ '20,7 124 | C/C++,Sometimes,2020,C/C++ '20,14 125 | C/C++,Rarely,2020,C/C++ '20,26 126 | C/C++,Never,2020,C/C++ '20,50 127 | Bash/Shell,Always,2022,Bash/Shell '22,9 128 | Bash/Shell,Frequently,2022,Bash/Shell '22,22 129 | Bash/Shell,Sometimes,2022,Bash/Shell '22,24 130 | Bash/Shell,Rarely,2022,Bash/Shell '22,19 131 | Bash/Shell,Never,2022,Bash/Shell '22,26 -------------------------------------------------------------------------------- /docs/examples/titanic.md: -------------------------------------------------------------------------------- 1 | # Passengers of the Titanic 2 | 3 | 4 | 5 | 6 | 7 | Create a `vizzu-player` element that will contain the rendered story. 8 | 9 | ``` 10 | 11 | ``` 12 | 13 | In a script module element: 14 | 15 | ```javascript 16 | // {% include "./titanic/main.js" %} 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/examples/titanic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/titanic.png -------------------------------------------------------------------------------- /docs/examples/titanic/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | // Set the size of the HTML element 9 | vp.style.cssText = 'width: 100%; height: 400px;' 10 | // Initializing Vizzu Player 11 | const vpInitialized = vp.initializing 12 | 13 | // Create data object 14 | const dataLoaded = Csv2Js.csv('./titanic.csv', { dimensions: ['Pclass'] }) 15 | 16 | // Because using presets here, you need to 17 | // create the configuration object after initialization 18 | // (then presets are accessible via vp.Vizzu.presets) 19 | Promise.all([dataLoaded, vpInitialized]).then((results) => { 20 | const data = results[0] 21 | const chart = results[1] 22 | 23 | // Switch on the tooltip that appears 24 | // when the user hovers the mouse over a chart element 25 | chart.feature('tooltip', true) 26 | 27 | // Set the style of the charts in the story 28 | const style = { 29 | plot: { 30 | yAxis: { 31 | label: { fontSize: '1em', paddingRight: '1.2em' }, 32 | title: { color: '#ffffff00' } 33 | }, 34 | xAxis: { 35 | label: { 36 | angle: '2.5', 37 | fontSize: '1.1em', 38 | paddingRight: '0em', 39 | paddingTop: '1em' 40 | }, 41 | title: { fontSize: '1em', paddingTop: '2.5em' } 42 | } 43 | }, 44 | logo: { width: '5em' } 45 | } 46 | 47 | // Each slide here is a page in the final interactive story 48 | const slides = [ 49 | [ 50 | { 51 | config: vp.Vizzu.presets.bar({ 52 | x: 'Count', 53 | title: 'Passengers of the Titanic' 54 | }) 55 | } 56 | ], 57 | [ 58 | { config: vp.Vizzu.presets.stackedBar({ x: 'Count', stackedBy: 'Sex' }) }, 59 | { 60 | config: vp.Vizzu.presets.groupedBar({ 61 | x: 'Count', 62 | y: 'Sex', 63 | legend: 'color', 64 | title: 'Rougly one-third of the passengers were ladies' 65 | }) 66 | } 67 | ], 68 | [ 69 | { 70 | config: { 71 | x: ['Count', 'Survived'], 72 | y: 'Sex', 73 | color: 'Sex', 74 | lightness: 'Survived', 75 | label: ['Survived', 'Count'] 76 | } 77 | }, 78 | { 79 | config: { 80 | align: 'stretch', 81 | title: 'Much more women survived than men' 82 | } 83 | } 84 | ], 85 | [ 86 | { 87 | config: { 88 | x: 'Count', 89 | align: 'none', 90 | label: null, 91 | lightness: null, 92 | title: "Let's add the age of the passengers to the mix" 93 | } 94 | }, 95 | { config: { y: ['Count', 'Sex'], x: 'Age_group', label: 'Count' } } 96 | ], 97 | [ 98 | { 99 | config: { 100 | label: null, 101 | title: "Let's see how many people survived/died in these age groups" 102 | } 103 | }, 104 | { 105 | config: { 106 | y: ['Count', 'Sex', 'Survived'], 107 | lightness: 'Survived', 108 | legend: 'lightness' 109 | } 110 | }, 111 | { config: { y: ['Count', 'Survived', 'Sex'] } } 112 | ], 113 | [ 114 | { 115 | config: { 116 | align: 'stretch', 117 | title: 'Survival rate varies a bit between age groups' 118 | } 119 | } 120 | ], 121 | [ 122 | { 123 | filter: (record) => record.Sex === 'male', 124 | config: { 125 | title: 'But again shows a very different picture for men...' 126 | } 127 | } 128 | ], 129 | [ 130 | { filter: null }, 131 | { 132 | filter: (record) => record.Sex === 'female', 133 | config: { title: '...and women' } 134 | } 135 | ] 136 | ] 137 | 138 | // Create story configuration object, add data and slides to it 139 | const story = { 140 | data, 141 | style, 142 | slides 143 | } 144 | 145 | // Set up the created element with the configuration object 146 | vp.slides = story 147 | }) 148 | -------------------------------------------------------------------------------- /docs/examples/trumptwitter.md: -------------------------------------------------------------------------------- 1 | # Trump Twitter Tirade 2 | 3 | 4 | 5 | 6 | 7 | Create a `vizzu-player` element that will contain the rendered story. 8 | 9 | ``` 10 | 11 | ``` 12 | 13 | In a script module element: 14 | 15 | ```javascript 16 | // {% include "./trumptwitter/main.js" %} 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/examples/trumptwitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/trumptwitter.png -------------------------------------------------------------------------------- /docs/examples/trumptwitter/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | 9 | // Create data object 10 | const dataLoaded = Csv2Js.csv('./trumptwitter.csv', { 11 | measures: [ 12 | 'NewtoTwitter', 13 | 'Businessman', 14 | 'Nominee', 15 | 'President', 16 | 'RT_New to Twitter', 17 | 'RT_Businessman', 18 | 'RT_Nominee', 19 | 'RT_President', 20 | 'Obama' 21 | ] 22 | }) 23 | 24 | dataLoaded.then((data) => { 25 | // Set the style of the charts in the story 26 | const style = { 27 | tooltip: { fontSize: '22px' }, 28 | title: { paddingTop: '1.2em', fontSize: '2.5em' }, 29 | legend: { label: { fontSize: '1.8em' }, width: '16em' }, 30 | logo: { width: '6em' }, 31 | plot: { 32 | marker: { label: { fontSize: '1.5em' } }, 33 | yAxis: { 34 | label: { fontSize: '1.5em' }, 35 | title: { color: '#ffffff00' }, 36 | interlacing: { color: '#ffffff00' } 37 | }, 38 | xAxis: { 39 | label: { fontSize: '1.6em', paddingTop: '1em' }, 40 | title: { fontSize: '1.4em', paddingTop: '2.5em' } 41 | } 42 | } 43 | } 44 | 45 | // Each slide here is a page in the final interactive story 46 | const slides = [ 47 | [ 48 | { 49 | filter: (record) => record.Firsttweet === 'Yes' && record.Dummy === 'No', 50 | config: { 51 | channels: { 52 | y: { set: ['tweets'] }, 53 | x: { set: ['Period', 'year', 'month'] }, 54 | color: 'Period' 55 | }, 56 | title: "Trump started tweeting in May '09" 57 | } 58 | } 59 | ], 60 | [ 61 | { 62 | filter: (record) => record.Period === 'New to Twitter' && record.Dummy === 'No', 63 | config: { title: "In the first two years he wasn't very active" } 64 | } 65 | ], 66 | [ 67 | { 68 | filter: (record) => 69 | (record.Period === 'New to Twitter' || record.Period === 'Businessman') && 70 | record.Dummy === 'No', 71 | config: { title: 'Then he got hooked on' } 72 | } 73 | ], 74 | [ 75 | { 76 | filter: (record) => 77 | (record.Period === 'New to Twitter' || 78 | record.Period === 'Businessman' || 79 | record.Period === 'Nominee') && 80 | record.Dummy === 'No', 81 | config: { 82 | title: 'Interesting trend after becoming a presidential nominee' 83 | } 84 | } 85 | ], 86 | [ 87 | { 88 | filter: (record) => record.Dummy === 'No', 89 | config: { title: 'And after he became President' } 90 | } 91 | ], 92 | [ 93 | { config: { geometry: 'area', align: 'center' } }, 94 | { config: { title: "All of Trump's tweets until May 2020" } } 95 | ], 96 | [ 97 | { 98 | config: { 99 | y: 'retweetcount', 100 | title: 'And the number of times these were retweeted' 101 | } 102 | } 103 | ], 104 | [ 105 | { 106 | config: { 107 | y: 'tweets', 108 | title: "Let's focus on the number of tweets for now" 109 | } 110 | }, 111 | { config: { x: { set: ['year', 'month'] }, color: null } } 112 | ], 113 | [ 114 | { 115 | config: { 116 | y: ['tweets', 'Type'], 117 | color: 'Type', 118 | title: 'Original tweets, retweets & replies sent' 119 | }, 120 | style: { 121 | plot: { marker: { colorPalette: '#A0CDEBFF #60C0E6FF #1DA1F3FF' } } 122 | } 123 | } 124 | ], 125 | [ 126 | { 127 | config: { split: true, align: 'none' }, 128 | style: { plot: { yAxis: { label: { color: '#ffffff00' } } } } 129 | } 130 | ], 131 | [ 132 | { 133 | config: { 134 | split: false, 135 | align: 'stretch', 136 | title: 'Original tweets, retweets & replies sent (%)' 137 | }, 138 | style: { plot: { yAxis: { label: { color: '#999999ff' } } } } 139 | } 140 | ], 141 | [ 142 | { config: { align: 'center', title: '' } }, 143 | { 144 | config: { y: 'tweets', color: null, legend: 'lightness' }, 145 | style: { plot: { marker: { colorPalette: 'null' } } } 146 | }, 147 | { 148 | config: { 149 | y: ['tweets', 'Tool'], 150 | color: 'Tool', 151 | title: 'Tools Trump Used to Tweet', 152 | legend: 'color' 153 | }, 154 | style: { 155 | plot: { 156 | marker: { 157 | colorPalette: '#597696FF #ED2828FF #26EC87FF #29B9BFFF ' 158 | } 159 | } 160 | } 161 | } 162 | ], 163 | [ 164 | { 165 | config: { split: true, align: 'none' }, 166 | style: { plot: { yAxis: { label: { color: '#ffffff00' } } } } 167 | } 168 | ], 169 | [ 170 | { config: { geometry: 'rectangle' } }, 171 | { 172 | config: { 173 | x: ['tweets', 'year', 'month'], 174 | y: 'Tool', 175 | geometry: 'rectangle', 176 | split: false, 177 | align: 'none' 178 | }, 179 | style: { 180 | plot: { 181 | xAxis: { title: { color: '#ffffff00' } }, 182 | yAxis: { label: { color: '#999999ff' } } 183 | } 184 | } 185 | }, 186 | { config: { x: 'tweets', label: 'tweets' } } 187 | ], 188 | [ 189 | { config: { x: ['tweets', 'AMPM', 'hour12'], label: null } }, 190 | { 191 | config: { 192 | y: { set: ['tweets', 'Tool'], range: { min: '110%', max: '0%' } }, 193 | x: ['AMPM', 'hour12'] 194 | } 195 | }, 196 | { config: { geometry: 'area' } }, 197 | { 198 | config: { 199 | coordSystem: 'polar', 200 | angle: Math.PI, 201 | title: 'Time of Day When Trump Tweeted' 202 | }, 203 | style: { 204 | plot: { 205 | yAxis: { label: { color: '#ffffff00' } }, 206 | xAxis: { 207 | label: { 208 | fontSize: '2em', 209 | paddingBottom: '2.5em', 210 | paddingTop: '2.5em', 211 | paddingLeft: '2.5em', 212 | paddingRight: '2.5em' 213 | } 214 | } 215 | } 216 | } 217 | } 218 | ], 219 | [ 220 | { 221 | config: { 222 | y: ['Businessman', 'Tool'], 223 | title: 'Times Trump Tweeted When Being a Businessman' 224 | } 225 | } 226 | ], 227 | [ 228 | { 229 | config: { 230 | y: ['President', 'Tool'], 231 | title: 'Times Trump Tweeted When Being President' 232 | } 233 | } 234 | ] 235 | ] 236 | 237 | // Create story configuration object, add data and slides to it 238 | const story = { 239 | data, 240 | style, 241 | slides 242 | } 243 | 244 | // Set the size of the HTML element 245 | vp.style.cssText = 'width: 100%; height: 400px;' 246 | 247 | // Set up the created element with the configuration object 248 | vp.slides = story 249 | vp.initializing.then((chart) => { 250 | // Switch on the tooltip that appears 251 | // when the user hovers the mouse over a chart element 252 | chart.feature('tooltip', true) 253 | }) 254 | }) 255 | -------------------------------------------------------------------------------- /docs/examples/usbudget.md: -------------------------------------------------------------------------------- 1 | # US Federal R&D budget 2 | 3 | US Federal R&D budget In this more involved example, we explore the history of 4 | the US Federal R&D budget between 1955-2020. On top of the base functionality, 5 | this story showcases: 6 | 7 | - Styling the overall story 8 | - Setting the size of the story 9 | - Slides with multiple steps 10 | 11 | 12 | 13 | 14 | 15 | Create a `vizzu-player` element that will contain the rendered story. 16 | 17 | ``` 18 | 19 | ``` 20 | 21 | In a script module element: 22 | 23 | ```javascript 24 | // {% include "./usbudget/main.js" %} 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/examples/usbudget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vizzuhq/vizzu-story-js/d6c2c87f608015a66272b8d6c1056d403a4ccf86/docs/examples/usbudget.png -------------------------------------------------------------------------------- /docs/examples/usbudget/main.js: -------------------------------------------------------------------------------- 1 | // Import VizzuPlayer 2 | // eslint-disable-next-line no-unused-vars 3 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 4 | import Csv2Js from '../../assets/javascripts/csv2js.js' 5 | 6 | // Get the created element 7 | const vp = document.querySelector('vizzu-player') 8 | 9 | // Create data object 10 | const dataLoaded = Csv2Js.csv('./usbudget.csv', { dimensions: ['Year'], units: { Amount: 'B$' } }) 11 | 12 | dataLoaded.then((data) => { 13 | // Set the style of the charts in the story 14 | 15 | const style = { 16 | plot: { 17 | yAxis: { 18 | label: { 19 | fontSize: '1em', 20 | paddingRight: '1.2em' 21 | }, 22 | title: { color: '#ffffff00' } 23 | }, 24 | xAxis: { 25 | label: { 26 | angle: '2.5', 27 | fontSize: '1.1em', 28 | paddingRight: '0em', 29 | paddingTop: '1em' 30 | }, 31 | title: { fontSize: '0.8em', paddingTop: '2.5em' } 32 | } 33 | }, 34 | logo: { width: '5em' } 35 | } 36 | 37 | // Each slide here is a page in the final interactive story 38 | const slides = [ 39 | // Add the first slide, 40 | // containing a single animation step that sets the initial chart 41 | { 42 | // Only include rows where the Function value != Defense 43 | filter: (record) => record.Function !== 'Defense', 44 | config: { 45 | channels: { 46 | y: { 47 | set: ['Amount', 'Function'], 48 | // Set the range of the y-axis 49 | // to the min and max of the data being shown 50 | // default value is 110% of the maximum value 51 | range: { min: '0%', max: '100%' } 52 | }, 53 | x: { set: ['Year'] }, 54 | color: 'Function' 55 | }, 56 | title: 'Stacked Area Chart - U.S. R&D Budget in 1955-2020', 57 | geometry: 'area' 58 | } 59 | }, 60 | // Show components side-by-side 61 | { 62 | config: { 63 | split: true, 64 | title: 'Show Components Side by Side' 65 | } 66 | }, 67 | // This slide contains multiple steps 68 | [ 69 | // Step 1 - let's get back to the previous view 70 | { config: { split: false } }, 71 | // Step 2 - Add the defense function to the chart by removing it from the filter 72 | { 73 | filter: null, 74 | config: { title: 'Add New Category While Keeping the Context' } 75 | } 76 | ], 77 | // Show share of components 78 | { 79 | config: { 80 | align: 'stretch', 81 | title: 'Show Share of Components (%)' 82 | } 83 | }, 84 | // Compare data from 1955 and 2020 85 | [ 86 | // Step 1 - switch back to value instead of percentage 87 | { config: { align: 'none' } }, 88 | // Step 2 - switch to a stacked column chart by changing the geometry 89 | { 90 | config: { 91 | geometry: 'rectangle' 92 | } 93 | }, 94 | // Step 3 - zoom to data from the first and last years 95 | { 96 | filter: (record) => record.Year === '1955' || record.Year === '2020', 97 | config: { 98 | title: 'Zoom to Specific Elements' 99 | } 100 | } 101 | ], 102 | // Group & rearrange elements for comparison 103 | [ 104 | { 105 | config: { 106 | x: ['Year', 'Function'], 107 | y: 'Amount', 108 | label: 'Amount', 109 | title: 'Group & Rearrange for Better Comparison' 110 | } 111 | }, 112 | { 113 | config: { x: ['Function', 'Year'] } 114 | } 115 | ] 116 | ] 117 | 118 | // Create story configuration object, add data, style and slides to it 119 | const story = { 120 | data, 121 | style, 122 | slides 123 | } 124 | 125 | // Set the size of the HTML element 126 | vp.style.cssText = 'width: 100%; height: 400px;' 127 | 128 | // Set up the created element with the configuration object 129 | vp.slides = story 130 | vp.initializing.then((chart) => { 131 | // Switch on the tooltip that appears 132 | // when the user hovers the mouse over a chart element 133 | chart.feature('tooltip', true) 134 | 135 | // Set a handler that prevents showing the year values that are not divisible by 5 136 | chart.on('plot-axis-label-draw', (event) => { 137 | const Year = parseFloat(event.detail.text) 138 | if (!isNaN(Year) && Year > 1950 && Year < 2020 && Year % 5 !== 0) { 139 | event.preventDefault() 140 | } 141 | }) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /docs/examples/usbudget/usbudget.csv: -------------------------------------------------------------------------------- 1 | Year,Function,Amount 2 | 1955,Health,0.54 3 | 1955,Space,0.34 4 | 1955,Science,0.63 5 | 1955,Energy,0.21 6 | 1955,Nat. Res.,0.25 7 | 1955,Other,0.72 8 | 1955,Defense,12.08 9 | 1956,Health,0.62 10 | 1956,Space,0.38 11 | 1956,Science,0.71 12 | 1956,Energy,0.32 13 | 1956,Nat. Res.,0.29 14 | 1956,Other,0.94 15 | 1956,Defense,14.41 16 | 1957,Health,0.98 17 | 1957,Space,0.4 18 | 1957,Science,0.87 19 | 1957,Energy,0.56 20 | 1957,Nat. Res.,0.33 21 | 1957,Other,1.08 22 | 1957,Defense,15.63 23 | 1958,Health,1.12 24 | 1958,Space,0.51 25 | 1958,Science,1.12 26 | 1958,Energy,0.77 27 | 1958,Nat. Res.,0.38 28 | 1958,Other,1.28 29 | 1958,Defense,17.48 30 | 1959,Health,1.51 31 | 1959,Space,0.81 32 | 1959,Science,1.43 33 | 1959,Energy,0.9 34 | 1959,Nat. Res.,0.62 35 | 1959,Other,1.88 36 | 1959,Defense,37.55 37 | 1960,Health,1.91 38 | 1960,Space,2.28 39 | 1960,Science,1.66 40 | 1960,Energy,1.1 41 | 1960,Nat. Res.,0.47 42 | 1960,Other,2.15 43 | 1960,Defense,40.99 44 | 1961,Health,2.25 45 | 1961,Space,4.4 46 | 1961,Science,1.94 47 | 1961,Energy,1.18 48 | 1961,Nat. Res.,0.72 49 | 1961,Other,2.21 50 | 1961,Defense,47.17 51 | 1962,Health,2.93 52 | 1962,Space,7.5 53 | 1962,Science,2.12 54 | 1962,Energy,2.68 55 | 1962,Nat. Res.,0.67 56 | 1962,Other,2.62 57 | 1962,Defense,47.83 58 | 1963,Health,3.67 59 | 1963,Space,15.28 60 | 1963,Science,2.43 61 | 1963,Energy,3.08 62 | 1963,Nat. Res.,0.79 63 | 1963,Other,2.9 64 | 1963,Defense,47.63 65 | 1964,Health,4.63 66 | 1964,Space,24.31 67 | 1964,Science,2.86 68 | 1964,Energy,3.3 69 | 1964,Nat. Res.,0.79 70 | 1964,Other,3.15 71 | 1964,Defense,51.78 72 | 1965,Health,3.82 73 | 1965,Space,29.1 74 | 1965,Science,2.95 75 | 1965,Energy,3.09 76 | 1965,Nat. Res.,0.91 77 | 1965,Other,3.9 78 | 1965,Defense,45.8 79 | 1966,Health,4.68 80 | 1966,Space,33.42 81 | 1966,Science,3.26 82 | 1966,Energy,2.86 83 | 1966,Nat. Res.,1.01 84 | 1966,Other,4.31 85 | 1966,Defense,44.97 86 | 1967,Health,5.56 87 | 1967,Space,31 88 | 1967,Science,3.49 89 | 1967,Energy,2.94 90 | 1967,Nat. Res.,1.11 91 | 1967,Other,4.43 92 | 1967,Defense,49.61 93 | 1968,Health,5.84 94 | 1968,Space,26.51 95 | 1968,Science,3.14 96 | 1968,Energy,3.06 97 | 1968,Nat. Res.,1.23 98 | 1968,Other,5.54 99 | 1968,Defense,50.77 100 | 1969,Health,6.1 101 | 1969,Space,22.87 102 | 1969,Science,3.78 103 | 1969,Energy,2.67 104 | 1969,Nat. Res.,1.32 105 | 1969,Other,5.01 106 | 1969,Defense,47.25 107 | 1970,Health,5.79 108 | 1970,Space,18.98 109 | 1970,Science,3.7 110 | 1970,Energy,2.43 111 | 1970,Nat. Res.,1.62 112 | 1970,Other,5.96 113 | 1970,Defense,43.27 114 | 1971,Health,5.76 115 | 1971,Space,16.13 116 | 1971,Science,3.67 117 | 1971,Energy,2.33 118 | 1971,Nat. Res.,1.8 119 | 1971,Other,7.78 120 | 1971,Defense,41.62 121 | 1972,Health,6.74 122 | 1972,Space,14.99 123 | 1972,Science,3.81 124 | 1972,Energy,1.61 125 | 1972,Nat. Res.,2.48 126 | 1972,Other,6.95 127 | 1972,Defense,43.31 128 | 1973,Health,7.79 129 | 1973,Space,14.3 130 | 1973,Science,3.78 131 | 1973,Energy,1.78 132 | 1973,Nat. Res.,2.53 133 | 1973,Other,6.9 134 | 1973,Defense,42.92 135 | 1974,Health,7.27 136 | 1974,Space,12.71 137 | 1974,Science,3.64 138 | 1974,Energy,2.3 139 | 1974,Nat. Res.,2.24 140 | 1974,Other,7.03 141 | 1974,Defense,41.23 142 | 1975,Health,7.51 143 | 1975,Space,11.43 144 | 1975,Science,3.5 145 | 1975,Energy,3.71 146 | 1975,Nat. Res.,2.57 147 | 1975,Other,6.34 148 | 1975,Defense,38.6 149 | 1976,Health,8.51 150 | 1976,Space,11.99 151 | 1976,Science,3.27 152 | 1976,Energy,5.29 153 | 1976,Nat. Res.,2.4 154 | 1976,Other,6.32 155 | 1976,Defense,36.48 156 | 1977,Health,5.71 157 | 1977,Space,12.15 158 | 1977,Science,3.24 159 | 1977,Energy,7.61 160 | 1977,Nat. Res.,2.03 161 | 1977,Other,5.88 162 | 1977,Defense,37.67 163 | 1978,Health,8.97 164 | 1978,Space,11.21 165 | 1978,Science,3.16 166 | 1978,Energy,8.25 167 | 1978,Nat. Res.,2.19 168 | 1978,Other,6.64 169 | 1978,Defense,39.2 170 | 1979,Health,9.73 171 | 1979,Space,10.98 172 | 1979,Science,3.24 173 | 1979,Energy,9.92 174 | 1979,Nat. Res.,2.69 175 | 1979,Other,6.07 176 | 1979,Defense,36.43 177 | 1980,Health,10.17 178 | 1980,Space,11.77 179 | 1980,Science,3.27 180 | 1980,Energy,9.09 181 | 1980,Nat. Res.,2.63 182 | 1980,Other,6.15 183 | 1980,Defense,40.45 184 | 1981,Health,10.27 185 | 1981,Space,12.07 186 | 1981,Science,3.52 187 | 1981,Energy,9.26 188 | 1981,Nat. Res.,2.29 189 | 1981,Other,5.92 190 | 1981,Defense,42.6 191 | 1982,Health,10.21 192 | 1982,Space,6.34 193 | 1982,Science,3.34 194 | 1982,Energy,7.83 195 | 1982,Nat. Res.,1.97 196 | 1982,Other,5.23 197 | 1982,Defense,46.59 198 | 1983,Health,10.05 199 | 1983,Space,4.5 200 | 1983,Science,3.29 201 | 1983,Energy,6.15 202 | 1983,Nat. Res.,1.93 203 | 1983,Other,4.72 204 | 1983,Defense,50.25 205 | 1984,Health,9.92 206 | 1984,Space,6.58 207 | 1984,Science,3.55 208 | 1984,Energy,6.01 209 | 1984,Nat. Res.,1.84 210 | 1984,Other,5.22 211 | 1984,Defense,56.06 212 | 1985,Health,10.8 213 | 1985,Space,5.07 214 | 1985,Science,3.56 215 | 1985,Energy,8.95 216 | 1985,Nat. Res.,1.86 217 | 1985,Other,5.24 218 | 1985,Defense,63.92 219 | 1986,Health,11.48 220 | 1986,Space,5.9 221 | 1986,Science,3.93 222 | 1986,Energy,5.4 223 | 1986,Nat. Res.,1.9 224 | 1986,Other,5.34 225 | 1986,Defense,73.42 226 | 1987,Health,11.7 227 | 1987,Space,5.42 228 | 1987,Science,4.03 229 | 1987,Energy,4.67 230 | 1987,Nat. Res.,1.78 231 | 1987,Other,4.93 232 | 1987,Defense,74.72 233 | 1988,Health,13.41 234 | 1988,Space,6.34 235 | 1988,Science,4.16 236 | 1988,Energy,4.46 237 | 1988,Nat. Res.,2.05 238 | 1988,Other,4.83 239 | 1988,Defense,74.2 240 | 1989,Health,14.15 241 | 1989,Space,7.91 242 | 1989,Science,4.22 243 | 1989,Energy,4.6 244 | 1989,Nat. Res.,1.98 245 | 1989,Other,5.38 246 | 1989,Defense,75.69 247 | 1990,Health,14.93 248 | 1990,Space,10.17 249 | 1990,Science,4.17 250 | 1990,Energy,4.24 251 | 1990,Nat. Res.,2.21 252 | 1990,Other,5.41 253 | 1990,Defense,74.3 254 | 1991,Health,14.89 255 | 1991,Space,10.96 256 | 1991,Science,4.31 257 | 1991,Energy,4.37 258 | 1991,Nat. Res.,2.31 259 | 1991,Other,5.59 260 | 1991,Defense,66.17 261 | 1992,Health,16.45 262 | 1992,Space,11.32 263 | 1992,Science,4.29 264 | 1992,Energy,4.42 265 | 1992,Nat. Res.,2.71 266 | 1992,Other,6.06 267 | 1992,Defense,65.04 268 | 1993,Health,17.27 269 | 1993,Space,11.66 270 | 1993,Science,4.23 271 | 1993,Energy,4.19 272 | 1993,Nat. Res.,2.93 273 | 1993,Other,6.3 274 | 1993,Defense,67.26 275 | 1994,Health,17.32 276 | 1994,Space,10.86 277 | 1994,Science,4.14 278 | 1994,Energy,4.32 279 | 1994,Nat. Res.,2.85 280 | 1994,Other,6.78 281 | 1994,Defense,62.01 282 | 1995,Health,17.96 283 | 1995,Space,13.15 284 | 1995,Science,4.14 285 | 1995,Energy,5.03 286 | 1995,Nat. Res.,2.65 287 | 1995,Other,6.1 288 | 1995,Defense,60.15 289 | 1996,Health,16.44 290 | 1996,Space,10.9 291 | 1996,Science,4.36 292 | 1996,Energy,4.6 293 | 1996,Nat. Res.,2.52 294 | 1996,Other,6.61 295 | 1996,Defense,61.75 296 | 1997,Health,17.63 297 | 1997,Space,12.52 298 | 1997,Science,4.18 299 | 1997,Energy,4.06 300 | 1997,Nat. Res.,2.45 301 | 1997,Other,6.7 302 | 1997,Defense,61.82 303 | 1998,Health,19.33 304 | 1998,Space,13.12 305 | 1998,Science,5.88 306 | 1998,Energy,2.32 307 | 1998,Nat. Res.,2.51 308 | 1998,Other,6.48 309 | 1998,Defense,61 310 | 1999,Health,21.51 311 | 1999,Space,12.48 312 | 1999,Science,6.35 313 | 1999,Energy,1.93 314 | 1999,Nat. Res.,2.6 315 | 1999,Other,5.95 316 | 1999,Defense,60.43 317 | 2000,Health,23.34 318 | 2000,Space,7.96 319 | 2000,Science,6.9 320 | 2000,Energy,1.86 321 | 2000,Nat. Res.,2.48 322 | 2000,Other,5.83 323 | 2000,Defense,60.35 324 | 2001,Health,26.13 325 | 2001,Space,8.06 326 | 2001,Science,6.97 327 | 2001,Energy,1.92 328 | 2001,Nat. Res.,2.51 329 | 2001,Other,6.04 330 | 2001,Defense,63.41 331 | 2002,Health,29.94 332 | 2002,Space,8.22 333 | 2002,Science,7.21 334 | 2002,Energy,1.69 335 | 2002,Nat. Res.,2.62 336 | 2002,Other,6.4 337 | 2002,Defense,68.19 338 | 2003,Health,32.84 339 | 2003,Space,8.33 340 | 2003,Science,7.93 341 | 2003,Energy,1.84 342 | 2003,Nat. Res.,2.66 343 | 2003,Other,7.63 344 | 2003,Defense,79.59 345 | 2004,Health,35.56 346 | 2004,Space,10.9 347 | 2004,Science,8.33 348 | 2004,Energy,1.88 349 | 2004,Nat. Res.,2.19 350 | 2004,Other,6.28 351 | 2004,Defense,88.6 352 | 2005,Health,36.29 353 | 2005,Space,9.05 354 | 2005,Science,8.48 355 | 2005,Energy,1.67 356 | 2005,Nat. Res.,2.47 357 | 2005,Other,6.77 358 | 2005,Defense,92.95 359 | 2006,Health,36.04 360 | 2006,Space,8.68 361 | 2006,Science,8.51 362 | 2006,Energy,1.47 363 | 2006,Nat. Res.,1.95 364 | 2006,Other,6.79 365 | 2006,Defense,93.12 366 | 2007,Health,35.81 367 | 2007,Space,10.56 368 | 2007,Science,8.29 369 | 2007,Energy,1.55 370 | 2007,Nat. Res.,2.06 371 | 2007,Other,7.02 372 | 2007,Defense,95.66 373 | 2008,Health,36.07 374 | 2008,Space,12.46 375 | 2008,Science,8.25 376 | 2008,Energy,1.48 377 | 2008,Nat. Res.,2.31 378 | 2008,Other,6.75 379 | 2008,Defense,96.79 380 | 2009,Health,37.05 381 | 2009,Space,11.01 382 | 2009,Science,8.75 383 | 2009,Energy,2.22 384 | 2009,Nat. Res.,2.32 385 | 2009,Other,7.06 386 | 2009,Defense,99.68 387 | 2010,Health,41.09 388 | 2010,Space,9.49 389 | 2010,Science,9.78 390 | 2010,Energy,2.34 391 | 2010,Nat. Res.,2.21 392 | 2010,Other,6.4 393 | 2010,Defense,96.65 394 | 2011,Health,41.67 395 | 2011,Space,9.14 396 | 2011,Science,10.84 397 | 2011,Energy,4.3 398 | 2011,Nat. Res.,2.19 399 | 2011,Other,6.59 400 | 2011,Defense,93.1 401 | 2012,Health,39.03 402 | 2012,Space,11.5 403 | 2012,Science,10.48 404 | 2012,Energy,3.46 405 | 2012,Nat. Res.,2.36 406 | 2012,Other,6.17 407 | 2012,Defense,86.15 408 | 2013,Health,36.45 409 | 2013,Space,11.96 410 | 2013,Science,10.4 411 | 2013,Energy,2.29 412 | 2013,Nat. Res.,2.14 413 | 2013,Other,5.87 414 | 2013,Defense,80.08 415 | 2014,Health,34.01 416 | 2014,Space,11.91 417 | 2014,Science,10.28 418 | 2014,Energy,2.61 419 | 2014,Nat. Res.,2.14 420 | 2014,Other,6.14 421 | 2014,Defense,77.97 422 | 2015,Health,33.77 423 | 2015,Space,12.2 424 | 2015,Science,10.07 425 | 2015,Energy,3.04 426 | 2015,Nat. Res.,2.27 427 | 2015,Other,5.64 428 | 2015,Defense,77.25 429 | 2016,Health,33.63 430 | 2016,Space,12.36 431 | 2016,Science,10.25 432 | 2016,Energy,3.01 433 | 2016,Nat. Res.,2.41 434 | 2016,Other,5.91 435 | 2016,Defense,78.66 436 | 2017,Health,36 437 | 2017,Space,10.71 438 | 2017,Science,10.23 439 | 2017,Energy,3.1 440 | 2017,Nat. Res.,2.54 441 | 2017,Other,5.83 442 | 2017,Defense,54.62 443 | 2018,Health,35.8 444 | 2018,Space,10.52 445 | 2018,Science,9.93 446 | 2018,Energy,3.13 447 | 2018,Nat. Res.,2.41 448 | 2018,Other,5.98 449 | 2018,Defense,51.09 450 | 2019,Health,37.29 451 | 2019,Space,9.99 452 | 2019,Science,9.97 453 | 2019,Energy,3.56 454 | 2019,Nat. Res.,2.4 455 | 2019,Other,5.79 456 | 2019,Defense,57.06 457 | 2020,Health,38.61 458 | 2020,Space,12.27 459 | 2020,Science,10.52 460 | 2020,Energy,3.46 461 | 2020,Nat. Res.,2.48 462 | 2020,Other,6.49 463 | 2020,Defense,65.81 464 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | !!! info 4 | 5 | `Vizzu-Story` requires and downloads the 6 | [Vizzu](https://github.com/vizzuhq/vizzu-lib) `JavaScript`/`C++` 7 | [library](https://www.jsdelivr.com/package/npm/vizzu) from `jsDelivr CDN`, but 8 | you can also use a different or self-hosted version of it. Check 9 | [Initialization chapter](./tutorial/initialization.md#vizzu-url) for more 10 | details. 11 | 12 | Install via [npm](https://www.npmjs.com/package/vizzu-story): 13 | 14 | ```sh 15 | npm install vizzu-story 16 | ``` 17 | 18 | Or use it from [CDN](https://www.jsdelivr.com/package/npm/vizzu-story): 19 | 20 | ```javascript 21 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/reference/README.md: -------------------------------------------------------------------------------- 1 | # Vizzu-Story JS Library Reference 2 | 3 | This is the API reference document of the `Vizzu-Story` `JS` library. It 4 | provides information about every detail of the API. This works best for users 5 | who already have a basic understanding of the `Vizzu-Story` library and its 6 | logic. 7 | 8 | In case you're just getting started with `Vizzu-Story`, we strongly recommend 9 | visiting our [Tutorial](../tutorial/index.md) first. 10 | 11 | ## Library Overview 12 | 13 | The main entry point of the library is the 14 | [VizzuPlayer](./classes/VizzuPlayer.md) class, and its most important component 15 | is the [slides](./classes/VizzuPlayer.md#slides) accessor. 16 | 17 | - [VizzuPlayer](./classes/VizzuPlayer.md) class 18 | - [constructor()](./classes/VizzuPlayer.md#constructors) 19 | - [slides](./classes/VizzuPlayer.md#slides) ([Story](./interfaces/Story.md)) : 20 | `void` 21 | 22 | ## Details 23 | 24 | You can find all interface declarations and types under these namespaces. 25 | 26 | - [Story](./interfaces/Story.md) - Contains slides, underlying data and chart's 27 | style settings 28 | - [Phase](./interfaces/Phase.md) - A single step of a slide 29 | -------------------------------------------------------------------------------- /docs/tutorial/building_blocks.md: -------------------------------------------------------------------------------- 1 | # Building blocks 2 | 3 | In `Vizzu-Story`, you can build and show a `story` object that contains all of 4 | the data being shown throughout the story and the charts created based on that 5 | data, arranged into `slides` and `steps`. 6 | 7 | ## Slides and steps 8 | 9 | Create the data story by defining a sequence of slides. A slide can be a single 10 | chart corresponding to an [animate](https://lib.vizzuhq.com/latest/tutorial/) 11 | call from `Vizzu`. Or a slide can be a sequence of animation calls, in which 12 | case all of these animations will be played until the last one in the sequence, 13 | allowing for more complex transitions between slides. 14 | 15 | ```javascript 16 | const slides = [ 17 | // This slide contains a single animation step 18 | { 19 | config: { 20 | x: 'Foo', 21 | y: 'Bar' 22 | } 23 | }, 24 | // This slide contains multiple steps 25 | [{ 26 | config: { 27 | color: 'Foo', 28 | x: 'Baz', 29 | geometry: 'circle' 30 | } 31 | }, { 32 | config: { 33 | color: 'Foo', 34 | x: 'Baz', 35 | geometry: 'rectangle' 36 | } 37 | }], 38 | ] 39 | ``` 40 | 41 | Navigation controls beneath the chart will navigate between the slides. You can 42 | use the `PgUp` and `PgDn` buttons, left and right arrows to navigate between 43 | slides, and the `Home` and `End` buttons to jump to the first or last slide. 44 | 45 | On each chart, you can define the chart configuration and style with the same 46 | objects as in `Vizzu`. However, you can not modify the underlying data between 47 | the slides, only the data filter used. 48 | 49 | ```typescript 50 | interface Chart { 51 | config?: Vizzu.Config.Chart 52 | filter?: Vizzu.Data.FilterCallback | null 53 | style?: Vizzu.Styles.Chart 54 | animOptions?: Vizzu.Anim.Options 55 | } 56 | ``` 57 | 58 | ```javascript 59 | const slides = [ 60 | // This slide sets config, filter, style and animOptions 61 | { 62 | config: { 63 | x: 'Foo', 64 | y: 'Bar' 65 | }, 66 | filter: record => record['Foo'] === 'Bob', 67 | style: { 68 | plot: { 69 | marker: { 70 | colorPalette: '#FF0000' 71 | } 72 | } 73 | }, 74 | animOptions: { 75 | duration: 1 76 | } 77 | } 78 | ] 79 | ``` 80 | 81 | !!! tip 82 | 83 | Check 84 | [Vizzu - Filtering & adding new records chapter](https://lib.vizzuhq.com/latest/tutorial/filter_add_new_records/) 85 | and [Vizzu - Style chapter](https://lib.vizzuhq.com/latest/tutorial/style/) for 86 | more details on data filtering and style options. 87 | 88 | ## Story 89 | 90 | Put the `data` object (described in the [Data chapter](./data.md)) and the slide 91 | list into the `story` descriptor object. 92 | 93 | ```javascript 94 | const story = { 95 | data: data, 96 | slides: slides 97 | } 98 | ``` 99 | 100 | Here you can also set the `story` `style` property to set the chart style used 101 | for the whole `story`. 102 | 103 | ```javascript 104 | const style = { 105 | title: { 106 | fontSize: 50 107 | } 108 | } 109 | 110 | const story = { 111 | data: data, 112 | style: style, 113 | slides: slides 114 | } 115 | ``` 116 | 117 | Then set up the created element with the configuration object: 118 | 119 | ```javascript 120 | const vp = document.querySelector('vizzu-player') 121 | vp.slides = story 122 | ``` 123 | 124 | ## Chart features 125 | 126 | You can enable or disable chart features, such as the `Tooltip` that appears if 127 | the viewer hovers their mouse over a specific element of the chart. 128 | 129 | ```javascript 130 | vp.initializing.then((chart) => { 131 | chart.feature("tooltip", true) 132 | }) 133 | ``` 134 | 135 | !!! tip 136 | 137 | See 138 | [Vizzu - Axes, title, tooltip chapter](https://lib.vizzuhq.com/latest/tutorial/axes_title_tooltip/) 139 | for more details on chart features. 140 | 141 | ## Chart events 142 | 143 | You have many more options to change the look and feel of the `story` by using 144 | events. 145 | 146 | ```javascript 147 | vp.initializing.then((chart) => { 148 | chart.on("click", (event) => { 149 | alert(JSON.stringify(event.detail)) 150 | }) 151 | }) 152 | ``` 153 | 154 | !!! tip 155 | 156 | See [Vizzu - Events chapter](https://lib.vizzuhq.com/latest/tutorial/events/) 157 | for more details on events. 158 | -------------------------------------------------------------------------------- /docs/tutorial/data.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | You can use the same data definition formats as in the `Vizzu` library. 4 | Similarly, there are two types of data series: dimensions and measures. 5 | 6 | !!! note 7 | 8 | Please note, that all of the data used throughout your data story has to be 9 | added to the story at initialization. The data being shown can be filtered at 10 | each step. 11 | 12 | !!! tip 13 | 14 | See [Vizzu - Data chapter](https://lib.vizzuhq.com/latest/tutorial/data/) for 15 | more details about data. 16 | 17 | Here's some sample code for common use cases. 18 | 19 | ## Specify data by series 20 | 21 | ```javascript 22 | const data = { 23 | series: [{ 24 | name: 'Foo', 25 | values: ['Alice', 'Bob', 'Ted'] 26 | }, { 27 | name: 'Bar', 28 | values: [15, 32, 12] 29 | }, { 30 | name: 'Baz', 31 | values: [5, 3, 2] 32 | }] 33 | } 34 | ``` 35 | 36 | ## Specify data by records 37 | 38 | ```javascript 39 | const data = { 40 | series: [{ 41 | name: 'Foo', 42 | type: 'dimension', 43 | }, { 44 | name: 'Bar', 45 | type: 'measure', 46 | }, { 47 | name: 'Baz', 48 | type: 'measure', 49 | }], 50 | records: [ 51 | ['Alice', 15, 5], 52 | ['Bob', 32, 3], 53 | ['Ted', 12, 2], 54 | ], 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/tutorial/index.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | This is an excellent starting point to get acquainted with `Vizzu-Story`, as it 4 | walks you through the installation and initialization of the extension, 5 | introduces the logic it employs and the different settings to control how your 6 | animated data stories look and behave. 7 | 8 | The tutorial is organized into chapters that introduce the concept and the 9 | details of `Vizzu-Story` step-by-step. You can find the list of chapters at the 10 | end of this page and in the menu. 11 | 12 | Since `Vizzu-Story` is built on top of 13 | [Vizzu](https://github.com/vizzuhq/vizzu), it's recommended that you learn and 14 | understand `Vizzu` first. The tutorial for `Vizzu` can be found 15 | [here](https://lib.vizzuhq.com/latest/tutorial/). 16 | 17 | ## Basic logic of Vizzu-Story 18 | 19 | ![Vizzu](../assets/api-overview.svg){ class='image-center' } 20 | 21 | With `Vizzu-Story`, you can build and show a `story` object that contains all of 22 | the data being shown throughout the story and the charts created based on that 23 | data, arranged into `slides` and `steps`. When played,`Vizzu-Story` 24 | automatically adds a set of buttons underneath the chart, via which the users 25 | can navigate between the `slides` within the story. 26 | 27 | `slides` can contain one or more `steps`. 28 | 29 | A `step` (and a single-step `slide`) is basically the same as a single chart 30 | corresponding to an [animate](https://lib.vizzuhq.com/latest/tutorial/) call 31 | from `Vizzu`, with a minor, but important difference: 32 | 33 | - all of the data has to be added to the story at initialization, and it can be 34 | filtered at every `step` throughout the `story`. 35 | 36 | In case of a `slide` with multiple `steps`, all, but the last `steps` are 37 | interim charts that connect a `slide` with a previous `slide` but the animation 38 | will not stop at these `steps` when the `story` is being played. 39 | 40 | ## Installation 41 | 42 | Install via [npm](https://www.npmjs.com/package/vizzu-story): 43 | 44 | ```sh 45 | npm install vizzu-story 46 | ``` 47 | 48 | Or use it from [CDN](https://www.jsdelivr.com/package/npm/vizzu-story): 49 | 50 | ```javascript 51 | import VizzuPlayer from 'https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js' 52 | ``` 53 | 54 | Visit [Installation chapter](../installation.md) for more options and details. 55 | 56 | ## Usage 57 | -------------------------------------------------------------------------------- /docs/tutorial/initialization.md: -------------------------------------------------------------------------------- 1 | # Initialization 2 | 3 | ## Import 4 | 5 | In a script module element import the extension from `CDN` or local install: 6 | 7 | ``` 8 | 12 | ``` 13 | 14 | ## Constructor 15 | 16 | In order to initialize a `VizzuPlayer` with a `VizzuController` that will 17 | contain the rendered `story`, create a `vizzu-player` `HTML` element: 18 | 19 | ``` 20 | 21 | ``` 22 | 23 | ## Size 24 | 25 | `Vizzu-Story` tries to apply the ideal size for the story, but you can also set 26 | them manually via the `width` and `height` style properties of the 27 | `vizzu-player` `HTML` element: 28 | 29 | Set size in `HTML` 30 | 31 | ``` 32 | 33 | 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | or in `JavaScript`: 46 | 47 | ```javascript 48 | const vp = document.querySelector('vizzu-player') 49 | vp.style.cssText = 'width: 100%;height: 400px;' 50 | ``` 51 | 52 | ## HTML attributes 53 | 54 | ### vizzu-url 55 | 56 | `Vizzu-Story` requires and downloads the 57 | [Vizzu](https://github.com/vizzuhq/vizzu-lib) `JavaScript`/`C++` 58 | [library](https://www.jsdelivr.com/package/npm/vizzu) from `jsDelivr CDN`, but 59 | you can also use a different or self-hosted version of it. 60 | 61 | Set `Vizzu` via the `vizzu-url` `HTML` attribute 62 | 63 | ``` 64 | 65 | ``` 66 | 67 | or add it to `window`: 68 | 69 | ```javascript 70 | import Vizzu from '/vizzu.min.js' 71 | 72 | 73 | window.Vizzu = Vizzu 74 | ``` 75 | 76 | !!! info 77 | 78 | After initialization, you can access the imported `Vizzu` library via the 79 | `Vizzu` getter. 80 | 81 | ```javascript 82 | const vp = document.querySelector('vizzu-player') 83 | 84 | vp.initializing.then((chart) => { 85 | const Vizzu = vp.Vizzu 86 | }) 87 | ``` 88 | 89 | ### hash-navigation 90 | 91 | If you add `hash-navigation` attribute, slides can be selected using the `URL` 92 | hash (`#` part), for example `presentation.html#3` selects slide `3`. You can 93 | also use negative numbers, where `-1` means the last slide. 94 | 95 | ``` 96 | 97 | ``` 98 | 99 | ### start-slide 100 | 101 | You can start the story on a specific slide via the `start-slide` `HTML` 102 | attribute. You can also use negative numbers, where `-1` means the last slide. 103 | 104 | ``` 105 | 106 | ``` 107 | 108 | ### custom-spinner 109 | 110 | You can use a custom loading animation. Set spinner via the `custom-spinner` 111 | `HTML` attribute: 112 | 113 | ``` 114 | 115 | ``` 116 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { EslintJavaScriptConfig, EslintTypeScriptConfig } from '@vizzu/eslint-config' 2 | 3 | export default [ 4 | ...EslintTypeScriptConfig, 5 | ...EslintJavaScriptConfig, 6 | { 7 | ignores: [ 8 | '**/node_modules/**', 9 | '**/build/**', 10 | '**/dist/**', 11 | '**/.vscode/**', 12 | '**/*.d.ts' 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vizzu-story", 3 | "version": "0.8.0", 4 | "description": "Build and present animated data stories", 5 | "main": "dist/vizzu-story.min.js", 6 | "types": "dist/vizzu-story.d.ts", 7 | "type": "module", 8 | "files": [ 9 | "dist/vizzu-story.d.ts" 10 | ], 11 | "scripts": { 12 | "lock": "npm-run-all lock:*", 13 | "lock:js": "npm update", 14 | "lock:py": "pdm lock --no-default -d", 15 | "format": "npm-run-all format:*", 16 | "format:src": "npm-run-all format-src:*", 17 | "format-src:js": "npx prettier -c src tests package.json", 18 | "format:docs": "npm-run-all format-docs:*", 19 | "format-docs:js": "npx prettier -c docs", 20 | "format-docs:py": "python ./tools/ci/std_check.py mdformat --wrap 80 --end-of-line keep --line-length 70 --check docs README.md CONTRIBUTING.md CODE_OF_CONDUCT.md", 21 | "format:tools": "npm-run-all format-tools:*", 22 | "format-tools:js": "npx prettier -c tools .github", 23 | "format-tools:py": "black --diff --check tools", 24 | "lint": "npm-run-all lint:*", 25 | "lint:src": "npm-run-all lint-src:*", 26 | "lint-src:js": "npx eslint src tests", 27 | "lint:docs": "npm-run-all lint-docs:*", 28 | "lint-docs:js": "npx eslint docs", 29 | "lint:tools": "npm-run-all lint-tools:*", 30 | "lint-tools:js": "npx eslint tools", 31 | "lint-tools:py": "pylint tools", 32 | "type": "npm-run-all type:*", 33 | "type:src": "npm-run-all type-src:*", 34 | "type-src:js": "npx check-dts src/vizzu-story.d.ts", 35 | "type:tools": "npm-run-all type-tools:*", 36 | "type-tools:py": "mypy tools", 37 | "test": "npm-run-all test:*", 38 | "test:unit": "NODE_OPTIONS='--no-warnings --experimental-vm-modules' npx jest --config tests/vizzu-player/unit/jest.config.js --verbose", 39 | "test:e2e": "NODE_OPTIONS='--no-warnings --experimental-vm-modules' npx jest --config tests/vizzu-player/e2e/jest.config.cjs --verbose", 40 | "ci": "npm-run-all ci:*", 41 | "ci:src": "npm-run-all ci-src:*", 42 | "ci-src:js": "npm-run-all format-src:js lint-src:js type-src:js test", 43 | "ci:docs": "npm-run-all ci-docs:*", 44 | "ci-docs:js": "npm-run-all format-docs:js lint-docs:js", 45 | "ci-docs:py": "npm-run-all format-docs:py", 46 | "ci:tools": "npm-run-all ci-tools:*", 47 | "ci-tools:js": "npm-run-all format-tools:js lint-tools:js", 48 | "ci-tools:py": "npm-run-all format-tools:py lint-tools:py type-tools:py", 49 | "fix": "npm-run-all fix-format fix-lint", 50 | "fix-format": "npm-run-all fix-format:*", 51 | "fix-format:src": "npm-run-all fix-format-src:*", 52 | "fix-format-src:js": "npx prettier -w src tests package.json", 53 | "fix-format:docs": "npm-run-all fix-format-docs:*", 54 | "fix-format-docs:js": "npx prettier -w docs", 55 | "fix-format-docs:py": "python ./tools/ci/std_check.py mdformat --wrap 80 --end-of-line keep --line-length 70 docs README.md CONTRIBUTING.md CODE_OF_CONDUCT.md", 56 | "fix-format:tools": "npm-run-all fix-format-tools:*", 57 | "fix-format-tools:js": "npx prettier -w tools .github", 58 | "fix-format-tools:py": "black tools", 59 | "fix-lint": "npm-run-all fix-lint:*", 60 | "fix-lint:src": "npm-run-all fix-lint-src:*", 61 | "fix-lint-src:js": "npx eslint --fix src tests", 62 | "fix-lint:docs": "npm-run-all fix-lint-docs:*", 63 | "fix-lint-docs:js": "npx eslint --fix docs", 64 | "fix-lint:tools": "npm-run-all fix-lint-tools:*", 65 | "fix-lint-tools:js": "npx eslint --fix tools", 66 | "build-docs": "mkdocs build -f ./tools/docs/mkdocs.yml", 67 | "deploy-docs": "python ./tools/docs/deploy.py", 68 | "build": "python ./tools/ci/version.py False && rm -rf dist build && rollup -c && mkdir build && npm pack --pack-destination build && tar -ztvf build/*.tgz" 69 | }, 70 | "repository": { 71 | "type": "git", 72 | "url": "https://github.com/vizzuhq/vizzu-story-js.git" 73 | }, 74 | "keywords": [ 75 | "template", 76 | "interactive", 77 | "presentation", 78 | "data-visualization", 79 | "charting", 80 | "vizzu" 81 | ], 82 | "author": "Vizzu Inc.", 83 | "license": "Apache-2.0", 84 | "homepage": "https://vizzu-story.vizzuhq.com/", 85 | "url": "https://github.com/vizzuhq/vizzu-story-js/issues", 86 | "devDependencies": { 87 | "@rollup/plugin-terser": "^0.4.4", 88 | "@vizzu/eslint-config": "^1.0.0", 89 | "@vizzu/prettier-config": "^0.1.0", 90 | "check-dts": "^0.8.2", 91 | "eslint": "^9.19.0", 92 | "express": "^4.21.2", 93 | "husky": "^9.1.7", 94 | "jest": "^29.7.0", 95 | "jest-environment-jsdom": "^29.7.0", 96 | "lodash.clonedeep": "^4.5.0", 97 | "npm-run-all": "^4.1.5", 98 | "prettier": "^3.4.2", 99 | "puppeteer": "^24.2.0", 100 | "rollup": "^4.34.4", 101 | "rollup-plugin-copy": "^3.5.0", 102 | "typedoc": "^0.27.6", 103 | "typedoc-plugin-markdown": "^4.4.1", 104 | "typedoc-plugin-rename-defaults": "^0.7.2", 105 | "typescript": "^5.7.3" 106 | }, 107 | "dependencies": { 108 | "vizzu": "~0.16.1" 109 | }, 110 | "prettier": "@vizzu/prettier-config" 111 | } 112 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vizzu-story-dev" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [ 6 | {name = "Vizzu Inc.", email = "hello@vizzu.io"}, 7 | ] 8 | dependencies = [] 9 | requires-python = ">=3.13" 10 | readme = "README.md" 11 | license = {text = "Apache-2.0"} 12 | 13 | [tool.pdm.dev-dependencies] 14 | codequality = [ 15 | "black", 16 | "pylint", 17 | "mypy", 18 | ] 19 | docs = [ 20 | "setuptools", 21 | "mdformat", 22 | "mdformat-beautysh", 23 | "mdformat-black", 24 | "mdformat-configurable-black", 25 | "mdformat-config", 26 | "mdformat-web", 27 | "mdformat-gfm", 28 | "mdformat-tables", 29 | "mdformat-footnote", 30 | "mdformat-frontmatter", 31 | "mdformat-mkdocs", 32 | "mkdocs", 33 | "mkdocs-material", 34 | "mkdocs-section-index", 35 | "mkdocs-literate-nav", 36 | "mkdocs-autorefs", 37 | "mkdocs-gen-files", 38 | "mkdocs-exclude", 39 | "mike", 40 | "pyyaml", 41 | "types-pyyaml", 42 | ] 43 | 44 | [tool.pylint.messages-control] 45 | disable = ["fixme"] 46 | good-names= ["i", "df"] -------------------------------------------------------------------------------- /rollup.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const terser = require('@rollup/plugin-terser') 3 | const copy = require('rollup-plugin-copy') 4 | 5 | module.exports = [ 6 | { 7 | input: path.resolve(__dirname, "./src/vizzu-player.js"), 8 | output: { 9 | file: path.resolve(__dirname, "./dist/vizzu-story.min.js"), 10 | format: "es", 11 | name: "bundle", 12 | }, 13 | plugins: [ 14 | terser(), 15 | copy({ 16 | targets: [ 17 | { src: 'src/vizzu-story.d.ts', dest: 'dist', flatten: true }, 18 | ], 19 | }) 20 | ], 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /src/AnimationQueue.js: -------------------------------------------------------------------------------- 1 | class AnimationNode { 2 | constructor(configObject, animOptions, parameters) { 3 | this.configObject = configObject 4 | this.animOptions = animOptions 5 | this.parameters = parameters 6 | this.next = null 7 | } 8 | } 9 | 10 | class AnimationQueue { 11 | constructor(vizzu) { 12 | this.head = null 13 | this.tail = null 14 | this.vizzu = vizzu 15 | this.playing = false 16 | this.paused = false 17 | this.controller = null 18 | this.direction = 'normal' 19 | this.lastAnimation = null 20 | this._lastParameters = null 21 | this.seekerEnabled = false 22 | 23 | this.vizzu.on('animation-complete', () => { 24 | this.playing = false 25 | this.next() 26 | }) 27 | } 28 | 29 | enqueue(configObject, animOptions, parameters = null) { 30 | if ( 31 | this.tail && 32 | this.tail.configObject === configObject && 33 | this.tail.animOptions === animOptions && 34 | this.tail.parameters === parameters 35 | ) 36 | return 37 | 38 | const newNode = new AnimationNode(configObject, animOptions, parameters) 39 | if (!this.head) { 40 | this.head = newNode 41 | this.tail = newNode 42 | } else { 43 | this.tail.next = newNode 44 | this.tail = newNode 45 | } 46 | 47 | if (!this.playing) { 48 | this.play() 49 | } 50 | } 51 | 52 | dequeue() { 53 | if (!this.head) return 54 | 55 | const removedNode = this.head 56 | this.head = this.head.next 57 | return removedNode 58 | } 59 | 60 | insertqueue(configObject, animOptions) { 61 | if (!this.head) return 62 | const firstAnimation = this.head 63 | const newAnimation = new AnimationNode(configObject, animOptions) 64 | 65 | if (!firstAnimation.next) { 66 | // There's no animation after the first one 67 | firstAnimation.next = newAnimation 68 | // The new animation becomes the last in the queue 69 | this.tail = newAnimation 70 | } else { 71 | // There's already an animation after the first one 72 | newAnimation.next = firstAnimation.next 73 | firstAnimation.next = newAnimation 74 | } 75 | } 76 | 77 | isLast(node) { 78 | if (!node || !node.next) return true 79 | return node.next === null 80 | } 81 | 82 | isEmpty() { 83 | return this.head === null 84 | } 85 | 86 | clear() { 87 | this.head = null 88 | this.tail = null 89 | } 90 | 91 | peek() { 92 | return this.head 93 | } 94 | 95 | play() { 96 | this.playing = false 97 | if (!this.head) return 98 | 99 | const firstAnimation = this.head 100 | if (firstAnimation.animOptions.playState === 'paused') { 101 | this.paused = true 102 | firstAnimation.animOptions.playState = 'running' 103 | } 104 | 105 | if (firstAnimation.animOptions.direction === 'reverse') { 106 | this.direction = 'reverse' 107 | } else { 108 | this.direction = 'normal' 109 | } 110 | this.seekerEnabled = true 111 | if (firstAnimation.parameters.steppType !== 'normal') { 112 | this.seekerEnabled = false 113 | } 114 | 115 | // change speed when the current animate is not a last 116 | let configObject = firstAnimation.configObject 117 | 118 | if (!this.isLast(firstAnimation)) { 119 | configObject = this._speedUp(firstAnimation.configObject) 120 | } 121 | 122 | let startSlideConfig = null 123 | if (configObject.length > 1) { 124 | startSlideConfig = configObject[0] 125 | this.vizzu.feature('rendering', false) 126 | this.vizzu.animate(startSlideConfig.target, 0).catch(this.vizzuCatch) 127 | } 128 | const vizzuAnimate = this.vizzu.animate(configObject, firstAnimation.animOptions) 129 | 130 | vizzuAnimate.activated.then((control) => { 131 | this.playing = true 132 | this._lastParameters = firstAnimation.parameters || null 133 | this.vizzu.feature('rendering', true) 134 | this.controller = control 135 | 136 | if (this.paused) { 137 | control.pause() 138 | } 139 | }) 140 | 141 | vizzuAnimate.catch((message) => { 142 | if (message.name !== 'CancelError') { 143 | console.error(message) 144 | } 145 | }) 146 | 147 | this.lastAnimation = { 148 | configObject, 149 | animOptions: firstAnimation.animOptions 150 | } 151 | if (!this.paused && this.direction === 'reverse' && startSlideConfig !== null) { 152 | this.vizzu.animate(startSlideConfig.target, 0).catch(this.vizzuCatch) 153 | } 154 | } 155 | 156 | next() { 157 | this.dequeue() 158 | 159 | if (!this.head) { 160 | this.playing = false 161 | return 162 | } 163 | 164 | this.play() 165 | } 166 | 167 | pause() { 168 | if (!this.controller) return 169 | 170 | this.playing = false 171 | this.paused = true 172 | this.controller.pause() 173 | } 174 | 175 | reverse() { 176 | if (!this.controller) return 177 | this.playing = true 178 | this.direction = 'reverse' 179 | this.controller.reverse() 180 | this.controller.play() 181 | } 182 | 183 | seekStart(percent) { 184 | this.playing = false 185 | this.controller.cancel() 186 | this.vizzu.feature('rendering', false) 187 | if (this.lastAnimation.configObject.length > 1) { 188 | this.vizzu 189 | .animate(this.lastAnimation.configObject[0].target, { 190 | position: 1, 191 | duration: '0s' 192 | }) 193 | .catch(this.vizzuCatch) 194 | } 195 | const vizzuAnimate = this.vizzu.animate( 196 | this._speedUp(this.lastAnimation.configObject), 197 | this.lastAnimation.animOptions 198 | ) 199 | 200 | vizzuAnimate.activated.then((control) => { 201 | this.controller = control 202 | control.pause() 203 | this.pushed = true 204 | control.seek((this._currentSeek || percent) + '%') 205 | this.vizzu.feature('rendering', true) 206 | }) 207 | 208 | vizzuAnimate.catch(this.vizzuCatch) 209 | } 210 | 211 | seek(percent) { 212 | if (!this.controller) return 213 | this._currentSeek = percent 214 | this.controller.seek(percent + '%') 215 | } 216 | 217 | vizzuCatch(message) { 218 | if (message.name !== 'CancelError') { 219 | console.error(message) 220 | } 221 | } 222 | 223 | getParameter(key) { 224 | if (this._lastParameters && key in this._lastParameters) { 225 | return this._lastParameters[key] 226 | } 227 | return null 228 | } 229 | 230 | manualUpdate(configObject, animOptions, slideNumber) { 231 | if (this.controller) { 232 | this.controller.play() 233 | this.controller.stop() 234 | } 235 | 236 | if (!this.head) { 237 | this.enqueue(configObject, animOptions, slideNumber) 238 | return 239 | } 240 | 241 | // Override the configuration and options of the first animation 242 | this.head.configObject = configObject 243 | this.head.animOptions = animOptions 244 | this.play() 245 | } 246 | 247 | abort() { 248 | if (!this.controller) return 249 | 250 | this.playing = false 251 | this.paused = false 252 | this.controller.stop() 253 | this.controller = null 254 | this.dequeue() 255 | this.next() 256 | } 257 | 258 | isPaused() { 259 | return this.paused 260 | } 261 | 262 | isPlaying() { 263 | return this.playing 264 | } 265 | 266 | hasNext() { 267 | return !!this.head && !!this.head.next 268 | } 269 | 270 | continue() { 271 | if (!this.controller) return 272 | 273 | this.paused = false 274 | this.playing = true 275 | 276 | this.controller.play() 277 | } 278 | 279 | _speedUp(configObject, duration = '500ms') { 280 | if (configObject instanceof Array) { 281 | return configObject.map((elem) => { 282 | return { target: elem.target, options: { duration } } 283 | }) 284 | } 285 | 286 | if (configObject instanceof Object && configObject.target) { 287 | return { 288 | target: configObject.target, 289 | options: { duration } 290 | } 291 | } 292 | return { 293 | target: configObject, 294 | options: { duration } 295 | } 296 | } 297 | } 298 | 299 | export default AnimationQueue 300 | -------------------------------------------------------------------------------- /src/controllers/slider.js: -------------------------------------------------------------------------------- 1 | const LOG_PREFIX = [ 2 | '%cVIZZU%cSLIDER', 3 | 'background: #e2ae30; color: #3a60bf; font-weight: bold', 4 | 'background: #000000; color: #fafafa;' 5 | ] 6 | 7 | const TEXT = { 8 | SEEK: 'Seek animation' 9 | } 10 | 11 | class Slider extends HTMLElement { 12 | constructor() { 13 | super() 14 | 15 | this.attachShadow({ mode: 'open' }) 16 | this.shadowRoot.innerHTML = this._render() 17 | 18 | let isPointerDown = false 19 | 20 | this.slider = this.shadowRoot.getElementById('slider') 21 | 22 | // Set up slider event listener 23 | this.slider.addEventListener('input', (event) => { 24 | if (this.isDisabled()) { 25 | return 26 | } 27 | 28 | this.seek(event.target.value / 10) 29 | }) 30 | 31 | window.addEventListener('pointermove', async (e) => { 32 | if (this.isDisabled() || !isPointerDown) { 33 | return 34 | } 35 | e.preventDefault() 36 | const currentPoition = Math.min( 37 | Math.max(0, e.offsetX - this.slider.offsetLeft), 38 | this.slider.offsetWidth 39 | ) 40 | const currentPoistionInPercent = currentPoition / this.slider.offsetWidth 41 | 42 | this.seek(currentPoistionInPercent * 100) 43 | this.slider.value = currentPoistionInPercent * 1000 44 | }) 45 | 46 | this.slider.addEventListener('pointerdown', async (e) => { 47 | if (this.isDisabled()) { 48 | return 49 | } 50 | isPointerDown = true 51 | const currentSlide = this.player.animationQueue.getParameter('currentSlide') 52 | this.player._currentSlide = currentSlide 53 | this.player.animationQueue.clear() 54 | this.player.animationQueue.seekStart(this.slider.value / 10) 55 | }) 56 | 57 | this.slider.addEventListener('pointerup', async (e) => { 58 | if (this.isDisabled()) { 59 | return 60 | } 61 | isPointerDown = false 62 | this.player.animationQueue.continue() 63 | }) 64 | } 65 | 66 | async connectedCallback() { 67 | await Promise.resolve() 68 | if (!this.controller) { 69 | const parent = this.getRootNode()?.host 70 | if (parent.nodeName === 'VIZZU-CONTROLLER') { 71 | this.controller = parent 72 | await parent.initializing 73 | this.player = parent.player 74 | 75 | const updateSlider = (event) => { 76 | if (this.player.animationQueue.playing) { 77 | const process = event.detail.progress * 1000 78 | if (this.player.direction === this.player.animationQueue.direction) { 79 | this._updateSlider(process) 80 | } else { 81 | this._updateSlider(1000 - process) 82 | } 83 | 84 | if (!this.player.animationQueue.seekerEnabled) { 85 | this.slider.setAttribute('readonly', true) 86 | } 87 | } 88 | } 89 | this.player.vizzu.on('update', updateSlider) 90 | 91 | const _checkDisabeld = () => { 92 | this.slider.removeAttribute('readonly') 93 | if ( 94 | !this.player.animationQueue.playing && 95 | !this.player.animationQueue.seekerEnabled 96 | ) { 97 | this.slider.setAttribute('disabled', true) 98 | } 99 | } 100 | this.player.vizzu.on('animation-complete', _checkDisabeld) 101 | } 102 | } 103 | } 104 | 105 | seek(percent) { 106 | this.player.animationQueue.seek(percent) 107 | } 108 | 109 | isDisabled() { 110 | return this.slider.hasAttribute('disabled') || this.slider.hasAttribute('readonly') 111 | } 112 | 113 | log(...msg) { 114 | if (this.player.debug) { 115 | console.log(...LOG_PREFIX, ...msg) 116 | } 117 | } 118 | 119 | _update(state) { 120 | this.log('update', state) 121 | const e = new CustomEvent('update', { detail: state }) 122 | this.dispatchEvent(e) 123 | } 124 | 125 | _updateSlider(value) { 126 | if (!this.slider) { 127 | return null 128 | } 129 | if (this.player.direction === 'normal' && this.player.currentSlide === 0) { 130 | this.slider.setAttribute('disabled', true) 131 | this.slider.value = 0 132 | } else { 133 | this.slider.removeAttribute('disabled') 134 | this.slider.removeAttribute('readonly') 135 | this.slider.value = value 136 | } 137 | } 138 | 139 | _render() { 140 | return ` 141 | 220 |
221 | 222 |
` 223 | } 224 | } 225 | 226 | try { 227 | if (!customElements.get('vizzu-controller-slider')) { 228 | customElements.define('vizzu-controller-slider', Slider) 229 | } else { 230 | console.warn('Slider already defined') 231 | } 232 | } catch (e) { 233 | console.error('Failed to register custom element: ', e) 234 | } 235 | 236 | export default Slider 237 | -------------------------------------------------------------------------------- /src/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vizzu - Presentation sample 8 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/example/index.js: -------------------------------------------------------------------------------- 1 | // import VizzuPlayer from "https://cdn.jsdelivr.net/npm/vizzu-story@latest/dist/vizzu-story.min.js" 2 | import VizzuPlayer from '../vizzu-player.js' // eslint-disable-line no-unused-vars 3 | 4 | import data from './data.js' 5 | import style from './style.js' 6 | 7 | function labelHandler(event) { 8 | const Year = parseFloat(event.detail.text) 9 | if (!isNaN(Year) && Year > 1950 && Year < 2020 && Year % 5 !== 0) { 10 | event.preventDefault() 11 | } 12 | } 13 | 14 | // eslint-disable-next-line no-unused-vars 15 | const vizzuPlayerData = { 16 | data, // data, copied into the initializer slide (if not present) 17 | style, // style, copied into the initializer slide (if not present) 18 | slides: [ 19 | // slide 20 | { 21 | config: {}, // Config.Chart 22 | filter: () => true, // data.filter TODO: not declarative, cannot be 23 | // serialized ??? string => Function 24 | style: {}, // Styles.Chart 25 | animOptions: {} // Anim.Options 26 | }, 27 | // ... or list of slides 28 | [ 29 | {}, // phase1, 30 | {} // phase2, ... 31 | ] 32 | ] 33 | } 34 | 35 | const vpd = { 36 | data, 37 | style, 38 | slides: [ 39 | { 40 | // slide 1 41 | filter: (record) => record.Function !== 'Defense', 42 | config: { 43 | channels: { 44 | y: { 45 | set: ['Amount', 'Function'], 46 | range: { 47 | min: '0%', 48 | max: '100%' 49 | } 50 | }, 51 | x: { 52 | set: ['Year'] 53 | }, 54 | color: 'Function' 55 | }, 56 | title: 'U.S. Non-Defense R&D Budget by Functions', 57 | geometry: 'area' 58 | } 59 | }, 60 | { 61 | // slide 2 62 | config: { 63 | title: 'Share of Total Expenditures %', 64 | align: 'stretch' 65 | } 66 | }, 67 | [ 68 | // slide 3 69 | { 70 | // slide 3.1 71 | filter: (record) => record.Function === 'Health' || record.Function === 'Space', 72 | config: { 73 | title: 'Compare Space & Health', 74 | align: 'none', 75 | split: true 76 | } 77 | }, 78 | { 79 | // slide 3.2 80 | filter: (record) => record.Function !== 'Defense', 81 | config: { 82 | title: 'All Non-defense Functions Side-by-Side', 83 | align: 'none', 84 | split: true 85 | } 86 | } 87 | ], 88 | [ 89 | // slide 4 90 | { 91 | // slide 4.1 92 | filter: null, 93 | config: { 94 | title: 'Show Defense Expenditures', 95 | split: false 96 | } 97 | }, 98 | { 99 | // slide 4.2 100 | filter: (record) => record.Function === 'Defense', 101 | config: { 102 | title: 'Defense Expenditures', 103 | align: 'none' 104 | } 105 | } 106 | ], 107 | { 108 | // slide 5 109 | filter: null, 110 | config: { 111 | title: 'Total U.S. R&D Budget' 112 | } 113 | }, 114 | { 115 | // slide 6 116 | config: { 117 | title: 'Total U.S. R&D Budget - Components Side by Side', 118 | x: 'Year', 119 | y: 'Amount', 120 | noop: 'Function', 121 | align: 'none', 122 | geometry: 'line' 123 | } 124 | } 125 | ] 126 | } 127 | 128 | const vp = document.querySelector('vizzu-player') 129 | vp.initializing.then((chart) => { 130 | vp.slides = vpd // init slides 131 | chart.on('plot-axis-label-draw', labelHandler) 132 | chart.feature('tooltip', true) 133 | }) 134 | 135 | /* 136 | TODO: 137 | - vizzu spinner 138 | - `animate` with seek 139 | - `reverse` animation 140 | - !SEEK! 141 | - animOptions check 142 | - styling with CSS variables 143 | */ 144 | -------------------------------------------------------------------------------- /src/example/loadinganim.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 19 | 21 | 23 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /src/example/style.js: -------------------------------------------------------------------------------- 1 | const style = { 2 | plot: { 3 | paddingLeft: '0em', 4 | yAxis: { 5 | label: { 6 | fontSize: '1em', 7 | paddingRight: '1.2em' 8 | }, 9 | title: { 10 | color: '#ffffff00' 11 | } 12 | }, 13 | xAxis: { 14 | label: { 15 | angle: '2.5', 16 | fontSize: '1.1em', 17 | paddingRight: '0em', 18 | paddingTop: '1em' 19 | }, 20 | title: { 21 | fontSize: '1em', 22 | paddingTop: '2.5em' 23 | } 24 | } 25 | } 26 | } 27 | 28 | export default style 29 | -------------------------------------------------------------------------------- /src/vizzu-controller.js: -------------------------------------------------------------------------------- 1 | import Slider from './controllers/slider.js' 2 | 3 | const LOG_PREFIX = [ 4 | '%cVIZZU%cCONTROLLER', 5 | 'background: #e2ae30; color: #3a60bf; font-weight: bold', 6 | 'background: #3a60bf; color: #e2ae30;' 7 | ] 8 | 9 | const TEXT = { 10 | PLAY: 'Play', 11 | PAUSE: 'Pause', 12 | NEXT: 'Next', 13 | PREVIOUS: 'Previous', 14 | FIRST: 'First', 15 | LAST: 'Last', 16 | FULLSCREEN: 'Fullscreen', 17 | EXIT_FULLSCREEN: 'Exit fullscreen', 18 | FULLSCREEN_NOT_ALLOWED: 'Fullscreen not allowed' 19 | } 20 | 21 | class VizzuController extends HTMLElement { 22 | constructor() { 23 | super() 24 | 25 | this.attachShadow({ mode: 'open' }) 26 | this.shadowRoot.innerHTML = this._render() 27 | 28 | this._update = this.update.bind(this) 29 | this._keyHandler = this._handleKey.bind(this) 30 | 31 | this.shadowRoot.addEventListener('click', (e) => { 32 | const btn = e.target.closest('button') 33 | 34 | if (!btn) return 35 | switch (btn.id) { 36 | case 'start': 37 | this.toStart() 38 | break 39 | case 'end': 40 | this.toEnd() 41 | break 42 | case 'previous': 43 | this.previous() 44 | break 45 | case 'next': 46 | this.next() 47 | break 48 | case 'fullscreen': 49 | this.fullscreen() 50 | break 51 | } 52 | }) 53 | 54 | this._resolvePlayer = null 55 | this.initializing = new Promise((resolve) => { 56 | this._resolvePlayer = resolve 57 | }) 58 | } 59 | 60 | update(e) { 61 | const data = e.detail 62 | this.log('update', data) 63 | this._state = data 64 | 65 | if (data.currentSlide === 0) { 66 | this.setAttribute('first', '') 67 | } else { 68 | this.removeAttribute('first') 69 | } 70 | 71 | if (data.currentSlide === data.length - 1) { 72 | this.setAttribute('last', '') 73 | } else { 74 | this.removeAttribute('last') 75 | } 76 | 77 | this.shadowRoot.getElementById('status').innerHTML = this._html_status 78 | } 79 | 80 | get currentSlide() { 81 | if (!this.player || !this.player.animationQueue) return null 82 | return this?.player?.animationQueue.getParameter('currentSlide') 83 | } 84 | 85 | get _html_status() { 86 | return `${(this.currentSlide || 0) + 1}/${ 87 | this._state?.length || '?' 88 | }` 89 | } 90 | 91 | _unsubscribe(player) { 92 | player?.removeEventListener('update', this._update) 93 | this._player = null 94 | } 95 | 96 | _subscribe(player) { 97 | player?.addEventListener('update', this._update) 98 | this._player = player 99 | } 100 | 101 | _handleKey(e) { 102 | const kbmode = this._player?.getAttribute('keyboard') || 'focus' 103 | this.log(`key[${kbmode}]: ${e.key}`) 104 | 105 | const usedControllKeys = [ 106 | 'PageUp', 107 | 'PageDown', 108 | 'ArrowRight', 109 | 'ArrowLeft', 110 | 'Home', 111 | 'End', 112 | 'f', 113 | 'F', 114 | 'Escape', 115 | 'Enter' 116 | ] 117 | 118 | if ( 119 | usedControllKeys.includes(e.key) && 120 | (kbmode === 'focus' || (kbmode === 'fullscreen' && document.fullscreenElement)) 121 | ) { 122 | e.preventDefault() 123 | switch (e.key) { 124 | case 'ArrowRight': 125 | case 'PageDown': 126 | this.next() 127 | break 128 | 129 | case 'ArrowLeft': 130 | case 'PageUp': 131 | this.previous() 132 | break 133 | 134 | case 'Home': 135 | this.toStart() 136 | break 137 | 138 | case 'End': 139 | this.toEnd() 140 | break 141 | 142 | case 'f': 143 | case 'F': 144 | this.fullscreen() 145 | break 146 | case 'Escape': 147 | if (document.fullscreenElement) { 148 | document.exitFullscreen() 149 | } 150 | break 151 | } 152 | } 153 | } 154 | 155 | async connectedCallback() { 156 | if (!this._player) { 157 | const p = this.getRootNode()?.host 158 | if (p.nodeName === 'VIZZU-PLAYER') { 159 | const player = this.getRootNode()?.host 160 | 161 | await player.initializing 162 | this._resolvePlayer(player.initializing) 163 | player.controller = this 164 | this._player = player 165 | this._subscribe(this._player) 166 | } 167 | } 168 | 169 | if (this._player) { 170 | this._player.addEventListener('keydown', this._keyHandler) 171 | } 172 | } 173 | 174 | disconnectedCallback() { 175 | if (this._player) { 176 | this._player.removeEventListener('keydown', this._keyHandler) 177 | this._unsubscribe(this._player) 178 | } 179 | } 180 | 181 | get player() { 182 | return this._player 183 | } 184 | 185 | set player(player) { 186 | this._player = player 187 | } 188 | 189 | get debug() { 190 | try { 191 | const debugCookie = document.cookie.split(';').some((c) => c.startsWith('vizzu-debug')) 192 | return debugCookie || this.hasAttribute('debug') 193 | } catch (e) { 194 | return this.hasAttribute('debug') 195 | } 196 | } 197 | 198 | set debug(debug) { 199 | document.cookie = `vizzu-debug=${debug ? 'true' : ''}; path=/` 200 | } 201 | 202 | log(...msg) { 203 | if (this.debug) { 204 | console.log(...LOG_PREFIX, ...msg) 205 | } 206 | } 207 | 208 | seek(percent) { 209 | this.log('seek', percent) 210 | this._player?._update(this?._player._state) 211 | this.player?.seek(percent) 212 | } 213 | 214 | toStart() { 215 | this.log('toStart') 216 | this.player?.toStart() 217 | } 218 | 219 | toEnd() { 220 | this.log('toEnd') 221 | this.player?.toEnd() 222 | } 223 | 224 | previous() { 225 | this.log('previous') 226 | this.player?.previous() 227 | } 228 | 229 | next() { 230 | this.log('next') 231 | this.player?.next() 232 | } 233 | 234 | get fullscreenTarget() { 235 | return this._fullscreenTarget || this.getRootNode()?.host || this.parentElement 236 | } 237 | 238 | fullscreen() { 239 | if (!this.shadowRoot.ownerDocument.fullscreenEnabled) { 240 | console.warn(TEXT.FULLSCREEN_NOT_ALLOWED) 241 | return 242 | } 243 | 244 | if (document.fullscreenElement) { 245 | document.exitFullscreen() 246 | } else { 247 | this.fullscreenTarget?.requestFullscreen() 248 | } 249 | } 250 | 251 | _render() { 252 | return ` 253 | 330 |
${this._html_status}
331 |
332 | 337 | 342 | 343 | 348 | 353 |
354 | 363 | ` 364 | } 365 | } 366 | 367 | try { 368 | if (!customElements.get('vizzu-controller')) { 369 | customElements.define('vizzu-controller', VizzuController) 370 | } else { 371 | console.warn('VizzuController already defined') 372 | } 373 | } catch (e) { 374 | console.error('Failed to register custom element: ', e) 375 | } 376 | 377 | export default VizzuController 378 | export { Slider } 379 | -------------------------------------------------------------------------------- /src/vizzu-story.d.ts: -------------------------------------------------------------------------------- 1 | /** Vizzu library types */ 2 | import * as vizzu from 'vizzu' 3 | 4 | type Vizzu = typeof vizzu 5 | 6 | declare namespace Vizzu { 7 | export import Config = vizzu.Config 8 | export import Data = vizzu.Data 9 | export import Styles = vizzu.Styles 10 | export import Anim = vizzu.Anim 11 | } 12 | 13 | /** Atomic phase of a slide coressponding to one Vizzu.animate() call. */ 14 | interface Phase { 15 | config?: Vizzu.Config.Chart 16 | filter?: Vizzu.Data.FilterCallback | null 17 | style?: Vizzu.Styles.Chart 18 | animOptions?: Vizzu.Anim.Options 19 | } 20 | 21 | /** Slide consists of a single or multiple phase. Controls will navigate 22 | * between slides. */ 23 | type Slide = Phase | Phase[] 24 | 25 | /** Story configuration object represents the whole presentation containing 26 | * the underlying data and the slides. */ 27 | interface Story { 28 | /** Data, copied into the initializer slide (if not present). */ 29 | data?: Vizzu.Data.Set 30 | /** Initial style, copied into the initializer slide (if not present). */ 31 | style?: Vizzu.Styles.Chart 32 | /** The sequence of the presentation's slides. */ 33 | slides: Slide[] 34 | } 35 | 36 | export default class VizzuPlayer extends HTMLElement { 37 | /** Setter for story object. */ 38 | set slides(slides: Story) 39 | } 40 | -------------------------------------------------------------------------------- /tests/assets/chart-params/animOptions.js: -------------------------------------------------------------------------------- 1 | const animOptionsAssets = {} 2 | 3 | animOptionsAssets.animOptions1 = { 4 | duration: 1 5 | } 6 | 7 | animOptionsAssets.animOptions2 = { 8 | delay: 5 9 | } 10 | 11 | animOptionsAssets.animOptions3 = { 12 | regroupStrategy: 'drilldown' 13 | } 14 | 15 | export { animOptionsAssets } 16 | -------------------------------------------------------------------------------- /tests/assets/chart-params/config.js: -------------------------------------------------------------------------------- 1 | const configAssets = {} 2 | 3 | configAssets.config1 = { 4 | x: 'Foo', 5 | y: 'Bar' 6 | } 7 | 8 | configAssets.config2 = { 9 | geometry: 'circle' 10 | } 11 | 12 | configAssets.config3 = { 13 | coordSystem: 'polar' 14 | } 15 | 16 | export { configAssets } 17 | -------------------------------------------------------------------------------- /tests/assets/chart-params/data.js: -------------------------------------------------------------------------------- 1 | const dataAssets = {} 2 | 3 | dataAssets.dataInit = { 4 | series: [ 5 | { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, 6 | { name: 'Bar', values: [15, 32, 12] }, 7 | { name: 'Baz', values: [5, 3, 2] } 8 | ] 9 | } 10 | 11 | export { dataAssets } 12 | -------------------------------------------------------------------------------- /tests/assets/chart-params/filter.js: -------------------------------------------------------------------------------- 1 | const filterAssets = {} 2 | 3 | filterAssets.filter1 = () => {} 4 | 5 | filterAssets.filter2 = () => true 6 | 7 | filterAssets.filter3 = () => { 8 | return [] 9 | } 10 | 11 | export { filterAssets } 12 | -------------------------------------------------------------------------------- /tests/assets/chart-params/style.js: -------------------------------------------------------------------------------- 1 | const styleAssets = {} 2 | 3 | styleAssets.styleInit = { backgroundColor: '#FFFFFFFF' } 4 | 5 | styleAssets.style1 = { title: { fontSize: 50 } } 6 | 7 | styleAssets.style2 = { borderWidth: 5 } 8 | 9 | styleAssets.style3 = { tooltip: { arrowSize: 10 } } 10 | 11 | export { styleAssets } 12 | -------------------------------------------------------------------------------- /tests/assets/data-sets/basic.mjs: -------------------------------------------------------------------------------- 1 | const vizzuPlayerData = { 2 | data: { 3 | series: [ 4 | { 5 | name: 'Foo', 6 | values: ['Alice', 'Bob', 'Ted'] 7 | }, 8 | { 9 | name: 'Bar', 10 | values: [15, 32, 12] 11 | }, 12 | { 13 | name: 'Baz', 14 | values: [5, 3, 2] 15 | } 16 | ] 17 | }, 18 | slides: [ 19 | [ 20 | { 21 | config: { 22 | x: 'Foo', 23 | y: 'Bar', 24 | label: 'Bar' 25 | } 26 | } 27 | ], 28 | [ 29 | { 30 | filter: (record) => record.Foo === 'Bob', 31 | config: { 32 | color: 'Foo', 33 | x: 'Baz', 34 | label: 'Baz', 35 | geometry: 'circle' 36 | } 37 | } 38 | ] 39 | ] 40 | } 41 | 42 | export default vizzuPlayerData 43 | -------------------------------------------------------------------------------- /tests/assets/data-sets/nolabel.mjs: -------------------------------------------------------------------------------- 1 | const vizzuPlayerData = { 2 | data: { 3 | series: [ 4 | { 5 | name: 'Foo', 6 | values: ['Alice', 'Bob', 'Ted'] 7 | }, 8 | { 9 | name: 'Bar', 10 | values: [15, 32, 12] 11 | }, 12 | { 13 | name: 'Baz', 14 | values: [5, 3, 2] 15 | } 16 | ] 17 | }, 18 | slides: [ 19 | [ 20 | { 21 | config: { 22 | x: 'Foo', 23 | y: 'Bar' 24 | } 25 | } 26 | ], 27 | [ 28 | { 29 | filter: (record) => record.Foo === 'Bob', 30 | config: { 31 | color: 'Foo', 32 | x: 'Baz', 33 | geometry: 'circle' 34 | } 35 | } 36 | ] 37 | ] 38 | } 39 | 40 | export default vizzuPlayerData 41 | -------------------------------------------------------------------------------- /tests/assets/data-tests/label/basic.cjs: -------------------------------------------------------------------------------- 1 | const first = ['15', '32', '12'] 2 | 3 | const expected = { 4 | 0: first, 5 | 1: ['3'], 6 | 2: first 7 | } 8 | 9 | module.exports = expected 10 | -------------------------------------------------------------------------------- /tests/assets/data-tests/label/index.cjs: -------------------------------------------------------------------------------- 1 | const nolabel = require('./nolabel') 2 | const basic = require('./basic') 3 | const olympics = require('./olympics.cjs') 4 | 5 | const testCases = [ 6 | { 7 | name: 'nolabel', 8 | expected: nolabel 9 | }, 10 | { 11 | name: 'basic', 12 | expected: basic 13 | }, 14 | { 15 | name: 'olympics', 16 | expected: olympics 17 | } 18 | ] 19 | 20 | for (const testCase of testCases) { 21 | for (const key in testCase.expected) { 22 | if (Array.isArray(testCase.expected[key])) { 23 | testCase.expected[key] = testCase.expected[key].sort() 24 | } 25 | } 26 | } 27 | 28 | module.exports = testCases 29 | -------------------------------------------------------------------------------- /tests/assets/data-tests/label/nolabel.cjs: -------------------------------------------------------------------------------- 1 | const expected = {} 2 | 3 | module.exports = expected 4 | -------------------------------------------------------------------------------- /tests/assets/data-tests/label/olympics.cjs: -------------------------------------------------------------------------------- 1 | const first = ['20 217'] 2 | 3 | const expected = { 4 | 0: first, 5 | 1: [ 6 | '3 080', 7 | '862', 8 | '25', 9 | '228', 10 | '515', 11 | '808', 12 | '555', 13 | '201', 14 | '103', 15 | '1 021', 16 | '1 763', 17 | '103', 18 | '3', 19 | '1 356', 20 | '996', 21 | '668', 22 | '388', 23 | '13', 24 | '5', 25 | '679', 26 | '3', 27 | '425', 28 | '201', 29 | '286', 30 | '16', 31 | '96', 32 | '10', 33 | '162', 34 | '611', 35 | '154', 36 | '42', 37 | '106', 38 | '126', 39 | '96', 40 | '494', 41 | '568', 42 | '24', 43 | '607', 44 | '108', 45 | '544', 46 | '90', 47 | '57', 48 | '195', 49 | '171', 50 | '42', 51 | '15', 52 | '18', 53 | '42', 54 | '153', 55 | '153', 56 | '176', 57 | '39', 58 | '51', 59 | '24', 60 | '120', 61 | '24', 62 | '6', 63 | '6', 64 | '6', 65 | '12', 66 | '32', 67 | '6', 68 | '78', 69 | '286', 70 | '51', 71 | '115', 72 | '36', 73 | '2', 74 | '7', 75 | '3', 76 | '121', 77 | '12', 78 | '7', 79 | '6', 80 | '3', 81 | '1' 82 | ], 83 | 2: [ 84 | 'Athletics', 85 | 'Shooting', 86 | 'Rowing', 87 | 'Art. Gymn.', 88 | 'Swimming', 89 | 'Wrestling', 90 | 'Boxing', 91 | 'Fencing', 92 | 'Weightl.', 93 | 'Judo' 94 | ], 95 | 3: [ 96 | 'Athletics', 97 | 'Shooting', 98 | 'Golf', 99 | 'Tennis', 100 | 'Cycling Track', 101 | 'Rowing', 102 | 'Sailing', 103 | 'Archery', 104 | 'Football', 105 | 'Art. Gymn.', 106 | 'Swimming', 107 | 'Water Polo', 108 | 'Roque', 109 | 'Wrestling', 110 | 'Boxing', 111 | 'Fencing', 112 | 'Diving', 113 | 'Tug of War', 114 | 'Lacrosse', 115 | 'Weightl.', 116 | 'Jeu de Paume', 117 | 'Equestrian', 118 | 'Cycling Road', 119 | 'Figure skating', 120 | 'Polo', 121 | 'Ice Hockey', 122 | 'Rugby', 123 | 'Ski Jumping', 124 | 'Speed skating', 125 | 'Bobsleigh', 126 | 'Skeleton', 127 | 'Hockey', 128 | 'Modern Pentathlon', 129 | 'Basketball', 130 | 'Alpine Skiing', 131 | 'Canoe Sprint', 132 | 'Canoe Marathon', 133 | 'Judo', 134 | 'Canoe Slalom', 135 | 'Cross Country Skiing', 136 | 'Volleyball', 137 | 'Artistic Swimming', 138 | 'Short Track', 139 | 'Freestyle Skiing', 140 | 'Cycling Mountain Bike', 141 | 'Softball', 142 | 'Baseball', 143 | 'Beach Volleyball', 144 | 'Snowboard', 145 | 'Luge', 146 | 'Taekwondo', 147 | 'Triathlon', 148 | 'Curling', 149 | 'Cycling BMX Racing', 150 | 'Nordic Combined', 151 | 'Marathon Swimming', 152 | 'Cycling BMX Freestyle', 153 | '3x3 Basketball', 154 | 'Surfing', 155 | 'Skateboarding', 156 | 'Karate', 157 | 'Sport Climbing', 158 | 'Handball', 159 | 'Biathlon', 160 | 'Rhythmic Gymnastics', 161 | 'Table Tennis', 162 | 'Trampoline Gymnastics', 163 | 'Cricket', 164 | 'Rackets', 165 | 'Water Motorsports', 166 | 'Badminton', 167 | 'Rugby Sevens', 168 | 'Croquet', 169 | 'Equestrian Vaulting', 170 | 'Military Patrol', 171 | 'Basque Pelota' 172 | ], 173 | 4: ['20 217'], 174 | 5: ['20 217'], 175 | 6: [ 176 | '122', 177 | '278', 178 | '280', 179 | '324', 180 | '311', 181 | '438', 182 | '427', 183 | '365', 184 | '388', 185 | '421', 186 | '479', 187 | '514', 188 | '523', 189 | '542', 190 | '608', 191 | '633', 192 | '705', 193 | '724', 194 | '746', 195 | '805', 196 | '877', 197 | '986', 198 | '183', 199 | '842', 200 | '205', 201 | '927', 202 | '234', 203 | '926', 204 | '252', 205 | '957', 206 | '258', 207 | '957', 208 | '293', 209 | '973', 210 | '307', 211 | '1 080', 212 | '327' 213 | ], 214 | 7: [ 215 | '122', 216 | '278', 217 | '280', 218 | '324', 219 | '311', 220 | '438', 221 | '427', 222 | '365', 223 | '388', 224 | '421', 225 | '479', 226 | '514', 227 | '523', 228 | '542', 229 | '608', 230 | '633', 231 | '705', 232 | '724', 233 | '746', 234 | '805', 235 | '877', 236 | '986', 237 | '183', 238 | '842', 239 | '205', 240 | '927', 241 | '234', 242 | '926', 243 | '252', 244 | '957', 245 | '258', 246 | '957', 247 | '293', 248 | '973', 249 | '307', 250 | '1 080', 251 | '327' 252 | ], 253 | 15: ['105', '354', '294', '3', '7'], 254 | 20: [ 255 | '43', 256 | '94', 257 | '97', 258 | '110', 259 | '103', 260 | '155', 261 | '126', 262 | '109', 263 | '116', 264 | '124', 265 | '138', 266 | '145', 267 | '147', 268 | '152', 269 | '163', 270 | '174', 271 | '195', 272 | '198', 273 | '204', 274 | '226', 275 | '241', 276 | '260', 277 | '271', 278 | '300', 279 | '301', 280 | '302', 281 | '302', 282 | '307', 283 | '340' 284 | ], 285 | 21: ['43'], 286 | 22: ['11', '6', '2', '5', '2', '2', '10', '1', '1', '2', '1'], 287 | 23: ['30', '10', '17', '31', '3', '4', '10', '9', '7', '5'], 288 | 24: ['106', '14', '18', '31', '5', '11', '11', '8', '5', '5'], 289 | 25: ['106', '14', '18', '31', '5', '11', '11', '8', '5', '5'], 290 | 26: ['129', '17', '74', '36', '8', '11', '11', '8', '8', '8'], 291 | 27: ['154', '22', '84', '43', '11', '12', '11', '32', '11', '10'], 292 | 28: ['154', '22', '84', '43', '11', '12', '11', '32', '11', '10'], 293 | 29: ['195', '22', '98', '52', '21', '21', '22', '51', '14', '25'], 294 | 30: ['240', '22', '107', '65', '29', '17', '26', '25', '55', '39'], 295 | 31: ['262', '32', '110', '71', '36', '24', '27', '25', '61', '47'], 296 | 32: ['303', '35', '114', '81', '48', '24', '27', '25', '70', '52'], 297 | 33: ['327', '66', '118', '88', '56', '33', '28', '25', '75', '59'], 298 | 34: ['327', '66', '118', '88', '56', '33', '28', '25', '75', '59'], 299 | 35: ['327', '66', '118', '88', '56', '33', '28', '25', '75', '59'], 300 | 36: ['365', '66', '121', '98', '64', '43', '30', '29', '91', '67'], 301 | 37: ['404', '66', '122', '103', '72', '59', '32', '32', '103', '71'], 302 | 38: ['436', '70', '59', '127', '107', '80', '68', '33', '108', '74'], 303 | 39: ['470', '82', '102', '129', '107', '93', '40', '74', '109', '75'], 304 | 40: ['506', '92', '132', '133', '108', '103', '46', '84', '111', '78'], 305 | 41: ['551', '106', '161', '138', '115', '106', '51', '94', '113', '79'], 306 | 42: ['584', '139', '211', '142', '117', '111', '100', '64', '117', '82'], 307 | 43: ['618', '189', '260', '145', '119', '113', '104', '73', '121', '86'], 308 | 44: ['618', '236', '340', '150', '125', '121', '111', '73', '124', '89'], 309 | 45: ['701', '253', '340', '155', '130', '135', '111', '83', '126', '93'], 310 | 46: ['737', '301', '395', '160', '136', '141', '122', '87', '126', '94'], 311 | 47: ['774', '334', '395', '165', '144', '147', '133', '90', '127', '95'], 312 | 48: ['818', '354', '395', '166', '159', '160', '140', '93', '129', '96'], 313 | 49: ['855', '367', '395', '177', '172', '173', '100', '148', '98', '133'], 314 | 50: ['891', '380', '395', '186', '183', '183', '117', '156', '114', '137'], 315 | 51: ['927', '396', '395', '205', '190', '160', '191', '131', '159', '137'], 316 | 52: ['974', '407', '395', '234', '201', '198', '199', '139', '167', '138'], 317 | 53: ['1 020', '424', '395', '261', '211', '224', '207', '147', '175', '150'], 318 | 54: ['1 059', '434', '395', '283', '221', '262', '217', '164', '181', '169'], 319 | 56: [ 320 | 'USA', 321 | 'Germany', 322 | 'Soviet Union', 323 | 'Great Britain', 324 | 'France', 325 | 'China', 326 | 'Italy', 327 | 'Australia', 328 | 'Hungary', 329 | 'Japan', 330 | 'Other' 331 | ], 332 | 57: first 333 | } 334 | 335 | module.exports = expected 336 | -------------------------------------------------------------------------------- /tests/assets/mocks/vizzu-attribute.js: -------------------------------------------------------------------------------- 1 | import VizzuMock from './vizzu.js' 2 | 3 | class Vizzu extends VizzuMock { 4 | get instanceMockType() { 5 | return 'attributeInstance' 6 | } 7 | 8 | static get classMockType() { 9 | return 'attributeClass' 10 | } 11 | } 12 | 13 | export default Vizzu 14 | -------------------------------------------------------------------------------- /tests/assets/mocks/vizzu-cdn.js: -------------------------------------------------------------------------------- 1 | import VizzuMock from './vizzu.js' 2 | 3 | class Vizzu extends VizzuMock { 4 | get instanceMockType() { 5 | return 'cdnInstance' 6 | } 7 | 8 | static get classMockType() { 9 | return 'cdnClass' 10 | } 11 | } 12 | 13 | export default Vizzu 14 | -------------------------------------------------------------------------------- /tests/assets/mocks/vizzu-window.js: -------------------------------------------------------------------------------- 1 | import VizzuMock from './vizzu.js' 2 | 3 | class Vizzu extends VizzuMock { 4 | get instanceMockType() { 5 | return 'windowInstance' 6 | } 7 | 8 | static get classMockType() { 9 | return 'windowClass' 10 | } 11 | } 12 | 13 | export default Vizzu 14 | -------------------------------------------------------------------------------- /tests/assets/mocks/vizzu.js: -------------------------------------------------------------------------------- 1 | class Presets { 2 | stream(config) { 3 | return { 4 | channels: { 5 | x: config.x, 6 | y: [config.y, config.stackedBy], 7 | color: config.stackedBy 8 | } 9 | } 10 | } 11 | } 12 | 13 | class Control { 14 | constructor(animation) { 15 | this._animation = animation 16 | } 17 | 18 | stop() {} 19 | 20 | pause() {} 21 | } 22 | 23 | class Vizzu { 24 | constructor() { 25 | this._snapshot = {} 26 | } 27 | 28 | animate() { 29 | this._snapshot = [...arguments][0] 30 | const anim = Promise.resolve('test1') 31 | anim.activated = Promise.resolve(new Control([...arguments][0])) 32 | return anim 33 | } 34 | 35 | get config() { 36 | return this._snapshot.config || {} 37 | } 38 | 39 | getComputedStyle() { 40 | return this._snapshot?.style || {} 41 | } 42 | 43 | feature() {} 44 | 45 | on() {} 46 | 47 | off() {} 48 | 49 | static get presets() { 50 | return new Presets() 51 | } 52 | 53 | async initializing() { 54 | return Promise.resolve() 55 | } 56 | } 57 | 58 | export default Vizzu 59 | -------------------------------------------------------------------------------- /tests/assets/slides/asset-functions.js: -------------------------------------------------------------------------------- 1 | function waitForSlidesToBeSet(vp, timeout) { 2 | return new Promise((resolve, reject) => { 3 | const startTime = Date.now() 4 | 5 | const checkSlides = () => { 6 | if (vp.slides !== undefined) { 7 | resolve(vp.slides) 8 | } else if (Date.now() - startTime >= timeout) { 9 | reject(new Error('Timeout: slides were not set within the specified time.')) 10 | } else { 11 | setTimeout(checkSlides, 100) 12 | } 13 | } 14 | 15 | checkSlides() 16 | }) 17 | } 18 | 19 | function waitForAnimationEnd(vp, timeout) { 20 | return new Promise((resolve, reject) => { 21 | const startTime = Date.now() 22 | 23 | const checkAnimation = () => { 24 | if (!vp.animationQueue.isPlaying()) { 25 | resolve() 26 | } else if (Date.now() - startTime >= timeout) { 27 | reject(new Error('Timeout: animation was not finished within the specified time.')) 28 | } else { 29 | setTimeout(checkAnimation, 100) 30 | } 31 | } 32 | 33 | checkAnimation() 34 | }) 35 | } 36 | 37 | export { waitForSlidesToBeSet, waitForAnimationEnd } 38 | -------------------------------------------------------------------------------- /tests/assets/slides/more-slides.js: -------------------------------------------------------------------------------- 1 | import { slidesWithOneSlideWithOneStep } from './one-slide-one-step.js' 2 | import { slideWithMoreSteps } from './one-slide-more-steps.js' 3 | 4 | import lodashClonedeep from 'lodash.clonedeep' 5 | 6 | const slidesWithMoreSlides = generateSlides() 7 | 8 | function addPreviousSlideLastKeyframeToExpected(expected) { 9 | return expected.map((keyFrame, key) => { 10 | if (key === 0) return keyFrame 11 | 12 | return [expected[key - 1].at(-1), ...keyFrame] 13 | }) 14 | } 15 | 16 | function addLastFilterToExpected(expected) { 17 | let lastFilter 18 | return expected.map((keyFrame, key) => { 19 | return keyFrame.map((keyFrameItem) => { 20 | if ( 21 | keyFrameItem.target.data && 22 | 'filter' in keyFrameItem.target.data && 23 | keyFrameItem.target.data.filter !== null 24 | ) { 25 | lastFilter = keyFrameItem.target.data.filter 26 | } else if (lastFilter) { 27 | keyFrameItem.target.data = { filter: lastFilter } 28 | } 29 | return keyFrameItem 30 | }) 31 | }) 32 | } 33 | 34 | function generateSlides() { 35 | const slidesWithMoreSlides = [] 36 | slidesWithOneSlideWithOneStep.forEach((slide) => { 37 | const oneStepAsObjectSlidePlusMoreStepsSlide = 38 | generateOneStepAsObjectSlidePlusMoreStepsSlide(slide) 39 | oneStepAsObjectSlidePlusMoreStepsSlide.expected = addPreviousSlideLastKeyframeToExpected( 40 | oneStepAsObjectSlidePlusMoreStepsSlide.expected 41 | ) 42 | slidesWithMoreSlides.push(oneStepAsObjectSlidePlusMoreStepsSlide) 43 | 44 | const oneStepAsListSlidePlusMoreStepsSlide = 45 | generateOneStepAsListSlidePlusMoreStepsSlide(slide) 46 | oneStepAsListSlidePlusMoreStepsSlide.expected = addPreviousSlideLastKeyframeToExpected( 47 | oneStepAsListSlidePlusMoreStepsSlide.expected 48 | ) 49 | slidesWithMoreSlides.push(oneStepAsListSlidePlusMoreStepsSlide) 50 | 51 | const moreStepsSlidePlusOneStepAsObjectSlide = 52 | generateMoreStepsSlidePlusOneStepAsObjectSlide(slide) 53 | moreStepsSlidePlusOneStepAsObjectSlide.expected = addLastFilterToExpected( 54 | moreStepsSlidePlusOneStepAsObjectSlide.expected 55 | ) 56 | moreStepsSlidePlusOneStepAsObjectSlide.expected = addPreviousSlideLastKeyframeToExpected( 57 | moreStepsSlidePlusOneStepAsObjectSlide.expected 58 | ) 59 | slidesWithMoreSlides.push(moreStepsSlidePlusOneStepAsObjectSlide) 60 | 61 | const moreStepsSlidePlusOneStepAsListSlide = 62 | generateMoreStepsSlidePlusOneStepAsListSlide(slide) 63 | moreStepsSlidePlusOneStepAsListSlide.expected = addLastFilterToExpected( 64 | moreStepsSlidePlusOneStepAsListSlide.expected 65 | ) 66 | moreStepsSlidePlusOneStepAsListSlide.expected = addPreviousSlideLastKeyframeToExpected( 67 | moreStepsSlidePlusOneStepAsListSlide.expected 68 | ) 69 | slidesWithMoreSlides.push(moreStepsSlidePlusOneStepAsListSlide) 70 | }) 71 | return slidesWithMoreSlides 72 | } 73 | 74 | function generateOneStepAsObjectSlidePlusMoreStepsSlide(slide) { 75 | const slides = lodashClonedeep(slide) 76 | slides.description = `slide1(object step with ${slides.description}), slide2(more steps)` 77 | slides.input.slides.push(slideWithMoreSteps.input.slides[0]) 78 | slides.expected.push(slideWithMoreSteps.expected[0]) 79 | return slides 80 | } 81 | 82 | function generateOneStepAsListSlidePlusMoreStepsSlide(slide) { 83 | const slides = lodashClonedeep(slide) 84 | slides.description = `slide1(list step with ${slides.description}), slide2(more steps)` 85 | slides.input.slides[0] = [slides.input.slides[0]] 86 | slides.input.slides.push(slideWithMoreSteps.input.slides[0]) 87 | slides.expected.push(slideWithMoreSteps.expected[0]) 88 | return slides 89 | } 90 | 91 | function generateMoreStepsSlidePlusOneStepAsObjectSlide(slide) { 92 | const slides = lodashClonedeep(slideWithMoreSteps) 93 | slides.description = `slide1(more steps), slide2(object step with ${slide.description})` 94 | slides.input.slides.push(slide.input.slides[0]) 95 | const expected = lodashClonedeep(slide.expected[0]) 96 | slides.expected.push(expected) 97 | return slides 98 | } 99 | 100 | function generateMoreStepsSlidePlusOneStepAsListSlide(slide) { 101 | const slides = lodashClonedeep(slideWithMoreSteps) 102 | slides.description = `slide1(more steps), slide2(list step with ${slide.description})` 103 | slides.input.slides.push([slide.input.slides[0]]) 104 | const expected = lodashClonedeep(slide.expected[0]) 105 | slides.expected.push(expected) 106 | return slides 107 | } 108 | 109 | const filterNull = { 110 | target: { 111 | data: { filter: null }, 112 | config: {}, 113 | style: {} 114 | } 115 | } 116 | const filterTrue = { 117 | target: { 118 | data: { filter: true }, 119 | config: {}, 120 | style: {} 121 | } 122 | } 123 | const filterFalse = { 124 | target: { 125 | data: { filter: false }, 126 | config: {}, 127 | style: {} 128 | } 129 | } 130 | 131 | const slidesWithMoreSlidesWithMoreFilters = { 132 | input: { 133 | slides: [ 134 | {}, 135 | [{}, { filter: true }, {}, { filter: false }, {}, { filter: null }, {}], 136 | {}, 137 | { filter: true }, 138 | {}, 139 | { filter: false }, 140 | {}, 141 | { filter: null }, 142 | {} 143 | ] 144 | }, 145 | expected: [ 146 | [filterNull], 147 | [ 148 | filterNull, 149 | filterNull, 150 | filterTrue, 151 | filterTrue, 152 | filterFalse, 153 | filterFalse, 154 | filterNull, 155 | filterNull 156 | ], 157 | [filterNull, filterNull], 158 | [filterNull, filterTrue], 159 | [filterTrue, filterTrue], 160 | [filterTrue, filterFalse], 161 | [filterFalse, filterFalse], 162 | [filterFalse, filterNull], 163 | [filterNull, filterNull] 164 | ] 165 | } 166 | 167 | export { slidesWithMoreSlides, slidesWithMoreSlidesWithMoreFilters } 168 | -------------------------------------------------------------------------------- /tests/assets/slides/one-slide-more-steps.js: -------------------------------------------------------------------------------- 1 | import { filterAssets } from '../chart-params/filter.js' 2 | import { configAssets } from '../chart-params/config.js' 3 | import { styleAssets } from '../chart-params/style.js' 4 | import { animOptionsAssets } from '../chart-params/animOptions.js' 5 | 6 | const slideWithMoreSteps = { 7 | input: { 8 | slides: [ 9 | [ 10 | { 11 | filter: filterAssets.filter2, 12 | config: configAssets.config2, 13 | style: styleAssets.style2, 14 | animOptions: animOptionsAssets.animOptions2 15 | }, 16 | { 17 | filter: filterAssets.filter3, 18 | config: configAssets.config3, 19 | style: styleAssets.style3, 20 | animOptions: animOptionsAssets.animOptions3 21 | } 22 | ] 23 | ] 24 | }, 25 | expected: [ 26 | [ 27 | { 28 | target: { 29 | data: { filter: filterAssets.filter2 }, 30 | config: configAssets.config2, 31 | style: styleAssets.style2 32 | }, 33 | options: animOptionsAssets.animOptions2 34 | }, 35 | { 36 | target: { 37 | data: { filter: filterAssets.filter3 }, 38 | config: configAssets.config3, 39 | style: styleAssets.style3 40 | }, 41 | options: animOptionsAssets.animOptions3 42 | } 43 | ] 44 | ] 45 | } 46 | 47 | export { slideWithMoreSteps } 48 | -------------------------------------------------------------------------------- /tests/assets/slides/one-slide-one-empty-step.js: -------------------------------------------------------------------------------- 1 | const slidesWithOneSlideWithOneEmptyStep = [] 2 | 3 | slidesWithOneSlideWithOneEmptyStep.push({ 4 | description: 'object step', 5 | input: { 6 | slides: [{}] 7 | }, 8 | expected: [[{ target: { data: { filter: null }, config: {}, style: {} } }]] 9 | }) 10 | 11 | slidesWithOneSlideWithOneEmptyStep.push({ 12 | description: 'list step', 13 | input: { 14 | slides: [[{}]] 15 | }, 16 | expected: [[{ target: { data: { filter: null }, config: {}, style: {} } }]] 17 | }) 18 | 19 | export { slidesWithOneSlideWithOneEmptyStep } 20 | -------------------------------------------------------------------------------- /tests/assets/slides/one-slide-one-step.js: -------------------------------------------------------------------------------- 1 | import { filterAssets } from '../chart-params/filter.js' 2 | import { configAssets } from '../chart-params/config.js' 3 | import { styleAssets } from '../chart-params/style.js' 4 | import { animOptionsAssets } from '../chart-params/animOptions.js' 5 | 6 | const slidesWithOneSlideWithOneStep = [] 7 | 8 | slidesWithOneSlideWithOneStep.push( 9 | { 10 | description: 'filter', 11 | input: { 12 | slides: [{ filter: filterAssets.filter1 }] 13 | }, 14 | expected: [ 15 | [ 16 | { 17 | target: { 18 | config: {}, 19 | style: {}, 20 | data: { filter: filterAssets.filter1 } 21 | } 22 | } 23 | ] 24 | ] 25 | }, 26 | { 27 | description: 'config', 28 | input: { 29 | slides: [{ config: configAssets.config1 }] 30 | }, 31 | expected: [ 32 | [{ target: { data: { filter: null }, config: configAssets.config1, style: {} } }] 33 | ] 34 | }, 35 | { 36 | description: 'style', 37 | input: { 38 | slides: [{ style: styleAssets.style1 }] 39 | }, 40 | expected: [[{ target: { data: { filter: null }, config: {}, style: styleAssets.style1 } }]] 41 | }, 42 | { 43 | description: 'animOptions', 44 | input: { 45 | slides: [{ animOptions: animOptionsAssets.animOptions1 }] 46 | }, 47 | expected: [ 48 | [ 49 | { 50 | target: { data: { filter: null }, config: {}, style: {} }, 51 | options: animOptionsAssets.animOptions1 52 | } 53 | ] 54 | ] 55 | } 56 | ) 57 | 58 | slidesWithOneSlideWithOneStep.push( 59 | { 60 | description: 'filter, animOptions', 61 | input: { 62 | slides: [ 63 | { 64 | filter: filterAssets.filter1, 65 | animOptions: animOptionsAssets.animOptions1 66 | } 67 | ] 68 | }, 69 | expected: [ 70 | [ 71 | { 72 | target: { 73 | data: { filter: filterAssets.filter1 }, 74 | config: {}, 75 | style: {} 76 | }, 77 | options: animOptionsAssets.animOptions1 78 | } 79 | ] 80 | ] 81 | }, 82 | { 83 | description: 'config, animOptions', 84 | input: { 85 | slides: [ 86 | { 87 | config: configAssets.config1, 88 | animOptions: animOptionsAssets.animOptions1 89 | } 90 | ] 91 | }, 92 | expected: [ 93 | [ 94 | { 95 | target: { data: { filter: null }, style: {}, config: configAssets.config1 }, 96 | options: animOptionsAssets.animOptions1 97 | } 98 | ] 99 | ] 100 | }, 101 | { 102 | description: 'style, animOptions', 103 | input: { 104 | slides: [ 105 | { 106 | style: styleAssets.style1, 107 | animOptions: animOptionsAssets.animOptions1 108 | } 109 | ] 110 | }, 111 | expected: [ 112 | [ 113 | { 114 | target: { data: { filter: null }, config: {}, style: styleAssets.style1 }, 115 | options: animOptionsAssets.animOptions1 116 | } 117 | ] 118 | ] 119 | } 120 | ) 121 | 122 | slidesWithOneSlideWithOneStep.push({ 123 | description: 'filter, config, style, animOptions', 124 | input: { 125 | slides: [ 126 | { 127 | filter: filterAssets.filter1, 128 | config: configAssets.config1, 129 | style: styleAssets.style1, 130 | animOptions: animOptionsAssets.animOptions1 131 | } 132 | ] 133 | }, 134 | expected: [ 135 | [ 136 | { 137 | target: { 138 | data: { filter: filterAssets.filter1 }, 139 | config: configAssets.config1, 140 | style: styleAssets.style1 141 | }, 142 | options: animOptionsAssets.animOptions1 143 | } 144 | ] 145 | ] 146 | }) 147 | 148 | export { slidesWithOneSlideWithOneStep } 149 | -------------------------------------------------------------------------------- /tests/assets/slides/zero-slide.js: -------------------------------------------------------------------------------- 1 | const zeroSlide = { 2 | input: { 3 | slides: [] 4 | }, 5 | expected: [] 6 | } 7 | 8 | export { zeroSlide } 9 | -------------------------------------------------------------------------------- /tests/vizzu-player/e2e/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | rootDir: '../../../', 3 | testEnvironment: 'node', 4 | testMatch: ['**/vizzu-player/e2e/*.test.cjs'], 5 | transform: {}, 6 | testTimeout: 25000 7 | } 8 | 9 | module.exports = config 10 | -------------------------------------------------------------------------------- /tests/vizzu-player/e2e/vp.data.label.test.cjs: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer') 2 | const path = require('path') 3 | const express = require('express') 4 | 5 | const testCases = require('../../assets/data-tests/label/index') 6 | const testDataSetPromises = [] 7 | testCases.forEach((testCase) => { 8 | testDataSetPromises.push(import(`../../assets/data-sets/${testCase.name}.mjs`)) 9 | }) 10 | 11 | describe('if slides set', () => { 12 | let testDataSets 13 | 14 | let server 15 | let browser 16 | 17 | beforeAll(async () => { 18 | testDataSets = await Promise.all(testDataSetPromises) 19 | 20 | const app = express() 21 | app.use('/', express.static(path.join(__dirname, '../../../'))) 22 | server = app.listen(0, () => {}) 23 | 24 | browser = await puppeteer.launch({ 25 | headless: 'new', 26 | args: ['--disable-web-security', '--no-sandbox'] 27 | }) 28 | }) 29 | 30 | afterAll(async () => { 31 | await browser.close() 32 | server.close() 33 | }) 34 | 35 | // TODO: recheck all slides, not just the first one 36 | testCases.forEach((testCase, index) => { 37 | test(`labels should should display the expected values - ${testCase.name}`, async () => { 38 | const page = await browser.newPage() 39 | 40 | const slides = testDataSets[index].default 41 | 42 | const keyFramesLengths = slides.slides.map((slide) => slide.length) 43 | const allKeyFrames = keyFramesLengths.reduce((accumulator, currentValue) => { 44 | return accumulator + currentValue 45 | }, 0) 46 | 47 | const data = await page.evaluate( 48 | async (serverPort, name, allKeyFrames) => { 49 | document.body.innerHTML = '' 50 | 51 | let resolveComplete = null 52 | const completed = new Promise((resolve) => { 53 | resolveComplete = resolve 54 | }) 55 | 56 | let completeCounter = 0 57 | const datas = {} 58 | 59 | function labelDrawHandler(event) { 60 | // check all slides, recheck first slide 61 | if (completeCounter < allKeyFrames + 1) { 62 | if (!datas[completeCounter]) { 63 | datas[completeCounter] = [] 64 | } 65 | datas[completeCounter].push(event.detail.text) 66 | } 67 | } 68 | 69 | const complete = () => { 70 | // check all slides, recheck first slide 71 | if (completeCounter === allKeyFrames + 1) { 72 | resolveComplete(true) 73 | } 74 | if (datas[completeCounter]) datas[completeCounter].sort() 75 | completeCounter++ 76 | } 77 | 78 | await import(`http://127.0.0.1:${serverPort}/src/vizzu-player.js`) 79 | 80 | const slidesModule = await import( 81 | `http://127.0.0.1:${serverPort}/tests/assets/data-sets/${name}.mjs` 82 | ) 83 | const slides = slidesModule.default 84 | 85 | const vp = document.getElementById('story') 86 | window.vp = vp 87 | vp.initializing.then(async (chart) => { 88 | vp.style.cssText = 'width: 100%;height: 100%;' 89 | chart.feature('tooltip', true) 90 | chart.on('plot-marker-label-draw', labelDrawHandler) 91 | chart.on('animation-complete', complete) 92 | vp.slides = slides 93 | }) 94 | 95 | await completed 96 | 97 | return datas 98 | }, 99 | server.address().port, 100 | testCase.name, 101 | allKeyFrames 102 | ) 103 | 104 | expect(data).toStrictEqual(testCase.expected) 105 | }) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /tests/vizzu-player/unit/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | rootDir: '../../../', 3 | collectCoverage: true, 4 | coverageProvider: 'v8', 5 | coverageDirectory: '.coverage', 6 | coverageThreshold: { 7 | global: { 8 | branches: 0, 9 | functions: 0, 10 | lines: 0, 11 | statements: 0 12 | } 13 | }, 14 | coveragePathIgnorePatterns: ['assets', 'node_modules'], 15 | testEnvironment: 'jsdom', 16 | testMatch: ['**/vizzu-player/unit/*.test.js'], 17 | transform: {}, 18 | moduleNameMapper: { 19 | 'https://cdn.jsdelivr.net/npm/vizzu@0.16/dist/vizzu.min.js': 20 | '../tests/assets/mocks/vizzu-cdn.js' 21 | } 22 | } 23 | 24 | export default config 25 | -------------------------------------------------------------------------------- /tests/vizzu-player/unit/vp.animationqueue.test.js: -------------------------------------------------------------------------------- 1 | import { zeroSlide } from '../../assets/slides/zero-slide.js' 2 | import { slideWithMoreSteps } from '../../assets/slides/one-slide-more-steps.js' 3 | 4 | import { waitForSlidesToBeSet, waitForAnimationEnd } from '../../assets/slides/asset-functions.js' 5 | 6 | import VizzuPlayer from '../../../src/vizzu-player.js' 7 | 8 | describe('if animationQueue is called', () => { 9 | const shouldBeEmpty = 'getter should return an empty the queue' 10 | const shouldBe = ' should return' 11 | 12 | let slides 13 | let vp 14 | 15 | beforeEach(() => { 16 | vp = new VizzuPlayer() 17 | slides = { slides: [] } 18 | for (let i = 0; i < 6; i++) { 19 | slides.slides.push(...slideWithMoreSteps.input.slides) 20 | } 21 | }) 22 | 23 | describe('queue is empty with zero slide', () => { 24 | test(`${shouldBeEmpty}`, () => { 25 | const vp = new VizzuPlayer() 26 | vp.slides = zeroSlide.input 27 | return vp.connectedCallback().then(() => { 28 | return waitForSlidesToBeSet(vp, 5000).then(() => { 29 | const isEmpty = vp.animationQueue.isEmpty() 30 | expect(isEmpty).toStrictEqual(true) 31 | }) 32 | }) 33 | }) 34 | }) 35 | 36 | describe('queue the slide counter after the next button', () => { 37 | test(`just one, ${shouldBe} 1`, () => { 38 | vp.slides = slides 39 | return vp.connectedCallback().then(() => { 40 | return waitForSlidesToBeSet(vp, 5000).then(() => { 41 | vp.animationQueue.abort() 42 | vp.next() 43 | return waitForAnimationEnd(vp, 5000).then(async () => { 44 | const currentSlide = vp.animationQueue.getParameter('currentSlide') 45 | expect(currentSlide).toStrictEqual(1) 46 | }) 47 | }) 48 | }) 49 | }) 50 | 51 | test(`jump to third slide, ${shouldBe} 2`, () => { 52 | vp.slides = slides 53 | return vp.connectedCallback().then(() => { 54 | return waitForSlidesToBeSet(vp, 5000).then(() => { 55 | vp.animationQueue.abort() 56 | vp.setSlide(2) 57 | return waitForAnimationEnd(vp, 5000).then(async () => { 58 | const currentSlide = vp.animationQueue.getParameter('currentSlide') 59 | expect(currentSlide).toStrictEqual(2) 60 | }) 61 | }) 62 | }) 63 | }) 64 | }) 65 | describe('queue abort', () => { 66 | test(`after is empty`, () => { 67 | const vp = new VizzuPlayer() 68 | vp.slides = slides 69 | return vp.connectedCallback().then(() => { 70 | return waitForSlidesToBeSet(vp, 5000).then(() => { 71 | vp.next() 72 | vp.animationQueue.abort() 73 | const isEmpty = vp.animationQueue.isEmpty() 74 | expect(isEmpty).toStrictEqual(true) 75 | }) 76 | }) 77 | }) 78 | }) 79 | 80 | describe('queue is paused', () => { 81 | it(`after is paused and not empty`, () => { 82 | const vp = new VizzuPlayer() 83 | vp.slides = slides 84 | return vp.connectedCallback().then(() => { 85 | return waitForSlidesToBeSet(vp, 5000).then(() => { 86 | vp.next() 87 | vp.animationQueue.pause() 88 | const isEmpty = vp.animationQueue.isEmpty() 89 | expect(vp.animationQueue.isPaused()).toStrictEqual(true) 90 | expect(isEmpty).toStrictEqual(false) 91 | }) 92 | }) 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /tools/ci/markdown_format.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | import mdformat 4 | 5 | 6 | class Markdown: 7 | # pylint: disable=too-few-public-methods 8 | 9 | @staticmethod 10 | def format(content: str) -> str: 11 | return mdformat.text( # type: ignore 12 | content, 13 | options={"wrap": 80, "end-of-line": "keep", "line-length": 70}, 14 | extensions={ 15 | "gfm", 16 | "tables", 17 | "footnote", 18 | "frontmatter", 19 | "configblack", 20 | "mkdocs", 21 | }, 22 | codeformatters={ 23 | "python", 24 | "bash", 25 | "sh", 26 | "json", 27 | "toml", 28 | "yaml", 29 | "javascript", 30 | "js", 31 | "css", 32 | "html", 33 | "xml", 34 | }, 35 | ) 36 | -------------------------------------------------------------------------------- /tools/ci/std_check.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | import sys 4 | import subprocess 5 | 6 | 7 | def main() -> None: 8 | with subprocess.Popen( 9 | sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.PIPE 10 | ) as process: 11 | out, err = process.communicate() 12 | if out or err or process.returncode: 13 | if out: 14 | print(out.decode()) 15 | if err: 16 | print(err.decode()) 17 | raise RuntimeError(f"failed to run {sys.argv[1]}") 18 | 19 | 20 | main() 21 | -------------------------------------------------------------------------------- /tools/ci/version.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from contextlib import chdir 4 | import json 5 | from pathlib import Path 6 | import sys 7 | 8 | 9 | REPO_PATH = Path(__file__).parent / ".." / ".." 10 | TOOLS_PATH = REPO_PATH / "tools" 11 | 12 | sys.path.insert(0, str(TOOLS_PATH / "modules")) 13 | 14 | from vizzu import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 15 | Vizzu, 16 | ) 17 | 18 | 19 | class Version: 20 | # pylint: disable=too-few-public-methods 21 | 22 | @staticmethod 23 | def set_readme_version(restore: bool) -> None: 24 | with open("README.md", "r", encoding="utf8") as fh_readme: 25 | content = fh_readme.read() 26 | 27 | content = Vizzu.set_version(content, restore) 28 | 29 | with open("README.md", "w", encoding="utf8") as fh_readme: 30 | fh_readme.write(content) 31 | 32 | 33 | def main() -> None: 34 | with chdir(REPO_PATH): 35 | restore = json.loads(sys.argv[1].lower()) 36 | Version.set_readme_version(restore) 37 | 38 | 39 | main() 40 | -------------------------------------------------------------------------------- /tools/docs/config.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from pathlib import Path 4 | from typing import Optional 5 | 6 | import yaml 7 | 8 | 9 | class MkdocsConfig: 10 | # pylint: disable=too-few-public-methods 11 | 12 | @staticmethod 13 | def _format_url(url: Optional[str]) -> Optional[str]: 14 | if url and url.endswith("/"): 15 | return url[:-1] 16 | return url 17 | 18 | @staticmethod 19 | def _format(config: dict) -> dict: 20 | if "site_url" in config: 21 | config["site_url"] = MkdocsConfig._format_url(config["site_url"]) 22 | return config 23 | 24 | @staticmethod 25 | def load(config: Path) -> dict: 26 | with open(config, "rt", encoding="utf8") as f_yml: 27 | return MkdocsConfig._format(yaml.load(f_yml, Loader=yaml.FullLoader)) 28 | -------------------------------------------------------------------------------- /tools/docs/deploy.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from contextlib import chdir 4 | from pathlib import Path 5 | from subprocess import Popen 6 | import sys 7 | 8 | 9 | REPO_PATH = Path(__file__).parent / ".." / ".." 10 | TOOLS_PATH = REPO_PATH / "tools" 11 | MKDOCS_PATH = TOOLS_PATH / "docs" 12 | 13 | sys.path.insert(0, str(TOOLS_PATH / "modules")) 14 | 15 | from vizzu import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 16 | Vizzu, 17 | ) 18 | 19 | 20 | class Deploy: 21 | latest: bool = True 22 | 23 | @staticmethod 24 | def mike() -> None: 25 | version = Vizzu.get_vizzustory_version() 26 | 27 | params = [ 28 | "mike", 29 | "deploy", 30 | ] 31 | if Deploy.latest: 32 | params.append("-u") 33 | params.append(version) 34 | if Deploy.latest: 35 | params.append("latest") 36 | params.append("-F") 37 | params.append("tools/docs/mkdocs.yml") 38 | 39 | with Popen( 40 | params, 41 | ) as process: 42 | process.communicate() 43 | 44 | if process.returncode: 45 | raise RuntimeError("failed to run mike") 46 | 47 | @staticmethod 48 | def set_config(restore: bool) -> None: 49 | with open(MKDOCS_PATH / "mkdocs.yml", "r", encoding="utf8") as fh_readme: 50 | content = fh_readme.read() 51 | 52 | if not restore: 53 | if not Deploy.latest: 54 | content = content.replace( 55 | "- content.action.edit", 56 | "# - content.action.edit", 57 | ) 58 | else: 59 | if not Deploy.latest: 60 | content = content.replace( 61 | "# - content.action.edit", 62 | "- content.action.edit", 63 | ) 64 | 65 | with open(MKDOCS_PATH / "mkdocs.yml", "w", encoding="utf8") as fh_readme: 66 | fh_readme.write(content) 67 | 68 | 69 | def main() -> None: 70 | with chdir(REPO_PATH): 71 | Deploy.set_config(restore=False) 72 | Deploy.mike() 73 | Deploy.set_config(restore=True) 74 | 75 | 76 | main() 77 | -------------------------------------------------------------------------------- /tools/docs/examples/gen_examples.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from contextlib import chdir 4 | from pathlib import Path 5 | import sys 6 | import re 7 | 8 | import mkdocs_gen_files # type: ignore 9 | 10 | 11 | REPO_PATH = Path(__file__).parent / ".." / ".." / ".." 12 | TOOLS_PATH = REPO_PATH / "tools" 13 | MKDOCS_PATH = TOOLS_PATH / "docs" 14 | JS_ASSETS_PATH = "assets/javascripts" 15 | 16 | sys.path.insert(0, str(TOOLS_PATH / "modules")) 17 | 18 | from vizzu import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 19 | Vizzu, 20 | VIZZUSTORY_SITE_URL, 21 | ) 22 | 23 | 24 | class GenExamples: 25 | # pylint: disable=too-few-public-methods 26 | 27 | @staticmethod 28 | def _create_index() -> None: 29 | with mkdocs_gen_files.open("examples/index.md", "a") as fh_index: 30 | meta = """---\nhide:\n - toc\n---""" 31 | fh_index.write(f"{meta}\n\n") 32 | fh_index.write("# Examples\n") 33 | fh_index.write(f'\n') 34 | 35 | @staticmethod 36 | def _add_image(example: Path) -> None: 37 | with mkdocs_gen_files.open("examples/index.md", "a") as fh_index: 38 | fh_index.write( 39 | "[" 40 | + f"![{example.stem}]" 41 | + f"(./{example.stem}.png)" 42 | + "{ class='image-gallery' }" 43 | + "]" 44 | + f"(./{example.name})\n" 45 | ) 46 | 47 | @staticmethod 48 | def _remove_eslint_comments(content: str) -> str: 49 | regex = r"^\s*//\s*eslint.*?\n" 50 | return re.sub(regex, "", content, flags=re.MULTILINE) 51 | 52 | @staticmethod 53 | def _set_csv2js_url(content: str) -> str: 54 | return content.replace( 55 | "../../assets/javascripts/csv2js.js", 56 | f"{VIZZUSTORY_SITE_URL}/latest/assets/javascripts/csv2js.js", 57 | ) 58 | 59 | @staticmethod 60 | def _set_csv_url(example: Path, content: str) -> str: 61 | return content.replace( 62 | 'Csv2Js.csv("./', 63 | f'Csv2Js.csv("{VIZZUSTORY_SITE_URL}/latest/examples/{example.stem}/', 64 | ) 65 | 66 | @staticmethod 67 | def _get_js(example: Path) -> str: 68 | with open( 69 | example.parent / example.stem / "main.js", "r", encoding="utf8" 70 | ) as fh_js: 71 | return fh_js.read() 72 | 73 | @staticmethod 74 | def _generate_example_js(example: Path, js_content: str) -> None: 75 | content = Vizzu.set_vizzustory_backend_url(js_content) 76 | with mkdocs_gen_files.open(f"examples/{example.stem}/main.js", "w") as fh_js: 77 | fh_js.write(content) 78 | 79 | @staticmethod 80 | def _generate_example_md(example: Path, js_content: str) -> None: 81 | regex = r"// {% include \"(\.\/.*\.js)\" %}" 82 | with open(example.parent / example.name, "r", encoding="utf8") as fh_md: 83 | content = fh_md.read() 84 | content = re.sub(regex, js_content, content) 85 | content = GenExamples._remove_eslint_comments(content) 86 | content = GenExamples._set_csv2js_url(content) 87 | content = GenExamples._set_csv_url(example, content) 88 | content = Vizzu.set_version(content) 89 | mkdocs_gen_files.set_edit_path( 90 | f"examples/{example.name}", f"examples/{example.name}" 91 | ) 92 | with mkdocs_gen_files.open(f"examples/{example.name}", "w") as fh_md: 93 | fh_md.write(content) 94 | 95 | @staticmethod 96 | def _generate_example(example: Path) -> None: 97 | js_content = GenExamples._get_js(example) 98 | GenExamples._generate_example_js(example, js_content) 99 | GenExamples._generate_example_md(example, js_content) 100 | GenExamples._add_image(example) 101 | 102 | @staticmethod 103 | def generate() -> None: 104 | GenExamples._create_index() 105 | example_path = REPO_PATH / "docs" / "examples" 106 | for example in sorted(example_path.glob("*.md")): 107 | GenExamples._generate_example(example) 108 | 109 | 110 | def main() -> None: 111 | with chdir(REPO_PATH): 112 | GenExamples.generate() 113 | 114 | 115 | main() 116 | -------------------------------------------------------------------------------- /tools/docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | strict: true 2 | 3 | site_name: vizzu-story 4 | site_url: https://vizzu-story.vizzuhq.com 5 | copyright: Copyright © 2022-2025 Vizzu Inc. 6 | 7 | docs_dir: ../../docs 8 | site_dir: ../../site 9 | 10 | repo_url: https://github.com/vizzuhq/vizzu-story-js 11 | edit_uri: https://github.com/vizzuhq/vizzu-story-js/edit/main/docs 12 | use_directory_urls: true 13 | 14 | theme: 15 | name: material 16 | palette: 17 | scheme: vizzu 18 | font: 19 | text: Roboto 20 | code: Roboto Mono 21 | logo: assets/logo-white.svg 22 | favicon: assets/favicon.svg 23 | custom_dir: ./overrides 24 | features: 25 | - toc.follow 26 | - search.suggest 27 | - search.highlight 28 | - navigation.top 29 | - navigation.footer 30 | - content.code.copy 31 | - content.action.edit 32 | 33 | extra_css: 34 | - assets/stylesheets/vizzu.css 35 | - assets/stylesheets/gallery.css 36 | - assets/stylesheets/highlight.css 37 | 38 | extra_javascript: 39 | - assets/javascripts/extlinks.js 40 | - //cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.7/build/highlight.min.js 41 | - //cdn.jsdelivr.net/npm/highlightjs-line-numbers.js@2.8.0/dist/highlightjs-line-numbers.min.js 42 | - assets/javascripts/highlight.js 43 | 44 | extra: 45 | version: 46 | provider: mike 47 | default: latest 48 | social: 49 | - icon: fontawesome/brands/slack 50 | name: Vizzu on Slack 51 | link: https://join.slack.com/t/vizzu-community/shared_invite/zt-w2nqhq44-2CCWL4o7qn2Ns1EFSf9kEg 52 | - icon: fontawesome/brands/twitter 53 | name: Vizzu on Twitter 54 | link: https://twitter.com/VizzuHQ 55 | - icon: fontawesome/brands/reddit 56 | name: Vizzu on Reddit 57 | link: https://www.reddit.com/user/VizzuHQ/?sort=top 58 | - icon: fontawesome/brands/github 59 | name: Vizzu on GitHub 60 | link: https://github.com/vizzuhq/ 61 | 62 | markdown_extensions: 63 | - pymdownx.tasklist: 64 | custom_checkbox: true 65 | - attr_list 66 | - md_in_html 67 | - admonition 68 | - pymdownx.highlight: 69 | use_pygments: false 70 | - pymdownx.details 71 | - pymdownx.superfences 72 | 73 | plugins: 74 | - mike: 75 | version_selector: true 76 | alias_type: symlink 77 | canonical_version: latest 78 | redirect_template: ./tools/mkdocs/overrides/mike/redirect.html 79 | - search 80 | - section-index 81 | - literate-nav: 82 | implicit_index: true 83 | - autorefs 84 | - gen-files: 85 | scripts: 86 | - pages/gen_pages.py 87 | - examples/gen_examples.py 88 | - reference/gen_reference.py 89 | - exclude: 90 | glob: 91 | - reference.md 92 | 93 | nav: 94 | - Home: index.md 95 | - installation.md 96 | - Tutorial: 97 | - tutorial/index.md 98 | - tutorial/data.md 99 | - tutorial/initialization.md 100 | - Building blocks: tutorial/building_blocks.md 101 | - Examples: examples/ 102 | - Code reference: reference/README.md 103 | - Development: 104 | - dev/index.md 105 | - Contributing: CONTRIBUTING.md 106 | - Code of Conduct: CODE_OF_CONDUCT.md 107 | - License: LICENSE.md 108 | -------------------------------------------------------------------------------- /tools/docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block extrahead %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% endblock %} {% block outdated %} You're not viewing the latest version. 20 | 21 | Click here to go to latest. 22 | 23 | {% endblock %} {% block content %} {% if page.meta.data_url %} 24 | 25 | {% include ".icons/material/database.svg" %} 26 | 27 | {% endif %} {% if page.meta.csv_url %} 28 | 29 | {% include ".icons/fontawesome/solid/file-csv.svg" %} 30 | 31 | {% endif %}{{ super() }} {% endblock content %} 32 | -------------------------------------------------------------------------------- /tools/docs/overrides/mike/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | Redirecting 28 | 31 | 34 | 35 | 36 | Redirecting to {{href}}... 37 | 38 | 39 | -------------------------------------------------------------------------------- /tools/docs/pages/gen_pages.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from contextlib import chdir 4 | import os 5 | from pathlib import Path 6 | from typing import Union, Optional, List 7 | import sys 8 | 9 | import mkdocs_gen_files # type: ignore 10 | 11 | 12 | REPO_PATH = Path(__file__).parent / ".." / ".." / ".." 13 | TOOLS_PATH = REPO_PATH / "tools" 14 | MKDOCS_PATH = TOOLS_PATH / "docs" 15 | 16 | sys.path.insert(0, str(TOOLS_PATH / "modules")) 17 | sys.path.insert(0, str(TOOLS_PATH / "ci")) 18 | sys.path.insert(0, str(MKDOCS_PATH)) 19 | 20 | from vizzu import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 21 | Vizzu, 22 | ) 23 | from config import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 24 | MkdocsConfig, 25 | ) 26 | 27 | 28 | class IndexPages: 29 | # pylint: disable=too-few-public-methods 30 | 31 | @staticmethod 32 | def _write_index_file(file: str, toc: list) -> None: 33 | for item in toc: 34 | if isinstance(item, str): 35 | IndexPages._write_str_index(file, item) 36 | elif isinstance(item, dict): 37 | IndexPages._write_dict_index(file, item) 38 | else: 39 | raise NotImplementedError(f"{item}") 40 | 41 | @staticmethod 42 | def _write_str_index(file: str, item: str) -> None: 43 | with mkdocs_gen_files.open(file, "a") as f_index: 44 | parts = item.split("/") 45 | part = parts[-1].replace(".md", "").capitalize() 46 | link = Path(os.path.relpath(item, Path(file).parent)) 47 | f_index.write(f"* [{part}]({link})\n") 48 | 49 | @staticmethod 50 | def _write_dict_index(file: str, item: dict) -> None: 51 | with mkdocs_gen_files.open(file, "a") as f_index: 52 | for key in item: 53 | if isinstance(item[key], str): 54 | link = Path(os.path.relpath(item[key], Path(file).parent)) 55 | f_index.write(f"* [{key}]({link})\n") 56 | continue 57 | if item[key] and isinstance(item[key], list): 58 | if isinstance(item[key][0], str): 59 | if item[key][0].endswith("index.md"): 60 | link = Path( 61 | os.path.relpath(item[key][0], Path(file).parent) 62 | ) 63 | f_index.write(f"* [{key}]({link})\n") 64 | continue 65 | raise NotImplementedError(f"{item}") 66 | 67 | @staticmethod 68 | def generate( 69 | nav_item: Union[list, dict, str], skip: Optional[List[str]] = None 70 | ) -> None: 71 | if isinstance(nav_item, list): 72 | if ( 73 | nav_item 74 | and isinstance(nav_item[0], str) 75 | and nav_item[0].endswith("index.md") 76 | ): 77 | if not skip or nav_item[0] not in skip: 78 | original = Path("docs", nav_item[0]) 79 | if original.exists(): 80 | mkdocs_gen_files.set_edit_path(nav_item[0], nav_item[0]) 81 | with mkdocs_gen_files.open(nav_item[0], "a") as f_index: 82 | f_index.write("\n") 83 | IndexPages._write_index_file(file=nav_item[0], toc=nav_item[1:]) 84 | for item in nav_item: 85 | IndexPages.generate(nav_item=item, skip=skip) 86 | elif isinstance(nav_item, dict): 87 | for key in nav_item: 88 | IndexPages.generate(nav_item=nav_item[key], skip=skip) 89 | 90 | 91 | class Page: 92 | # pylint: disable=too-few-public-methods 93 | 94 | @staticmethod 95 | def generate(src: Path, dst: str, pos: str, site: str, keep: bool = False) -> None: 96 | with open(src, "rt", encoding="utf8") as f_src: 97 | content = f_src.read() 98 | 99 | content = content.replace(f"{site}/latest/", pos).replace(f"{site}/latest", pos) 100 | content = Vizzu.set_version(content) 101 | if keep: 102 | content = f"
{content}
" 103 | 104 | mkdocs_gen_files.set_edit_path(dst, ".." / Path(dst).parent / Path(src).name) 105 | with mkdocs_gen_files.open(dst, "w") as f_dst: 106 | f_dst.write(content) 107 | 108 | 109 | class Docs: 110 | # pylint: disable=too-few-public-methods 111 | 112 | @staticmethod 113 | def generate(skip: Optional[List[str]] = None) -> None: 114 | docs_path = REPO_PATH / "docs" 115 | for path in list(docs_path.rglob("*.md")) + list(docs_path.rglob("*.js")): 116 | if skip and path.name in skip: 117 | continue 118 | with open(path, "rt", encoding="utf8") as f_src: 119 | dst = path.relative_to(docs_path) 120 | content = f_src.read() 121 | if path.suffix == ".md": 122 | content = Vizzu.set_version(content) 123 | mkdocs_gen_files.set_edit_path(dst, dst) 124 | with mkdocs_gen_files.open(dst, "w") as f_dst: 125 | f_dst.write(content) 126 | 127 | 128 | def main() -> None: 129 | with chdir(REPO_PATH): 130 | config = MkdocsConfig.load(MKDOCS_PATH / "mkdocs.yml") 131 | 132 | Docs.generate(skip=["reference.md"]) 133 | 134 | IndexPages.generate(nav_item=config["nav"]) 135 | 136 | Page.generate( 137 | src=REPO_PATH / "README.md", 138 | dst="index.md", 139 | pos="./", 140 | site=config["site_url"], 141 | ) 142 | 143 | Page.generate( 144 | src=REPO_PATH / "CONTRIBUTING.md", 145 | dst="CONTRIBUTING.md", 146 | pos="../", 147 | site=config["site_url"], 148 | ) 149 | 150 | Page.generate( 151 | src=REPO_PATH / "CODE_OF_CONDUCT.md", 152 | dst="CODE_OF_CONDUCT.md", 153 | pos="../", 154 | site=config["site_url"], 155 | ) 156 | 157 | Page.generate( 158 | src=REPO_PATH / "LICENSE", 159 | dst="LICENSE.md", 160 | pos="../", 161 | site=config["site_url"], 162 | keep=True, 163 | ) 164 | 165 | 166 | main() 167 | -------------------------------------------------------------------------------- /tools/docs/reference/gen_reference.cjs: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | 3 | const repoPath = path.join(__dirname, '..', '..', '..') 4 | const mkdocsPath = path.join(repoPath, 'tools', 'docs') 5 | const genPath = path.join(mkdocsPath, 'reference') 6 | const srcPath = path.join(repoPath, 'src') 7 | 8 | async function reference() { 9 | const TypeDoc = await import('typedoc') 10 | const app = await TypeDoc.Application.bootstrapWithPlugins( 11 | { 12 | plugin: ['typedoc-plugin-markdown', 'typedoc-plugin-rename-defaults'], 13 | entryPoints: [path.join(srcPath, 'vizzu-story.d.ts')], 14 | entryPointStrategy: 'expand', 15 | tsconfig: path.join(genPath, 'tsconfig.json'), 16 | name: 'Vizzu-Story', 17 | disableSources: true, 18 | excludePrivate: true, 19 | theme: 'markdown' 20 | }, 21 | [new TypeDoc.TSConfigReader()] 22 | ) 23 | 24 | const project = await app.convert() 25 | 26 | if (project) { 27 | const outputDir = path.join(genPath, 'tmp') 28 | return app.generateDocs(project, outputDir) 29 | } 30 | } 31 | 32 | reference() 33 | -------------------------------------------------------------------------------- /tools/docs/reference/gen_reference.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from contextlib import chdir 4 | import os 5 | from pathlib import Path 6 | import shutil 7 | import sys 8 | 9 | import mkdocs_gen_files 10 | 11 | 12 | REPO_PATH = Path(__file__).parent / ".." / ".." / ".." 13 | TOOLS_PATH = REPO_PATH / "tools" 14 | MKDOCS_PATH = TOOLS_PATH / "docs" 15 | GEN_PATH = MKDOCS_PATH / "reference" 16 | SRC_PATH = REPO_PATH / "src" 17 | 18 | sys.path.insert(0, str(TOOLS_PATH / "modules")) 19 | sys.path.insert(0, str(TOOLS_PATH / "ci")) 20 | 21 | from node import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 22 | Node, 23 | ) 24 | from vizzu import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 25 | Vizzu, 26 | ) 27 | from markdown_format import ( # pylint: disable=import-error, wrong-import-position, wrong-import-order 28 | Markdown, 29 | ) 30 | 31 | 32 | class Reference: 33 | # pylint: disable=too-few-public-methods 34 | 35 | @staticmethod 36 | def generate(folder: str) -> None: 37 | Reference._gen_reference(folder) 38 | 39 | @staticmethod 40 | def _gen_reference(folder: str) -> None: 41 | tmp_dir = GEN_PATH / "tmp" 42 | if os.path.exists(tmp_dir): 43 | shutil.rmtree(tmp_dir) 44 | 45 | Node.node(False, GEN_PATH / "gen_reference.cjs") 46 | 47 | for path in Path(tmp_dir).rglob("*.md"): 48 | with open(path, "rt", encoding="utf8") as f_src: 49 | if (Path(tmp_dir) / "README.md").resolve() == path.resolve(): 50 | continue 51 | content = f_src.read() 52 | content = content.replace("##### ", "").replace("#### ", "") 53 | content = Vizzu.set_version(content) 54 | content = Markdown.format(content) 55 | with mkdocs_gen_files.open( 56 | f"{folder}/{path.relative_to(tmp_dir)}", "w" 57 | ) as f_dst: 58 | f_dst.write(content) 59 | 60 | 61 | def main() -> None: 62 | with chdir(REPO_PATH): 63 | Reference.generate("reference") 64 | 65 | 66 | main() 67 | -------------------------------------------------------------------------------- /tools/docs/reference/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": true 4 | }, 5 | "files": ["../../../src/vizzu-story.d.ts"] 6 | } 7 | -------------------------------------------------------------------------------- /tools/modules/node.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from pathlib import Path 4 | from subprocess import PIPE, Popen 5 | from typing import Union 6 | 7 | 8 | class Node: 9 | @staticmethod 10 | def node(strict: bool, script: Union[str, Path], *params: str) -> str: 11 | return Node.run(strict, "node", script, *params) 12 | 13 | @staticmethod 14 | def npx(strict: bool, script: Union[str, Path], *params: str) -> str: 15 | return Node.run(strict, "npx", script, *params) 16 | 17 | @staticmethod 18 | def run(strict: bool, exe: str, script: Union[str, Path], *params: str) -> str: 19 | with Popen( 20 | [exe, script, *params], 21 | stdin=PIPE, 22 | stdout=PIPE, 23 | stderr=PIPE, 24 | ) as node: 25 | outs, errs = node.communicate() 26 | 27 | if errs: 28 | print(errs.decode()) 29 | 30 | if node.returncode or (strict and errs): 31 | raise RuntimeError(f"failed to run {Path(script).stem}") 32 | 33 | return outs.decode() 34 | -------------------------------------------------------------------------------- /tools/modules/vizzu.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from pathlib import Path 4 | import re 5 | 6 | 7 | REPO_PATH = Path(__file__).parent / ".." / ".." 8 | 9 | VIZZUSTORY_BACKEND_URL = "" 10 | 11 | VIZZUSTORY_VERSION = "" 12 | VIZZU_VERSION = "" 13 | 14 | VIZZUSTORY_SITE_URL = "https://vizzu-story.vizzuhq.com" 15 | VIZZUSTORY_CDN_URL = "https://cdn.jsdelivr.net/npm/vizzu-story" 16 | VIZZU_SITE_URL = "https://lib.vizzuhq.com" 17 | VIZZU_CDN_URL = "https://cdn.jsdelivr.net/npm/vizzu" 18 | 19 | 20 | class Vizzu: 21 | _vizzustory_version = "" 22 | _vizzu_version = "" 23 | 24 | @staticmethod 25 | def get_vizzustory_backend_url() -> str: 26 | if VIZZUSTORY_BACKEND_URL: 27 | return VIZZUSTORY_BACKEND_URL 28 | version = Vizzu.get_vizzustory_version() 29 | return f"{VIZZUSTORY_CDN_URL}@{version}/dist/vizzu-story.min.js" 30 | 31 | @staticmethod 32 | def set_vizzustory_backend_url(content: str, restore: bool = False) -> str: 33 | url = Vizzu.get_vizzustory_backend_url() 34 | if not restore: 35 | content = content.replace( 36 | f"{VIZZUSTORY_CDN_URL}@latest/dist/vizzu-story.min.js", 37 | url, 38 | ) 39 | else: 40 | content = content.replace( 41 | url, 42 | f"{VIZZUSTORY_CDN_URL}@latest/dist/vizzu-story.min.js", 43 | ) 44 | return content 45 | 46 | @staticmethod 47 | def get_vizzustory_full_version() -> list: 48 | with open( 49 | REPO_PATH / "package.json", 50 | "r", 51 | encoding="utf8", 52 | ) as f_version: 53 | content = f_version.read() 54 | version = re.search(r"\"version\":\s\"(\d+).(\d+).(\d+)\"", content) 55 | return [ 56 | version.group(1), # type: ignore 57 | version.group(2), # type: ignore 58 | version.group(3), # type: ignore 59 | ] 60 | 61 | @staticmethod 62 | def get_vizzustory_version() -> str: 63 | if VIZZUSTORY_VERSION: 64 | return VIZZUSTORY_VERSION 65 | if not Vizzu._vizzustory_version: 66 | version_parts = Vizzu.get_vizzustory_full_version() 67 | Vizzu._vizzustory_version = f"{version_parts[0]}.{version_parts[1]}" 68 | return Vizzu._vizzustory_version 69 | 70 | @staticmethod 71 | def get_vizzu_version() -> str: 72 | if VIZZU_VERSION: 73 | return VIZZU_VERSION 74 | if not Vizzu._vizzu_version: 75 | with open( 76 | REPO_PATH / "package.json", 77 | "r", 78 | encoding="utf8", 79 | ) as f_version: 80 | content = f_version.read() 81 | version = re.search(r"\"vizzu\":\s\"~(\d+).(\d+).(\d+)\"", content) 82 | Vizzu._vizzu_version = f"{version.group(1)}.{version.group(2)}" # type: ignore 83 | return Vizzu._vizzu_version 84 | 85 | @staticmethod 86 | def set_version(content: str, restore: bool = False) -> str: 87 | vizzu_version = Vizzu.get_vizzu_version() 88 | vizzustory_version = Vizzu.get_vizzustory_version() 89 | if not restore: 90 | content = content.replace( 91 | f"{VIZZUSTORY_SITE_URL}/latest/", 92 | f"{VIZZUSTORY_SITE_URL}/{vizzustory_version}/", 93 | ) 94 | content = content.replace( 95 | f"{VIZZU_SITE_URL}/latest/", 96 | f"{VIZZU_SITE_URL}/{vizzu_version}/", 97 | ) 98 | content = content.replace( 99 | f"{VIZZUSTORY_CDN_URL}@latest/dist/vizzu-story.min.js", 100 | f"{VIZZUSTORY_CDN_URL}@{vizzustory_version}/dist/vizzu-story.min.js", 101 | ) 102 | content = content.replace( 103 | f"{VIZZU_CDN_URL}@latest/dist/vizzu.min.js", 104 | f"{VIZZU_CDN_URL}@{vizzu_version}/dist/vizzu.min.js", 105 | ) 106 | else: 107 | content = content.replace( 108 | f"{VIZZUSTORY_SITE_URL}/{vizzustory_version}/", 109 | f"{VIZZUSTORY_SITE_URL}/latest/", 110 | ) 111 | content = content.replace( 112 | f"{VIZZU_SITE_URL}/{vizzu_version}/", 113 | f"{VIZZU_SITE_URL}/latest/", 114 | ) 115 | content = content.replace( 116 | f"{VIZZUSTORY_CDN_URL}@{vizzustory_version}/dist/vizzu-story.min.js", 117 | f"{VIZZUSTORY_CDN_URL}@latest/dist/vizzu-story.min.js", 118 | ) 119 | content = content.replace( 120 | f"{VIZZU_CDN_URL}@{vizzu_version}/dist/vizzu.min.js", 121 | f"{VIZZU_CDN_URL}@latest/dist/vizzu.min.js", 122 | ) 123 | 124 | return content 125 | --------------------------------------------------------------------------------