├── .coveragerc ├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── .pylintrc ├── .release-it.json ├── LICENSE.txt ├── MANIFEST.in ├── Pipfile ├── README.md ├── binder ├── environment.yml └── postBuild ├── codecov.yml ├── docs └── source │ ├── _static │ └── helper.js │ ├── api.rst │ ├── conf.py │ ├── develop-install.rst │ ├── index.rst │ ├── installing.rst │ └── introduction.rst ├── environment.yml ├── examples ├── colors.ipynb ├── introduction.ipynb ├── kmap.ipynb └── venn.ipynb ├── jest.config.js ├── package.json ├── pytest.ini ├── scripts └── bump.js ├── setup.cfg ├── setup.py ├── setupbase.py ├── src ├── __tests__ │ └── utils.ts ├── extension.ts ├── index.spec.ts ├── index.ts ├── plugin.ts ├── utils.ts ├── version.ts └── widget.ts ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.lint.json ├── tsconfig.test.json ├── upsetjs_jupyter_widget.json ├── upsetjs_jupyter_widget ├── __init__.py ├── _array.py ├── _frontend.py ├── _generate.py ├── _model.py ├── _version.py ├── nbextension │ ├── __init__.py │ └── static │ │ └── extension.js ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_nbextension_path.py │ └── test_widget.py └── widget.py ├── webpack.config.js └── yarn.lock /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = upsetjs_jupyter_widget/tests/* 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['react-app', 'prettier/@typescript-eslint', 'plugin:prettier/recommended'], 3 | parserOptions: { 4 | project: './tsconfig.lint.json', 5 | }, 6 | settings: { 7 | react: { 8 | version: '999.999.999', 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # These settings are for any web project 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto eol=lf 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text 23 | *.ts text 24 | *.coffee text 25 | *.json text 26 | *.htm text 27 | *.html text 28 | *.xml text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text eof=LF 38 | *.bat text 39 | 40 | # templates 41 | *.hbt text 42 | *.jade text 43 | *.haml text 44 | *.hbs text 45 | *.dot text 46 | *.tmpl text 47 | *.phtml text 48 | 49 | # server config 50 | .htaccess text 51 | 52 | # git config 53 | .gitattributes text 54 | .gitignore text 55 | 56 | # code analysis config 57 | .jshintrc text 58 | .jscsrc text 59 | .jshintignore text 60 | .csslintrc text 61 | 62 | # misc config 63 | *.yaml text 64 | *.yml text 65 | .editorconfig text 66 | 67 | # build config 68 | *.npmignore text 69 | *.bowerrc text 70 | Dockerfile text eof=LF 71 | 72 | # Heroku 73 | Procfile text 74 | .slugignore text 75 | 76 | # Documentation 77 | *.md text 78 | LICENSE text 79 | AUTHORS text 80 | 81 | 82 | # 83 | ## These files are binary and should be left untouched 84 | # 85 | 86 | # (binary is a macro for -text -diff) 87 | *.png binary 88 | *.jpg binary 89 | *.jpeg binary 90 | *.gif binary 91 | *.ico binary 92 | *.mov binary 93 | *.mp4 binary 94 | *.mp3 binary 95 | *.flv binary 96 | *.fla binary 97 | *.swf binary 98 | *.gz binary 99 | *.zip binary 100 | *.7z binary 101 | *.ttf binary 102 | *.pyc binary 103 | *.pdf binary 104 | 105 | # Source files 106 | # ============ 107 | *.pxd text 108 | *.py text 109 | *.py3 text 110 | *.pyw text 111 | *.pyx text 112 | *.sh text eol=lf 113 | *.json text 114 | 115 | # Binary files 116 | # ============ 117 | *.db binary 118 | *.p binary 119 | *.pkl binary 120 | *.pyc binary 121 | *.pyd binary 122 | *.pyo binary 123 | 124 | # Note: .db, .p, and .pkl files are associated 125 | # with the python modules ``pickle``, ``dbm.*``, 126 | # ``shelve``, ``marshal``, ``anydbm``, & ``bsddb`` 127 | # (among others). 128 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build_node: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: '12.x' 13 | - run: npm i -g yarn 14 | - run: yarn set version berry 15 | - run: yarn config set checksumBehavior ignore 16 | - name: Cache Node.js modules 17 | uses: actions/cache@v1 18 | with: 19 | path: ./.yarn 20 | key: ${{ runner.os }}-yarn3-${{ hashFiles('**/yarn.lock') }} 21 | restore-keys: | 22 | ${{ runner.os }}-yarn3- 23 | - run: yarn install 24 | env: 25 | CI: true 26 | - run: yarn build 27 | env: 28 | CI: true 29 | - run: yarn lint 30 | env: 31 | CI: true 32 | - run: yarn test --ci --coverage --maxWorkers=2 33 | env: 34 | CI: true 35 | build_python: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions/setup-node@v1 40 | with: 41 | node-version: '12.x' 42 | - run: npm i -g yarn 43 | - name: Cache Conda 44 | uses: actions/cache@v1 45 | with: 46 | path: ~/conda_pkgs_dir 47 | key: ${{ runner.os }}-conda6-${{ hashFiles('environment.yml') }} 48 | restore-keys: | 49 | ${{ runner.os }}-conda6- 50 | - uses: conda-incubator/setup-miniconda@v2 51 | with: 52 | python-version: 3.9 53 | mamba-version: '*' 54 | channels: conda-forge,defaults 55 | channel-priority: true 56 | activate-environment: upsetjs_jupyter_widget 57 | environment-file: environment.yml 58 | auto-activate-base: false 59 | use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly! 60 | - name: pytest 61 | shell: bash -l {0} 62 | run: yarn test:p:ci 63 | - name: Lint 64 | shell: bash -l {0} 65 | run: yarn lint:p 66 | 67 | - name: Build Notebooks 68 | shell: bash -l {0} 69 | run: yarn nbconvert 70 | 71 | - name: Create Docu 72 | shell: bash -l {0} 73 | run: yarn docs:p 74 | 75 | - run: | 76 | mkdir -p public/integrations/jupyter 77 | mv examples/*.html public/integrations/jupyter/ 78 | mv public/integrations/jupyter/introduction.html public/integrations/jupyter/index.html 79 | 80 | mkdir -p public/api/jupyter 81 | mv docs/build/_static public/api/jupyter/static 82 | mv docs/build/*.js public/api/jupyter/ 83 | mv docs/build/*.html public/api/jupyter/ 84 | find . -type f -name "*.html" -print0 | xargs -0 sed -i 's/"_/"/g' 85 | 86 | - name: Deploy Master 87 | if: github.ref == 'refs/heads/master' && github.event_name == 'push' 88 | uses: upsetjs/actions-gh-pages@sgratzl 89 | env: 90 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 91 | with: 92 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 93 | external_repository: upsetjs/upsetjs.github.io 94 | publish_branch: master 95 | publish_dir: ./public 96 | enable_jekyll: true 97 | remove_path_spec: 'integrations/jupyter/,api/jupyter/' 98 | 99 | - if: github.ref == 'refs/heads/develop' 100 | # move to next directory 101 | run: | 102 | mv public public2 103 | mkdir -p public 104 | mv public2 public/next 105 | 106 | - name: Deploy Develop 107 | if: github.ref == 'refs/heads/develop' && github.event_name == 'push' 108 | uses: upsetjs/actions-gh-pages@sgratzl 109 | env: 110 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 111 | with: 112 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 113 | external_repository: upsetjs/upsetjs.github.io 114 | publish_branch: master 115 | publish_dir: ./public 116 | enable_jekyll: true 117 | remove_path_spec: 'next/integrations/jupyter/,next/api/jupyter/' 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | docs/source/_static/embed-bundle.js 65 | docs/source/_static/embed-bundle.js.map 66 | docs/source/_static/embed-bundle.js.LICENSE.txt 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # ========================= 94 | # Operating System Files 95 | # ========================= 96 | 97 | # OSX 98 | # ========================= 99 | 100 | .DS_Store 101 | .AppleDouble 102 | .LSOverride 103 | 104 | # Thumbnails 105 | ._* 106 | 107 | # Files that might appear in the root of a volume 108 | .DocumentRevisions-V100 109 | .fseventsd 110 | .Spotlight-V100 111 | .TemporaryItems 112 | .Trashes 113 | .VolumeIcon.icns 114 | 115 | # Directories potentially created on remote AFP share 116 | .AppleDB 117 | .AppleDesktop 118 | Network Trash Folder 119 | Temporary Items 120 | .apdisk 121 | 122 | # Windows 123 | # ========================= 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | ehthumbs.db 128 | 129 | # Folder config file 130 | Desktop.ini 131 | 132 | # Recycle Bin used on file shares 133 | $RECYCLE.BIN/ 134 | 135 | # Windows Installer files 136 | *.cab 137 | *.msi 138 | *.msm 139 | *.msp 140 | 141 | # Windows shortcuts 142 | *.lnk 143 | 144 | 145 | # NPM 146 | # ---- 147 | 148 | **/node_modules/ 149 | upsetjs_jupyter_widget/nbextension/static/index.* 150 | upsetjs_jupyter_widget/labextension/*.tgz 151 | 152 | # Coverage data 153 | # ------------- 154 | **/coverage/ 155 | 156 | # Packed lab extensions 157 | upsetjs_jupyter_widget/labextension 158 | 159 | .yarnrc.yml 160 | .yarn 161 | *.tsbuildinfo 162 | .vscode 163 | src/**/*.js 164 | src/**/*.d.ts 165 | *.map 166 | /.mypy_cache 167 | /junit 168 | /MANIFEST 169 | /examples/*.html 170 | /.pypirc 171 | .pnp.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | tests/ 4 | .jshintrc 5 | # Ignore any build output from python: 6 | dist/*.tar.gz 7 | dist/*.wheel 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | semi: true, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | disable=bad-continuation, 3 | too-many-return-statements, 4 | too-many-instance-attributes, 5 | too-few-public-methods, 6 | line-too-long, 7 | duplicate-code, 8 | too-many-arguments, 9 | too-many-ancestors, 10 | unsubscriptable-object 11 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "before:init": "yarn lint && yarn lint:p", 4 | "before:release": "yarn build:p", 5 | "after:npm:release": "twine upload dist/upsetjs_*", 6 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." 7 | }, 8 | "git": { 9 | "tagName": "v${version}" 10 | }, 11 | "npm": { 12 | "publish": true 13 | }, 14 | "github": { 15 | "release": true, 16 | "assets": ["dist/*.tar.gz", "dist/*.whl", "upsetjs_jupyter_widget/labextension/*.tgz"] 17 | }, 18 | "plugins": { 19 | "./scripts/bump.js": {} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.md 3 | 4 | include setupbase.py 5 | include pytest.ini 6 | include .coverage.rc 7 | 8 | include tsconfig.json 9 | include package.json 10 | include webpack.config.js 11 | include upsetjs_jupyter_widget/labextension/*.tgz 12 | 13 | # Documentation 14 | graft docs 15 | exclude docs/\#* 16 | prune docs/build 17 | prune docs/gh-pages 18 | prune docs/dist 19 | 20 | # Examples 21 | graft examples 22 | 23 | # Tests 24 | graft tests 25 | prune tests/build 26 | 27 | # Javascript files 28 | graft upsetjs_jupyter_widget/nbextension 29 | graft src 30 | prune **/node_modules 31 | prune coverage 32 | prune lib 33 | 34 | # Patterns to exclude from any directory 35 | global-exclude *~ 36 | global-exclude *.pyc 37 | global-exclude *.pyo 38 | global-exclude .git 39 | global-exclude .ipynb_checkpoints 40 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | invoke = "*" 8 | mypy = ">=0.740" 9 | bumpversion = ">=0.5.3" 10 | watchdog = ">=0.9.0" 11 | pylint = ">=2.4.3" 12 | coverage = ">=4.5.4" 13 | Sphinx = ">=2.2.0" 14 | twine = ">=2.0.0" 15 | pytest = ">=5.2.2" 16 | pytest-runner = ">=5.1" 17 | black = "*" 18 | autopep8 = "*" 19 | pytest-cov = "*" 20 | # nbval = "*" 21 | jupyterlab = "*" 22 | pexpect = "*" 23 | recommonmark = "*" 24 | sphinx_rtd_theme = "*" 25 | nbsphinx = ">=0.2.13,<0.4.0" 26 | jupyter_sphinx = "*" 27 | nbsphinx-link = "*" 28 | pytest_check_links = "*" 29 | pypandoc = "*" 30 | sphinx-autodoc-typehints = "*" 31 | 32 | [packages] 33 | upsetjs_jupyter_widget = {editable = true,path = "."} 34 | 35 | [requires] 36 | python_version = "3.7" 37 | 38 | [pipenv] 39 | allow_prereleases = true 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UpSet.js Jupyter Widget 2 | 3 | [![NPM Package][npm-image]][npm-url] [![Github Actions][github-actions-image]][github-actions-url] [![Open in NBViewer][nbviewer]][nbviewer-url] [![Open in Binder][binder]][binder-j-url] [![Open API Docs][docs]][docs-j-url] [![Open Example][example]][example-j-url] 4 | 5 | A Jupyter Widget Library around [UpSet.js](https://github.com/upsetjs/upsetjs). 6 | 7 | This package is part of the UpSet.js ecosystem located at the main [Github Monorepo](https://github.com/upsetjs/upsetjs). 8 | 9 | ## Installation 10 | 11 | You can install using `pip`: 12 | 13 | ```bash 14 | # some ipywidget 8.x.x alpha vesion 15 | pip install ipywidgets upsetjs_jupyter_widget 16 | # for notebooks 17 | jupyter nbextension enable --sys-prefix --py upsetjs_jupyter_widget 18 | # for lab 19 | jupyter labextension install @jupyter-widgets/jupyterlab-manager upsetjs_jupyter_widget 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```python 25 | from ipywidgets import interact 26 | from upsetjs_jupyter_widget import UpSetJSWidget 27 | import pandas as pd 28 | ``` 29 | 30 | ```python 31 | w = UpSetJSWidget[str]() 32 | ``` 33 | 34 | ```python 35 | w.from_dict(dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])) 36 | w 37 | ``` 38 | 39 | ![upset_from_dict](https://user-images.githubusercontent.com/4129778/79368564-e4715d00-7f4f-11ea-92f5-23ee89b5332f.png) 40 | 41 | ```python 42 | df = pd.DataFrame(dict( 43 | one=[1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1], 44 | two=[1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0], 45 | three=[1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1] 46 | ), index=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm']) 47 | w.from_dataframe(df) 48 | w 49 | ``` 50 | 51 | ![upset_from_dataframe](https://user-images.githubusercontent.com/4129778/79368563-e3d8c680-7f4f-11ea-92d2-db0c7af2882e.png) 52 | 53 | it support the `ipywidget` interact method to get notified about the user input 54 | 55 | ```python 56 | def selection_changed(s): 57 | return s.name if s else None 58 | interact(selection_changed, s=w) 59 | ``` 60 | 61 | see also [introduction.ipynb](./master/examples/introduction.ipynb) 62 | 63 | see also [![Open in NBViewer][nbviewer]][nbviewer-url] [![Open in Binder][binder]][binder-j-url] 64 | 65 | ## Documentation 66 | 67 | the package documentation is located at [![Open API Docs][docs]][docs-j-url]. An introduction Jupyter Notebooks is at [![Open Example][example]][example-j-url]. 68 | 69 | ## Venn Diagram 70 | 71 | Besides the main UpSet.js plot also Venn Diagrams for up to five sets are supported. It uses the same input formats and has similar functionality in terms of interaction. 72 | 73 | ```python 74 | from upsetjs_jupyter_widget import UpSetJSVennDiagramWidget 75 | v = UpSetJSVennDiagramWidget[str]() 76 | v.from_dict(dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])) 77 | v 78 | ``` 79 | 80 | ![image](https://user-images.githubusercontent.com/4129778/84817608-8a574b80-b015-11ea-91b8-2ff17bb533e4.png) 81 | 82 | see also [venn.ipynb](https://upset.js.org/integrations/jupyter/venn.html) 83 | 84 | ## Karnaugh Map 85 | 86 | Besides the main UpSet.js plot also a variant of a Karnaugh Map. It uses the same input formats and has similar functionality in terms of interaction. 87 | 88 | ```python 89 | from upsetjs_jupyter_widget import UpSetJSKarnaughMapWidget 90 | v = UpSetJSKarnaughMapWidget[str]() 91 | v.from_dict(dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])) 92 | v 93 | ``` 94 | 95 | ![image](https://user-images.githubusercontent.com/4129778/86368718-c9c0a180-bc7d-11ea-99c3-2086e6ec1422.png) 96 | 97 | see also [kmap.ipynb](https://upset.js.org/integrations/jupyter/kmap.html) 98 | 99 | ## Dev Environment 100 | 101 | ```sh 102 | npm i -g yarn 103 | yarn set version berry 104 | yarn 105 | yarn pnpify --sdk vscode 106 | conda create -f environment.yml 107 | ``` 108 | 109 | ```sh 110 | conda activate upsetjs_jupyter_widget 111 | pip install -e . 112 | jupyter nbextension install --sys-prefix --overwrite --py upsetjs_jupyter_widget 113 | jupyter nbextension enable --sys-prefix --py upsetjs_jupyter_widget 114 | jupyter labextension install @jupyter-widgets/jupyterlab-manager . 115 | ``` 116 | 117 | ### Commands 118 | 119 | ```sh 120 | yarn test 121 | yarn lint 122 | yarn build !! within the pipenv 123 | ``` 124 | 125 | ## Privacy Policy 126 | 127 | UpSet.js is a client only library. The library or any of its integrations doesn't track you or transfers your data to any server. The uploaded data in the app are stored in your browser only using IndexedDB. The Tableau extension can run in a sandbox environment prohibiting any server requests. However, as soon as you export your session within the app to an external service (e.g., Codepen.io) your data will be transferred. 128 | 129 | ## License / Terms of Service 130 | 131 | ### Commercial license 132 | 133 | If you want to use Upset.js for a commercial application the commercial license is the appropriate license. Contact [@sgratzl](mailto:sam@sgratzl.com) for details. 134 | 135 | ### Open-source license 136 | 137 | This library is released under the `GNU AGPLv3` version to be used for private and academic purposes. In case of a commercial use, please get in touch regarding a commercial license. 138 | 139 | [npm-image]: https://badge.fury.io/js/%40upsetjs%2Fjupyter_widget.svg 140 | [npm-url]: https://npmjs.org/package/@upsetjs/jupyter_widget 141 | [github-actions-image]: https://github.com/upsetjs/upsetjs_jupyter_widget/workflows/ci/badge.svg 142 | [github-actions-url]: https://github.com/upsetjs/upsetjs_jupyter_widget/actions 143 | [codepen]: https://img.shields.io/badge/CodePen-open-blue?logo=codepen 144 | [nbviewer]: https://img.shields.io/badge/NBViewer-open-blue?logo=jupyter 145 | [nbviewer-url]: https://nbviewer.jupyter.org/github/upsetjs/upsetjs_jupyter_widget/blob/master/examples/introduction.ipynb 146 | [binder]: https://mybinder.org/badge_logo.svg 147 | [binder-j-url]: https://mybinder.org/v2/gh/upsetjs/upsetjs_jupyter_widget/master?urlpath=lab/tree/examples/introduction.ipynb 148 | [docs]: https://img.shields.io/badge/API-open-blue 149 | [docs-j-url]: https://upset.js.org/api/jupyter 150 | [example]: https://img.shields.io/badge/Example-open-red 151 | [example-j-url]: https://upset.js.org/integrations/jupyter 152 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: upsetjs-jupyter-widget 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python 6 | - jupyterlab==2.1.0 7 | - numpy 8 | - pandas 9 | - pip 10 | - pip: 11 | - ipywidgets==8.0.0a0 12 | - upsetjs_jupyter_widget 13 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Install JupyterLab extension 5 | jupyter nbextension enable --py widgetsnbextension 6 | jupyter nbextension enable --sys-prefix --py upsetjs_jupyter_widget 7 | jupyter labextension install @jupyter-widgets/jupyterlab-manager@3.0.0-alpha.0 @upsetjs/jupyter_widget 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | # show coverage in CI status, but never consider it a failure 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: 0% 8 | patch: 9 | default: 10 | target: 0% 11 | ignore: 12 | - 'upsetjs_jupyter_widget/tests' 13 | -------------------------------------------------------------------------------- /docs/source/_static/helper.js: -------------------------------------------------------------------------------- 1 | var cache_require = window.require; 2 | 3 | window.addEventListener('load', function() { 4 | window.require = cache_require; 5 | }); 6 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ================= 3 | 4 | .. autoclass:: upsetjs_jupyter_widget.UpSetJSWidget 5 | :members: 6 | :undoc-members: 7 | 8 | .. automodule:: upsetjs_jupyter_widget._model 9 | :members: 10 | :undoc-members: 11 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # upsetjs_jupyter_widget documentation build configuration file 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | 16 | # -- General configuration ------------------------------------------------ 17 | 18 | # If your documentation needs a minimal Sphinx version, state it here. 19 | # 20 | # needs_sphinx = '1.0' 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be 23 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 24 | # ones. 25 | extensions = [ 26 | "sphinx.ext.autodoc", 27 | "sphinx.ext.viewcode", 28 | "sphinx.ext.intersphinx", 29 | "sphinx.ext.napoleon", 30 | "sphinx_autodoc_typehints", 31 | "sphinx.ext.todo", 32 | # "nbsphinx", 33 | # "nbsphinx_link", 34 | ] 35 | 36 | # Ensure our extension is available: 37 | import sys 38 | from os.path import dirname, join as pjoin 39 | 40 | source = dirname(__file__) 41 | docs = dirname(source) 42 | root = dirname(docs) 43 | sys.path.insert(0, root) 44 | sys.path.insert(0, pjoin(docs, "sphinxext")) 45 | sys.path.insert(0, pjoin(docs, "upsetjs_jupyter_widget")) 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ["_templates"] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | source_suffix = [".rst", ".md"] 54 | # source_suffix = ".rst" 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # General information about the project. 60 | project = "upsetjs_jupyter_widget" 61 | copyright = "2021, Samuel Gratzl" 62 | author = "Samuel Gratzl" 63 | 64 | # The version info for the project you're documenting, acts as replacement for 65 | # |version| and |release|, also used in various other places throughout the 66 | # built documents. 67 | # 68 | # The short X.Y version. 69 | 70 | 71 | # get version from python package: 72 | import os 73 | 74 | _version_py = os.path.join(root, "upsetjs_jupyter_widget", "_version.py") 75 | version_ns = {} 76 | with open(_version_py) as f: 77 | exec(f.read(), version_ns) 78 | 79 | # The short X.Y version. 80 | version = "%i.%i" % version_ns["version_info"][:2] 81 | # The full version, including alpha/beta/rc tags. 82 | release = version_ns["__version__"] 83 | 84 | # The language for content autogenerated by Sphinx. Refer to documentation 85 | # for a list of supported languages. 86 | # 87 | # This is also used if you do content translation via gettext catalogs. 88 | # Usually you set "language" from the command line for these cases. 89 | language = "en" 90 | 91 | # List of patterns, relative to source directory, that match files and 92 | # directories to ignore when looking for source files. 93 | # This patterns also effect to html_static_path and html_extra_path 94 | exclude_patterns = ["**.ipynb_checkpoints"] 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = "sphinx" 98 | 99 | # If true, `todo` and `todoList` produce output, else they produce nothing. 100 | todo_include_todos = False 101 | 102 | 103 | # -- Options for HTML output ---------------------------------------------- 104 | 105 | 106 | # Theme options are theme-specific and customize the look and feel of a theme 107 | # further. For a list of options available for each theme, see the 108 | # documentation. 109 | # 110 | # html_theme_options = {} 111 | 112 | # Add any paths that contain custom static files (such as style sheets) here, 113 | # relative to this directory. They are copied after the builtin static files, 114 | # so a file named "default.css" will overwrite the builtin "default.css". 115 | html_static_path = ["_static"] 116 | html_copy_source = False 117 | html_show_sourcelink = False 118 | 119 | 120 | # -- Options for HTMLHelp output ------------------------------------------ 121 | 122 | # Output file base name for HTML help builder. 123 | htmlhelp_basename = "upsetjs_jupyter_widgetdoc" 124 | 125 | 126 | # -- Options for LaTeX output --------------------------------------------- 127 | 128 | # latex_elements = { 129 | # The paper size ('letterpaper' or 'a4paper'). 130 | # 131 | # 'papersize': 'letterpaper', 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 134 | # 'pointsize': '10pt', 135 | # Additional stuff for the LaTeX preamble. 136 | # 137 | # 'preamble': '', 138 | # Latex figure (float) alignment 139 | # 140 | # 'figure_align': 'htbp', 141 | # } 142 | 143 | # Grouping the document tree into LaTeX files. List of tuples 144 | # (source start file, target name, title, 145 | # author, documentclass [howto, manual, or own class]). 146 | latex_documents = [ 147 | ( 148 | master_doc, 149 | "upsetjs_jupyter_widget.tex", 150 | "upsetjs_jupyter_widget Documentation", 151 | "Samuel Gratzl", 152 | "manual", 153 | ), 154 | ] 155 | 156 | 157 | # -- Options for manual page output --------------------------------------- 158 | 159 | # One entry per manual page. List of tuples 160 | # (source start file, name, description, authors, manual section). 161 | man_pages = [ 162 | ( 163 | master_doc, 164 | "upsetjs_jupyter_widget", 165 | "upsetjs_jupyter_widget Documentation", 166 | [author], 167 | 1, 168 | ) 169 | ] 170 | 171 | 172 | # -- Options for Texinfo output ------------------------------------------- 173 | 174 | # Grouping the document tree into Texinfo files. List of tuples 175 | # (source start file, target name, title, author, 176 | # dir menu entry, description, category) 177 | texinfo_documents = [ 178 | ( 179 | master_doc, 180 | "upsetjs_jupyter_widget", 181 | "upsetjs_jupyter_widget Documentation", 182 | author, 183 | "upsetjs_jupyter_widget", 184 | "A Jupyter Widget Library around UpSet.js", 185 | "Miscellaneous", 186 | ), 187 | ] 188 | 189 | 190 | # Example configuration for intersphinx: refer to the Python standard library. 191 | intersphinx_mapping = {"https://docs.python.org/": None} 192 | 193 | # Read The Docs 194 | # on_rtd is whether we are on readthedocs.org, this line of code grabbed from 195 | # docs.readthedocs.org 196 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 197 | 198 | if not on_rtd: # only import and set the theme if we're building docs locally 199 | import sphinx_rtd_theme 200 | 201 | html_theme = "sphinx_rtd_theme" 202 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 203 | 204 | # otherwise, readthedocs.org uses their theme by default, so no need to specify it 205 | 206 | 207 | # Uncomment this line if you have know exceptions in your included notebooks 208 | # that nbsphinx complains about: 209 | # 210 | nbsphinx_allow_errors = True # exception ipstruct.py ipython_genutils 211 | 212 | 213 | # https://pypi.org/project/sphinx-autodoc-typehints/ 214 | always_document_param_types = True 215 | 216 | 217 | def setup(app): 218 | def add_scripts(app): 219 | for fname in ["helper.js", "embed-bundle.js"]: 220 | if not os.path.exists(os.path.join(source, "_static", fname)): 221 | print("missing javascript file: %s" % fname) 222 | app.add_js_file(fname) 223 | 224 | app.connect("builder-inited", add_scripts) 225 | -------------------------------------------------------------------------------- /docs/source/develop-install.rst: -------------------------------------------------------------------------------- 1 | 2 | Developer install 3 | ================= 4 | 5 | 6 | To install a developer version of upsetjs_jupyter_widget, you will first need to clone 7 | the repository:: 8 | 9 | git clone https://github.com/upsetjs/upsetjs_jupyter_widget 10 | cd upsetjs_jupyter_widget 11 | 12 | Next, install it with a develop install using pip:: 13 | 14 | pip install -e . 15 | 16 | 17 | If you are planning on working on the JS/frontend code, you should also do 18 | a link installation of the extension:: 19 | 20 | jupyter nbextension install [--sys-prefix / --user / --system] --symlink --py upsetjs_jupyter_widget 21 | 22 | jupyter nbextension enable [--sys-prefix / --user / --system] --py upsetjs_jupyter_widget 23 | 24 | with the `appropriate flag`_. Or, if you are using Jupyterlab:: 25 | 26 | jupyter labextension install . 27 | 28 | 29 | .. links 30 | 31 | .. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions 32 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | upsetjs_jupyter_widget 3 | ===================================== 4 | 5 | Version: |release| 6 | 7 | A Jupyter Widget Library around UpSet.js 8 | 9 | 10 | Quickstart 11 | ---------- 12 | 13 | To get started with upsetjs_jupyter_widget, install with pip:: 14 | 15 | pip install upsetjs_jupyter_widget 16 | 17 | or with conda:: 18 | 19 | conda install upsetjs_jupyter_widget 20 | 21 | 22 | Contents 23 | -------- 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | :caption: Installation and usage 28 | 29 | installing 30 | introduction 31 | 32 | .. toctree:: 33 | :maxdepth: 1 34 | 35 | api 36 | 37 | .. toctree:: 38 | :maxdepth: 2 39 | :caption: Development 40 | 41 | develop-install 42 | 43 | 44 | .. links 45 | 46 | .. _`Jupyter widgets`: https://jupyter.org/widgets.html 47 | 48 | .. _`notebook`: https://jupyter-notebook.readthedocs.io/en/latest/ 49 | -------------------------------------------------------------------------------- /docs/source/installing.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _installation: 3 | 4 | Installation 5 | ============ 6 | 7 | 8 | The simplest way to install upsetjs_jupyter_widget is via pip:: 9 | 10 | pip install upsetjs_jupyter_widget 11 | 12 | or via conda:: 13 | 14 | conda install upsetjs_jupyter_widget 15 | 16 | 17 | If you installed via pip, and notebook version < 5.3, you will also have to 18 | install / configure the front-end extension as well. If you are using classic 19 | notebook (as opposed to Jupyterlab), run:: 20 | 21 | jupyter nbextension install [--sys-prefix / --user / --system] --py upsetjs_jupyter_widget 22 | 23 | jupyter nbextension enable [--sys-prefix / --user / --system] --py upsetjs_jupyter_widget 24 | 25 | with the `appropriate flag`_. If you are using Jupyterlab, install the extension 26 | with:: 27 | 28 | jupyter labextension install @upsetjs/jupyter_widget 29 | 30 | If you are installing using conda, these commands should be unnecessary, but If 31 | you need to run them the commands should be the same (just make sure you choose the 32 | `--sys-prefix` flag). 33 | 34 | 35 | .. links 36 | 37 | .. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions 38 | -------------------------------------------------------------------------------- /docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Introduction 3 | ============= 4 | 5 | .. todo:: 6 | 7 | add prose explaining project purpose and usage here 8 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: upsetjs_jupyter_widget 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - python==3.9 7 | - mypy 8 | - pylint 9 | - black 10 | - bumpversion 11 | - ipywidgets 12 | - jupyterlab 13 | - pandas 14 | - pytest 15 | - jupyter 16 | - pytest-cov 17 | - twine 18 | - sphinx 19 | - recommonmark 20 | - sphinx_rtd_theme 21 | - nbsphinx 22 | - jupyter_sphinx 23 | - nbsphinx-link 24 | - pypandoc 25 | - sphinx-autodoc-typehints 26 | -------------------------------------------------------------------------------- /examples/colors.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UpSet.js Jupyter Widget - Coloring Diagrams\n", 8 | " \n", 9 | "UpSet.js allow you to color sets in UpSet plots, Venn diagrams, and Karnaugh maps." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 3, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from ipywidgets import interact\n", 19 | "from upsetjs_jupyter_widget import UpSetJSWidget, UpSetJSVennDiagramWidget, UpSetJSEulerDiagramWidget, UpSetJSKarnaughMapWidget\n", 20 | "import pandas as pd" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 4, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "dict_input = dict(s1 = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], s2 = ['a', 'b', 'd', 'e', 'j'], s3 = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])\n", 30 | "colors = {'s1': '#1f77b4', 's2': '#2ca02c', 's3': '#d62728', 's1&s2': '#9467bd', 's1&s3': '#8c564b', 's2&s3': '#e377c2', 's1&s2&s3': '#bcbd22'}" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 5, 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "data": { 40 | "application/vnd.jupyter.widget-view+json": { 41 | "model_id": "0a5da1a3393844a184e491f67da011ea", 42 | "version_major": 2, 43 | "version_minor": 0 44 | }, 45 | "text/plain": [ 46 | "UpSetJSWidget(value=None, combinations=[UpSetSetIntersection(name=s1, sets={'s1'}, cardinality=9, elems={'b', …" 47 | ] 48 | }, 49 | "execution_count": 5, 50 | "metadata": {}, 51 | "output_type": "execute_result" 52 | } 53 | ], 54 | "source": [ 55 | "w = UpSetJSWidget[str]()\n", 56 | "w.selection_color = ''\n", 57 | "w.has_selection_opacity = 0.3\n", 58 | "w.from_dict(dict_input, colors=colors)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 6, 64 | "metadata": {}, 65 | "outputs": [ 66 | { 67 | "data": { 68 | "application/vnd.jupyter.widget-view+json": { 69 | "model_id": "f7061c4ff76b44cd9971d4d5cb79d2ea", 70 | "version_major": 2, 71 | "version_minor": 0 72 | }, 73 | "text/plain": [ 74 | "UpSetJSVennDiagramWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=s1, sets={'s1'}, cardinal…" 75 | ] 76 | }, 77 | "execution_count": 6, 78 | "metadata": {}, 79 | "output_type": "execute_result" 80 | } 81 | ], 82 | "source": [ 83 | "v = UpSetJSVennDiagramWidget[str]()\n", 84 | "v.selection_color = ''\n", 85 | "v.has_selection_opacity = 0.3\n", 86 | "v.from_dict(dict_input, colors=colors)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 7, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "application/vnd.jupyter.widget-view+json": { 97 | "model_id": "415652b7f8e740b3925f8d4ac7b706cf", 98 | "version_major": 2, 99 | "version_minor": 0 100 | }, 101 | "text/plain": [ 102 | "UpSetJSEulerDiagramWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=s1, sets={'s1'}, cardina…" 103 | ] 104 | }, 105 | "execution_count": 7, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "e = UpSetJSEulerDiagramWidget[str]()\n", 112 | "e.selection_color = ''\n", 113 | "e.has_selection_opacity = 0.3\n", 114 | "e.from_dict(dict_input, colors=colors)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 8, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "data": { 124 | "application/vnd.jupyter.widget-view+json": { 125 | "model_id": "af5914b90b704240ba1195962fe5c790", 126 | "version_major": 2, 127 | "version_minor": 0 128 | }, 129 | "text/plain": [ 130 | "UpSetJSKarnaughMapWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=s1, sets={'s1'}, cardinal…" 131 | ] 132 | }, 133 | "execution_count": 8, 134 | "metadata": {}, 135 | "output_type": "execute_result" 136 | } 137 | ], 138 | "source": [ 139 | "k = UpSetJSKarnaughMapWidget[str]()\n", 140 | "k.selection_color = ''\n", 141 | "k.has_selection_opacity = 0.3\n", 142 | "k.from_dict(dict_input, colors=colors)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [] 151 | } 152 | ], 153 | "metadata": { 154 | "kernelspec": { 155 | "display_name": "Python 3.7.1 64-bit ('upsetjs_jupyter_widget': pipenv)", 156 | "language": "python", 157 | "name": "python37164bitupsetjsjupyterwidgetpipenvfee1645c874046dc9f0c363432ab7182" 158 | }, 159 | "language_info": { 160 | "codemirror_mode": { 161 | "name": "ipython", 162 | "version": 3 163 | }, 164 | "file_extension": ".py", 165 | "mimetype": "text/x-python", 166 | "name": "python", 167 | "nbconvert_exporter": "python", 168 | "pygments_lexer": "ipython3", 169 | "version": "3.7.1" 170 | }, 171 | "widgets": { 172 | "application/vnd.jupyter.widget-state+json": { 173 | "state": { 174 | "0a5da1a3393844a184e491f67da011ea": { 175 | "model_module": "@upsetjs/jupyter_widget", 176 | "model_module_version": "^1.3.0", 177 | "model_name": "UpSetModel", 178 | "state": { 179 | "_expression_data": false, 180 | "_model_module_version": "^1.3.0", 181 | "_render_mode": "upset", 182 | "_view_module_version": "^1.3.0", 183 | "alternating_background_color": null, 184 | "attrs": [], 185 | "band_scale": "band", 186 | "bar_label_offset": null, 187 | "bar_padding": null, 188 | "color": null, 189 | "combination_name": null, 190 | "combination_name_axis_offset": null, 191 | "combinations": [ 192 | { 193 | "cardinality": 9, 194 | "color": "#1f77b4", 195 | "degree": 1, 196 | "elems": [ 197 | 1, 198 | 6, 199 | 0, 200 | 4, 201 | 7, 202 | 10, 203 | 2, 204 | 11, 205 | 12 206 | ], 207 | "name": "s1", 208 | "set_names": [ 209 | "s1" 210 | ], 211 | "type": "intersection" 212 | }, 213 | { 214 | "cardinality": 9, 215 | "color": "#d62728", 216 | "degree": 1, 217 | "elems": [ 218 | 8, 219 | 6, 220 | 7, 221 | 0, 222 | 4, 223 | 9, 224 | 11, 225 | 5, 226 | 12 227 | ], 228 | "name": "s3", 229 | "set_names": [ 230 | "s3" 231 | ], 232 | "type": "intersection" 233 | }, 234 | { 235 | "cardinality": 6, 236 | "color": "#8c564b", 237 | "degree": 2, 238 | "elems": [ 239 | 6, 240 | 0, 241 | 7, 242 | 4, 243 | 11, 244 | 12 245 | ], 246 | "name": "(s1 ∩ s3)", 247 | "set_names": [ 248 | "s3", 249 | "s1" 250 | ], 251 | "type": "intersection" 252 | }, 253 | { 254 | "cardinality": 5, 255 | "color": "#2ca02c", 256 | "degree": 1, 257 | "elems": [ 258 | 1, 259 | 3, 260 | 0, 261 | 4, 262 | 9 263 | ], 264 | "name": "s2", 265 | "set_names": [ 266 | "s2" 267 | ], 268 | "type": "intersection" 269 | }, 270 | { 271 | "cardinality": 3, 272 | "color": "#9467bd", 273 | "degree": 2, 274 | "elems": [ 275 | 1, 276 | 0, 277 | 4 278 | ], 279 | "name": "(s1 ∩ s2)", 280 | "set_names": [ 281 | "s2", 282 | "s1" 283 | ], 284 | "type": "intersection" 285 | }, 286 | { 287 | "cardinality": 3, 288 | "color": "#e377c2", 289 | "degree": 2, 290 | "elems": [ 291 | 9, 292 | 0, 293 | 4 294 | ], 295 | "name": "(s2 ∩ s3)", 296 | "set_names": [ 297 | "s2", 298 | "s3" 299 | ], 300 | "type": "intersection" 301 | }, 302 | { 303 | "cardinality": 2, 304 | "color": "#bcbd22", 305 | "degree": 3, 306 | "elems": [ 307 | 0, 308 | 4 309 | ], 310 | "name": "(s1 ∩ s2 ∩ s3)", 311 | "set_names": [ 312 | "s2", 313 | "s3", 314 | "s1" 315 | ], 316 | "type": "intersection" 317 | } 318 | ], 319 | "description": null, 320 | "dot_padding": null, 321 | "elems": [ 322 | "a", 323 | "b", 324 | "c", 325 | "d", 326 | "e", 327 | "f", 328 | "g", 329 | "h", 330 | "i", 331 | "j", 332 | "k", 333 | "l", 334 | "m" 335 | ], 336 | "export_buttons": null, 337 | "font_family": null, 338 | "font_sizes": {}, 339 | "has_selection_color": null, 340 | "has_selection_opacity": 0.3, 341 | "height_ratios": [ 342 | 0.6, 343 | 0.4 344 | ], 345 | "hover_hint_color": null, 346 | "layout": "IPY_MODEL_8f2bbf62f29b43499b0e372a5af0a029", 347 | "mode": "hover", 348 | "not_member_color": null, 349 | "numeric_scale": "linear", 350 | "opacity": null, 351 | "padding": null, 352 | "queries": [], 353 | "query_legend": null, 354 | "selection_color": "", 355 | "set_name": null, 356 | "set_name_axis_offset": null, 357 | "sets": [ 358 | { 359 | "cardinality": 9, 360 | "color": "#1f77b4", 361 | "elems": [ 362 | 1, 363 | 6, 364 | 0, 365 | 4, 366 | 7, 367 | 10, 368 | 2, 369 | 11, 370 | 12 371 | ], 372 | "name": "s1", 373 | "type": "set" 374 | }, 375 | { 376 | "cardinality": 9, 377 | "color": "#d62728", 378 | "elems": [ 379 | 8, 380 | 6, 381 | 7, 382 | 0, 383 | 4, 384 | 9, 385 | 11, 386 | 5, 387 | 12 388 | ], 389 | "name": "s3", 390 | "type": "set" 391 | }, 392 | { 393 | "cardinality": 5, 394 | "color": "#2ca02c", 395 | "elems": [ 396 | 1, 397 | 3, 398 | 0, 399 | 4, 400 | 9 401 | ], 402 | "name": "s2", 403 | "type": "set" 404 | } 405 | ], 406 | "text_color": null, 407 | "theme": "light", 408 | "title": null, 409 | "value": null, 410 | "width_ratios": [ 411 | 0.25, 412 | 0.1, 413 | 0.65 414 | ] 415 | } 416 | }, 417 | "415652b7f8e740b3925f8d4ac7b706cf": { 418 | "model_module": "@upsetjs/jupyter_widget", 419 | "model_module_version": "^1.3.0", 420 | "model_name": "UpSetModel", 421 | "state": { 422 | "_expression_data": false, 423 | "_model_module_version": "^1.3.0", 424 | "_render_mode": "euler", 425 | "_view_module_version": "^1.3.0", 426 | "color": null, 427 | "combinations": [ 428 | { 429 | "cardinality": 2, 430 | "color": "#1f77b4", 431 | "degree": 1, 432 | "elems": [ 433 | 10, 434 | 2 435 | ], 436 | "name": "s1", 437 | "set_names": [ 438 | "s1" 439 | ], 440 | "type": "distinctIntersection" 441 | }, 442 | { 443 | "cardinality": 2, 444 | "color": "#d62728", 445 | "degree": 1, 446 | "elems": [ 447 | 5, 448 | 8 449 | ], 450 | "name": "s3", 451 | "set_names": [ 452 | "s3" 453 | ], 454 | "type": "distinctIntersection" 455 | }, 456 | { 457 | "cardinality": 1, 458 | "color": "#2ca02c", 459 | "degree": 1, 460 | "elems": [ 461 | 3 462 | ], 463 | "name": "s2", 464 | "set_names": [ 465 | "s2" 466 | ], 467 | "type": "distinctIntersection" 468 | }, 469 | { 470 | "cardinality": 1, 471 | "color": "#9467bd", 472 | "degree": 2, 473 | "elems": [ 474 | 1 475 | ], 476 | "name": "(s1 ∩ s2)", 477 | "set_names": [ 478 | "s1", 479 | "s2" 480 | ], 481 | "type": "distinctIntersection" 482 | }, 483 | { 484 | "cardinality": 4, 485 | "color": "#8c564b", 486 | "degree": 2, 487 | "elems": [ 488 | 11, 489 | 12, 490 | 6, 491 | 7 492 | ], 493 | "name": "(s1 ∩ s3)", 494 | "set_names": [ 495 | "s1", 496 | "s3" 497 | ], 498 | "type": "distinctIntersection" 499 | }, 500 | { 501 | "cardinality": 1, 502 | "color": "#e377c2", 503 | "degree": 2, 504 | "elems": [ 505 | 9 506 | ], 507 | "name": "(s2 ∩ s3)", 508 | "set_names": [ 509 | "s3", 510 | "s2" 511 | ], 512 | "type": "distinctIntersection" 513 | }, 514 | { 515 | "cardinality": 2, 516 | "color": "#bcbd22", 517 | "degree": 3, 518 | "elems": [ 519 | 0, 520 | 4 521 | ], 522 | "name": "(s1 ∩ s2 ∩ s3)", 523 | "set_names": [ 524 | "s1", 525 | "s2", 526 | "s3" 527 | ], 528 | "type": "distinctIntersection" 529 | } 530 | ], 531 | "description": null, 532 | "elems": [ 533 | "a", 534 | "b", 535 | "c", 536 | "d", 537 | "e", 538 | "f", 539 | "g", 540 | "h", 541 | "i", 542 | "j", 543 | "k", 544 | "l", 545 | "m" 546 | ], 547 | "export_buttons": null, 548 | "font_family": null, 549 | "font_sizes": {}, 550 | "has_selection_color": null, 551 | "has_selection_opacity": 0.3, 552 | "layout": "IPY_MODEL_4ffcbfc1e25a4d088ee1ce681643b731", 553 | "mode": "hover", 554 | "opacity": null, 555 | "padding": null, 556 | "queries": [], 557 | "query_legend": null, 558 | "selection_color": "", 559 | "sets": [ 560 | { 561 | "cardinality": 9, 562 | "color": "#1f77b4", 563 | "elems": [ 564 | 1, 565 | 6, 566 | 0, 567 | 4, 568 | 7, 569 | 10, 570 | 2, 571 | 11, 572 | 12 573 | ], 574 | "name": "s1", 575 | "type": "set" 576 | }, 577 | { 578 | "cardinality": 9, 579 | "color": "#d62728", 580 | "elems": [ 581 | 8, 582 | 6, 583 | 7, 584 | 0, 585 | 4, 586 | 9, 587 | 11, 588 | 5, 589 | 12 590 | ], 591 | "name": "s3", 592 | "type": "set" 593 | }, 594 | { 595 | "cardinality": 5, 596 | "color": "#2ca02c", 597 | "elems": [ 598 | 1, 599 | 3, 600 | 0, 601 | 4, 602 | 9 603 | ], 604 | "name": "s2", 605 | "type": "set" 606 | } 607 | ], 608 | "stroke_color": null, 609 | "text_color": null, 610 | "theme": "light", 611 | "title": null, 612 | "value": null, 613 | "value_text_color": null 614 | } 615 | }, 616 | "4ffcbfc1e25a4d088ee1ce681643b731": { 617 | "model_module": "@jupyter-widgets/base", 618 | "model_module_version": "2.0.0", 619 | "model_name": "LayoutModel", 620 | "state": { 621 | "align_self": "stretch", 622 | "height": "400px" 623 | } 624 | }, 625 | "8f2bbf62f29b43499b0e372a5af0a029": { 626 | "model_module": "@jupyter-widgets/base", 627 | "model_module_version": "2.0.0", 628 | "model_name": "LayoutModel", 629 | "state": { 630 | "align_self": "stretch", 631 | "height": "400px" 632 | } 633 | }, 634 | "aaecd916780944c694e3995ae0c91d06": { 635 | "model_module": "@jupyter-widgets/base", 636 | "model_module_version": "2.0.0", 637 | "model_name": "LayoutModel", 638 | "state": { 639 | "align_self": "stretch", 640 | "height": "400px" 641 | } 642 | }, 643 | "af5914b90b704240ba1195962fe5c790": { 644 | "model_module": "@upsetjs/jupyter_widget", 645 | "model_module_version": "^1.3.0", 646 | "model_name": "UpSetModel", 647 | "state": { 648 | "_expression_data": false, 649 | "_model_module_version": "^1.3.0", 650 | "_render_mode": "kmap", 651 | "_view_module_version": "^1.3.0", 652 | "bar_padding": null, 653 | "color": null, 654 | "combinations": [ 655 | { 656 | "cardinality": 2, 657 | "color": "#1f77b4", 658 | "degree": 1, 659 | "elems": [ 660 | 10, 661 | 2 662 | ], 663 | "name": "s1", 664 | "set_names": [ 665 | "s1" 666 | ], 667 | "type": "distinctIntersection" 668 | }, 669 | { 670 | "cardinality": 2, 671 | "color": "#d62728", 672 | "degree": 1, 673 | "elems": [ 674 | 5, 675 | 8 676 | ], 677 | "name": "s3", 678 | "set_names": [ 679 | "s3" 680 | ], 681 | "type": "distinctIntersection" 682 | }, 683 | { 684 | "cardinality": 1, 685 | "color": "#2ca02c", 686 | "degree": 1, 687 | "elems": [ 688 | 3 689 | ], 690 | "name": "s2", 691 | "set_names": [ 692 | "s2" 693 | ], 694 | "type": "distinctIntersection" 695 | }, 696 | { 697 | "cardinality": 1, 698 | "color": "#9467bd", 699 | "degree": 2, 700 | "elems": [ 701 | 1 702 | ], 703 | "name": "(s1 ∩ s2)", 704 | "set_names": [ 705 | "s2", 706 | "s1" 707 | ], 708 | "type": "distinctIntersection" 709 | }, 710 | { 711 | "cardinality": 4, 712 | "color": "#8c564b", 713 | "degree": 2, 714 | "elems": [ 715 | 11, 716 | 12, 717 | 6, 718 | 7 719 | ], 720 | "name": "(s1 ∩ s3)", 721 | "set_names": [ 722 | "s3", 723 | "s1" 724 | ], 725 | "type": "distinctIntersection" 726 | }, 727 | { 728 | "cardinality": 1, 729 | "color": "#e377c2", 730 | "degree": 2, 731 | "elems": [ 732 | 9 733 | ], 734 | "name": "(s2 ∩ s3)", 735 | "set_names": [ 736 | "s2", 737 | "s3" 738 | ], 739 | "type": "distinctIntersection" 740 | }, 741 | { 742 | "cardinality": 2, 743 | "color": "#bcbd22", 744 | "degree": 3, 745 | "elems": [ 746 | 0, 747 | 4 748 | ], 749 | "name": "(s1 ∩ s2 ∩ s3)", 750 | "set_names": [ 751 | "s2", 752 | "s3", 753 | "s1" 754 | ], 755 | "type": "distinctIntersection" 756 | } 757 | ], 758 | "description": null, 759 | "elems": [ 760 | "a", 761 | "b", 762 | "c", 763 | "d", 764 | "e", 765 | "f", 766 | "g", 767 | "h", 768 | "i", 769 | "j", 770 | "k", 771 | "l", 772 | "m" 773 | ], 774 | "export_buttons": null, 775 | "font_family": null, 776 | "font_sizes": {}, 777 | "has_selection_color": null, 778 | "has_selection_opacity": 0.3, 779 | "layout": "IPY_MODEL_b9ae91baf5fe43a4b2ee2838a72d49eb", 780 | "mode": "hover", 781 | "numeric_scale": "linear", 782 | "opacity": null, 783 | "padding": null, 784 | "queries": [], 785 | "query_legend": null, 786 | "selection_color": "", 787 | "sets": [ 788 | { 789 | "cardinality": 9, 790 | "color": "#1f77b4", 791 | "elems": [ 792 | 1, 793 | 6, 794 | 0, 795 | 4, 796 | 7, 797 | 10, 798 | 2, 799 | 11, 800 | 12 801 | ], 802 | "name": "s1", 803 | "type": "set" 804 | }, 805 | { 806 | "cardinality": 9, 807 | "color": "#d62728", 808 | "elems": [ 809 | 8, 810 | 6, 811 | 7, 812 | 0, 813 | 4, 814 | 9, 815 | 11, 816 | 5, 817 | 12 818 | ], 819 | "name": "s3", 820 | "type": "set" 821 | }, 822 | { 823 | "cardinality": 5, 824 | "color": "#2ca02c", 825 | "elems": [ 826 | 1, 827 | 3, 828 | 0, 829 | 4, 830 | 9 831 | ], 832 | "name": "s2", 833 | "type": "set" 834 | } 835 | ], 836 | "stroke_color": null, 837 | "text_color": null, 838 | "theme": "light", 839 | "title": null, 840 | "value": null 841 | } 842 | }, 843 | "b9ae91baf5fe43a4b2ee2838a72d49eb": { 844 | "model_module": "@jupyter-widgets/base", 845 | "model_module_version": "2.0.0", 846 | "model_name": "LayoutModel", 847 | "state": { 848 | "align_self": "stretch", 849 | "height": "400px" 850 | } 851 | }, 852 | "f7061c4ff76b44cd9971d4d5cb79d2ea": { 853 | "model_module": "@upsetjs/jupyter_widget", 854 | "model_module_version": "^1.3.0", 855 | "model_name": "UpSetModel", 856 | "state": { 857 | "_expression_data": false, 858 | "_model_module_version": "^1.3.0", 859 | "_render_mode": "venn", 860 | "_view_module_version": "^1.3.0", 861 | "color": null, 862 | "combinations": [ 863 | { 864 | "cardinality": 2, 865 | "color": "#1f77b4", 866 | "degree": 1, 867 | "elems": [ 868 | 10, 869 | 2 870 | ], 871 | "name": "s1", 872 | "set_names": [ 873 | "s1" 874 | ], 875 | "type": "distinctIntersection" 876 | }, 877 | { 878 | "cardinality": 2, 879 | "color": "#d62728", 880 | "degree": 1, 881 | "elems": [ 882 | 5, 883 | 8 884 | ], 885 | "name": "s3", 886 | "set_names": [ 887 | "s3" 888 | ], 889 | "type": "distinctIntersection" 890 | }, 891 | { 892 | "cardinality": 1, 893 | "color": "#2ca02c", 894 | "degree": 1, 895 | "elems": [ 896 | 3 897 | ], 898 | "name": "s2", 899 | "set_names": [ 900 | "s2" 901 | ], 902 | "type": "distinctIntersection" 903 | }, 904 | { 905 | "cardinality": 1, 906 | "color": "#9467bd", 907 | "degree": 2, 908 | "elems": [ 909 | 1 910 | ], 911 | "name": "(s1 ∩ s2)", 912 | "set_names": [ 913 | "s2", 914 | "s1" 915 | ], 916 | "type": "distinctIntersection" 917 | }, 918 | { 919 | "cardinality": 4, 920 | "color": "#8c564b", 921 | "degree": 2, 922 | "elems": [ 923 | 11, 924 | 12, 925 | 6, 926 | 7 927 | ], 928 | "name": "(s1 ∩ s3)", 929 | "set_names": [ 930 | "s3", 931 | "s1" 932 | ], 933 | "type": "distinctIntersection" 934 | }, 935 | { 936 | "cardinality": 1, 937 | "color": "#e377c2", 938 | "degree": 2, 939 | "elems": [ 940 | 9 941 | ], 942 | "name": "(s2 ∩ s3)", 943 | "set_names": [ 944 | "s2", 945 | "s3" 946 | ], 947 | "type": "distinctIntersection" 948 | }, 949 | { 950 | "cardinality": 2, 951 | "color": "#bcbd22", 952 | "degree": 3, 953 | "elems": [ 954 | 0, 955 | 4 956 | ], 957 | "name": "(s1 ∩ s2 ∩ s3)", 958 | "set_names": [ 959 | "s2", 960 | "s3", 961 | "s1" 962 | ], 963 | "type": "distinctIntersection" 964 | } 965 | ], 966 | "description": null, 967 | "elems": [ 968 | "a", 969 | "b", 970 | "c", 971 | "d", 972 | "e", 973 | "f", 974 | "g", 975 | "h", 976 | "i", 977 | "j", 978 | "k", 979 | "l", 980 | "m" 981 | ], 982 | "export_buttons": null, 983 | "font_family": null, 984 | "font_sizes": {}, 985 | "has_selection_color": null, 986 | "has_selection_opacity": 0.3, 987 | "layout": "IPY_MODEL_aaecd916780944c694e3995ae0c91d06", 988 | "mode": "hover", 989 | "opacity": null, 990 | "padding": null, 991 | "queries": [], 992 | "query_legend": null, 993 | "selection_color": "", 994 | "sets": [ 995 | { 996 | "cardinality": 9, 997 | "color": "#1f77b4", 998 | "elems": [ 999 | 1, 1000 | 6, 1001 | 0, 1002 | 4, 1003 | 7, 1004 | 10, 1005 | 2, 1006 | 11, 1007 | 12 1008 | ], 1009 | "name": "s1", 1010 | "type": "set" 1011 | }, 1012 | { 1013 | "cardinality": 9, 1014 | "color": "#d62728", 1015 | "elems": [ 1016 | 8, 1017 | 6, 1018 | 7, 1019 | 0, 1020 | 4, 1021 | 9, 1022 | 11, 1023 | 5, 1024 | 12 1025 | ], 1026 | "name": "s3", 1027 | "type": "set" 1028 | }, 1029 | { 1030 | "cardinality": 5, 1031 | "color": "#2ca02c", 1032 | "elems": [ 1033 | 1, 1034 | 3, 1035 | 0, 1036 | 4, 1037 | 9 1038 | ], 1039 | "name": "s2", 1040 | "type": "set" 1041 | } 1042 | ], 1043 | "stroke_color": null, 1044 | "text_color": null, 1045 | "theme": "light", 1046 | "title": null, 1047 | "value": null, 1048 | "value_text_color": null 1049 | } 1050 | } 1051 | }, 1052 | "version_major": 2, 1053 | "version_minor": 0 1054 | } 1055 | } 1056 | }, 1057 | "nbformat": 4, 1058 | "nbformat_minor": 4 1059 | } 1060 | -------------------------------------------------------------------------------- /examples/kmap.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UpSet.js Jupyter Widget - Karnaugh Map\n", 8 | "\n", 9 | "UpSet.js has a basic support for a variant of Karnaugh Maps." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from ipywidgets import interact\n", 19 | "from upsetjs_jupyter_widget import UpSetJSKarnaughMapWidget\n", 20 | "import pandas as pd" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "This wrapper is implemented in Python 3 with mypy typings and generics. The generic type `T` of the `UpSetJSKarnaughMapWidget` is type of element that we wanna handle. In the following example we handle `str` elements." 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "w = UpSetJSKarnaughMapWidget[str]()" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "## Basic User Interface" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "data": { 53 | "application/vnd.jupyter.widget-view+json": { 54 | "model_id": "4f9e4feff5f14e6a9edc1fa0495b03c5", 55 | "version_major": 2, 56 | "version_minor": 0 57 | }, 58 | "text/plain": [ 59 | "UpSetJSKarnaughMapWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=one, sets={'one'}, cardin…" 60 | ] 61 | }, 62 | "execution_count": 3, 63 | "metadata": {}, 64 | "output_type": "execute_result" 65 | } 66 | ], 67 | "source": [ 68 | "dict_input = dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])\n", 69 | "w.from_dict(dict_input)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "## Input Formats\n", 77 | "\n", 78 | "same as UpSetJSWidget" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "## Basic Attrributes\n", 86 | "\n", 87 | "same as UpSetJSWidget" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "## Interaction\n", 95 | "\n", 96 | "same as UpSetJSWidget" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "## Queries\n", 104 | "\n", 105 | "same as UpSetJSWidget" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Styling\n", 113 | "\n", 114 | "same as UpSetJSWidget" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 3.7.1 64-bit ('upsetjs_jupyter_widget': pipenv)", 128 | "language": "python", 129 | "name": "python37164bitupsetjsjupyterwidgetpipenvfee1645c874046dc9f0c363432ab7182" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 3 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython3", 141 | "version": "3.7.1" 142 | }, 143 | "widgets": { 144 | "application/vnd.jupyter.widget-state+json": { 145 | "state": { 146 | "0d53eac5ebec494bb2655bd95293da12": { 147 | "model_module": "@jupyter-widgets/base", 148 | "model_module_version": "2.0.0", 149 | "model_name": "LayoutModel", 150 | "state": { 151 | "align_self": "stretch", 152 | "height": "400px" 153 | } 154 | }, 155 | "4f9e4feff5f14e6a9edc1fa0495b03c5": { 156 | "model_module": "@upsetjs/jupyter_widget", 157 | "model_module_version": "^1.3.0", 158 | "model_name": "UpSetModel", 159 | "state": { 160 | "_expression_data": false, 161 | "_model_module_version": "^1.3.0", 162 | "_render_mode": "kmap", 163 | "_view_module_version": "^1.3.0", 164 | "bar_padding": null, 165 | "color": null, 166 | "combinations": [ 167 | { 168 | "cardinality": 2, 169 | "color": null, 170 | "degree": 1, 171 | "elems": [ 172 | 10, 173 | 2 174 | ], 175 | "name": "one", 176 | "set_names": [ 177 | "one" 178 | ], 179 | "type": "distinctIntersection" 180 | }, 181 | { 182 | "cardinality": 2, 183 | "color": null, 184 | "degree": 1, 185 | "elems": [ 186 | 5, 187 | 8 188 | ], 189 | "name": "three", 190 | "set_names": [ 191 | "three" 192 | ], 193 | "type": "distinctIntersection" 194 | }, 195 | { 196 | "cardinality": 1, 197 | "color": null, 198 | "degree": 1, 199 | "elems": [ 200 | 3 201 | ], 202 | "name": "two", 203 | "set_names": [ 204 | "two" 205 | ], 206 | "type": "distinctIntersection" 207 | }, 208 | { 209 | "cardinality": 1, 210 | "color": null, 211 | "degree": 2, 212 | "elems": [ 213 | 1 214 | ], 215 | "name": "(one ∩ two)", 216 | "set_names": [ 217 | "one", 218 | "two" 219 | ], 220 | "type": "distinctIntersection" 221 | }, 222 | { 223 | "cardinality": 4, 224 | "color": null, 225 | "degree": 2, 226 | "elems": [ 227 | 12, 228 | 6, 229 | 11, 230 | 7 231 | ], 232 | "name": "(one ∩ three)", 233 | "set_names": [ 234 | "one", 235 | "three" 236 | ], 237 | "type": "distinctIntersection" 238 | }, 239 | { 240 | "cardinality": 1, 241 | "color": null, 242 | "degree": 2, 243 | "elems": [ 244 | 9 245 | ], 246 | "name": "(two ∩ three)", 247 | "set_names": [ 248 | "two", 249 | "three" 250 | ], 251 | "type": "distinctIntersection" 252 | }, 253 | { 254 | "cardinality": 2, 255 | "color": null, 256 | "degree": 3, 257 | "elems": [ 258 | 0, 259 | 4 260 | ], 261 | "name": "(one ∩ two ∩ three)", 262 | "set_names": [ 263 | "one", 264 | "two", 265 | "three" 266 | ], 267 | "type": "distinctIntersection" 268 | } 269 | ], 270 | "description": null, 271 | "elems": [ 272 | "a", 273 | "b", 274 | "c", 275 | "d", 276 | "e", 277 | "f", 278 | "g", 279 | "h", 280 | "i", 281 | "j", 282 | "k", 283 | "l", 284 | "m" 285 | ], 286 | "export_buttons": null, 287 | "font_family": null, 288 | "font_sizes": {}, 289 | "has_selection_color": null, 290 | "has_selection_opacity": null, 291 | "layout": "IPY_MODEL_0d53eac5ebec494bb2655bd95293da12", 292 | "mode": "hover", 293 | "numeric_scale": "linear", 294 | "opacity": null, 295 | "padding": null, 296 | "queries": [], 297 | "query_legend": null, 298 | "selection_color": null, 299 | "sets": [ 300 | { 301 | "cardinality": 9, 302 | "color": null, 303 | "elems": [ 304 | 1, 305 | 7, 306 | 10, 307 | 0, 308 | 6, 309 | 11, 310 | 2, 311 | 12, 312 | 4 313 | ], 314 | "name": "one", 315 | "type": "set" 316 | }, 317 | { 318 | "cardinality": 9, 319 | "color": null, 320 | "elems": [ 321 | 5, 322 | 7, 323 | 0, 324 | 6, 325 | 11, 326 | 9, 327 | 12, 328 | 4, 329 | 8 330 | ], 331 | "name": "three", 332 | "type": "set" 333 | }, 334 | { 335 | "cardinality": 5, 336 | "color": null, 337 | "elems": [ 338 | 1, 339 | 0, 340 | 3, 341 | 9, 342 | 4 343 | ], 344 | "name": "two", 345 | "type": "set" 346 | } 347 | ], 348 | "stroke_color": null, 349 | "text_color": null, 350 | "theme": "light", 351 | "title": null, 352 | "value": null 353 | } 354 | } 355 | }, 356 | "version_major": 2, 357 | "version_minor": 0 358 | } 359 | } 360 | }, 361 | "nbformat": 4, 362 | "nbformat_minor": 4 363 | } 364 | -------------------------------------------------------------------------------- /examples/venn.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UpSet.js Jupyter Widget - Venn and Euler Diagram\n", 8 | "\n", 9 | "UpSet.js has a basic support for classical Venn diagrams for up to five sets." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from ipywidgets import interact\n", 19 | "from upsetjs_jupyter_widget import UpSetJSVennDiagramWidget, UpSetJSEulerDiagramWidget\n", 20 | "import pandas as pd" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "This wrapper is implemented in Python 3 with mypy typings and generics. The generic type `T` of the `UpSetJSVennDiagramWidget` is type of element that we wanna handle. In the following example we handle `str` elements." 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "w = UpSetJSVennDiagramWidget[str]()" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "e = UpSetJSEulerDiagramWidget[str]()" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## Basic User Interface" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 12, 58 | "metadata": {}, 59 | "outputs": [ 60 | { 61 | "data": { 62 | "application/vnd.jupyter.widget-view+json": { 63 | "model_id": "d357f362e65849cebf0855db74ec3c2a", 64 | "version_major": 2, 65 | "version_minor": 0 66 | }, 67 | "text/plain": [ 68 | "UpSetJSVennDiagramWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=one, sets={'one'}, cardin…" 69 | ] 70 | }, 71 | "execution_count": 12, 72 | "metadata": {}, 73 | "output_type": "execute_result" 74 | } 75 | ], 76 | "source": [ 77 | "dict_input = dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])\n", 78 | "w.from_dict(dict_input)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 11, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "data": { 88 | "application/vnd.jupyter.widget-view+json": { 89 | "model_id": "d0472425431a4ed2b928487bf92e241a", 90 | "version_major": 2, 91 | "version_minor": 0 92 | }, 93 | "text/plain": [ 94 | "UpSetJSVennDiagramWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=five, sets={'five'}, card…" 95 | ] 96 | }, 97 | "execution_count": 11, 98 | "metadata": {}, 99 | "output_type": "execute_result" 100 | } 101 | ], 102 | "source": [ 103 | "dict_input5 = dict(one = ['a', 'b', 'c', 'e', 'g', 'h', 'k', 'l', 'm'], two = ['a', 'b', 'd', 'e', 'j'], \n", 104 | " three = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'], \n", 105 | " four = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'], \n", 106 | " five = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'm'])\n", 107 | "w.copy().from_dict(dict_input5)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 5, 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "data": { 117 | "application/vnd.jupyter.widget-view+json": { 118 | "model_id": "36e1c9e250b040a6a292285b8fc1969a", 119 | "version_major": 2, 120 | "version_minor": 0 121 | }, 122 | "text/plain": [ 123 | "UpSetJSEulerDiagramWidget(value=None, combinations=[UpSetSetDistinctIntersection(name=one, sets={'one'}, cardi…" 124 | ] 125 | }, 126 | "execution_count": 5, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "e.from_dict(dict_input)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "## Input Formats\n", 140 | "\n", 141 | "see UpSetSJWidget" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "## Basic Attrributes\n", 149 | "\n", 150 | "see UpsetJSWidget" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "## Interaction\n", 158 | "\n", 159 | "see UpSetJSWidget" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "## Queries\n", 167 | "\n", 168 | "see UpSetJSWidget" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "## Styling\n", 176 | "\n", 177 | "see UpSetJSWidget" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [] 186 | } 187 | ], 188 | "metadata": { 189 | "kernelspec": { 190 | "display_name": "Python 3.7.1 64-bit ('upsetjs_jupyter_widget': pipenv)", 191 | "language": "python", 192 | "name": "python37164bitupsetjsjupyterwidgetpipenvfee1645c874046dc9f0c363432ab7182" 193 | }, 194 | "language_info": { 195 | "codemirror_mode": { 196 | "name": "ipython", 197 | "version": 3 198 | }, 199 | "file_extension": ".py", 200 | "mimetype": "text/x-python", 201 | "name": "python", 202 | "nbconvert_exporter": "python", 203 | "pygments_lexer": "ipython3", 204 | "version": "3.7.1" 205 | }, 206 | "widgets": { 207 | "application/vnd.jupyter.widget-state+json": { 208 | "state": { 209 | "36e1c9e250b040a6a292285b8fc1969a": { 210 | "model_module": "@upsetjs/jupyter_widget", 211 | "model_module_version": "^1.3.0", 212 | "model_name": "UpSetModel", 213 | "state": { 214 | "_expression_data": false, 215 | "_model_module_version": "^1.3.0", 216 | "_render_mode": "euler", 217 | "_view_module_version": "^1.3.0", 218 | "color": null, 219 | "combinations": [ 220 | { 221 | "cardinality": 2, 222 | "color": null, 223 | "degree": 1, 224 | "elems": [ 225 | 2, 226 | 10 227 | ], 228 | "name": "one", 229 | "set_names": [ 230 | "one" 231 | ], 232 | "type": "distinctIntersection" 233 | }, 234 | { 235 | "cardinality": 2, 236 | "color": null, 237 | "degree": 1, 238 | "elems": [ 239 | 5, 240 | 8 241 | ], 242 | "name": "three", 243 | "set_names": [ 244 | "three" 245 | ], 246 | "type": "distinctIntersection" 247 | }, 248 | { 249 | "cardinality": 1, 250 | "color": null, 251 | "degree": 1, 252 | "elems": [ 253 | 3 254 | ], 255 | "name": "two", 256 | "set_names": [ 257 | "two" 258 | ], 259 | "type": "distinctIntersection" 260 | }, 261 | { 262 | "cardinality": 1, 263 | "color": null, 264 | "degree": 2, 265 | "elems": [ 266 | 1 267 | ], 268 | "name": "(one ∩ two)", 269 | "set_names": [ 270 | "two", 271 | "one" 272 | ], 273 | "type": "distinctIntersection" 274 | }, 275 | { 276 | "cardinality": 4, 277 | "color": null, 278 | "degree": 2, 279 | "elems": [ 280 | 6, 281 | 12, 282 | 7, 283 | 11 284 | ], 285 | "name": "(one ∩ three)", 286 | "set_names": [ 287 | "three", 288 | "one" 289 | ], 290 | "type": "distinctIntersection" 291 | }, 292 | { 293 | "cardinality": 1, 294 | "color": null, 295 | "degree": 2, 296 | "elems": [ 297 | 9 298 | ], 299 | "name": "(two ∩ three)", 300 | "set_names": [ 301 | "two", 302 | "three" 303 | ], 304 | "type": "distinctIntersection" 305 | }, 306 | { 307 | "cardinality": 2, 308 | "color": null, 309 | "degree": 3, 310 | "elems": [ 311 | 0, 312 | 4 313 | ], 314 | "name": "(one ∩ two ∩ three)", 315 | "set_names": [ 316 | "two", 317 | "three", 318 | "one" 319 | ], 320 | "type": "distinctIntersection" 321 | } 322 | ], 323 | "description": null, 324 | "elems": [ 325 | "a", 326 | "b", 327 | "c", 328 | "d", 329 | "e", 330 | "f", 331 | "g", 332 | "h", 333 | "i", 334 | "j", 335 | "k", 336 | "l", 337 | "m" 338 | ], 339 | "export_buttons": null, 340 | "font_family": null, 341 | "font_sizes": {}, 342 | "has_selection_color": null, 343 | "has_selection_opacity": null, 344 | "layout": "IPY_MODEL_f855e1e4722b48eabc2cf978408a3877", 345 | "mode": "hover", 346 | "opacity": null, 347 | "padding": null, 348 | "queries": [], 349 | "query_legend": null, 350 | "selection_color": null, 351 | "sets": [ 352 | { 353 | "cardinality": 9, 354 | "color": null, 355 | "elems": [ 356 | 12, 357 | 7, 358 | 2, 359 | 6, 360 | 10, 361 | 1, 362 | 11, 363 | 0, 364 | 4 365 | ], 366 | "name": "one", 367 | "type": "set" 368 | }, 369 | { 370 | "cardinality": 9, 371 | "color": null, 372 | "elems": [ 373 | 12, 374 | 7, 375 | 8, 376 | 6, 377 | 11, 378 | 5, 379 | 0, 380 | 9, 381 | 4 382 | ], 383 | "name": "three", 384 | "type": "set" 385 | }, 386 | { 387 | "cardinality": 5, 388 | "color": null, 389 | "elems": [ 390 | 1, 391 | 3, 392 | 0, 393 | 9, 394 | 4 395 | ], 396 | "name": "two", 397 | "type": "set" 398 | } 399 | ], 400 | "stroke_color": null, 401 | "text_color": null, 402 | "theme": "light", 403 | "title": null, 404 | "value": null, 405 | "value_text_color": null 406 | } 407 | }, 408 | "7cea33f01d0247f288c27633e4c6e20a": { 409 | "model_module": "@jupyter-widgets/base", 410 | "model_module_version": "2.0.0", 411 | "model_name": "LayoutModel", 412 | "state": { 413 | "align_self": "stretch", 414 | "height": "400px" 415 | } 416 | }, 417 | "d0472425431a4ed2b928487bf92e241a": { 418 | "model_module": "@upsetjs/jupyter_widget", 419 | "model_module_version": "^1.3.0", 420 | "model_name": "UpSetModel", 421 | "state": { 422 | "_expression_data": false, 423 | "_model_module_version": "^1.3.0", 424 | "_render_mode": "venn", 425 | "_view_module_version": "^1.3.0", 426 | "color": null, 427 | "combinations": [ 428 | { 429 | "cardinality": 0, 430 | "color": null, 431 | "degree": 1, 432 | "elems": [], 433 | "name": "five", 434 | "set_names": [ 435 | "five" 436 | ], 437 | "type": "distinctIntersection" 438 | }, 439 | { 440 | "cardinality": 0, 441 | "color": null, 442 | "degree": 1, 443 | "elems": [], 444 | "name": "four", 445 | "set_names": [ 446 | "four" 447 | ], 448 | "type": "distinctIntersection" 449 | }, 450 | { 451 | "cardinality": 2, 452 | "color": null, 453 | "degree": 1, 454 | "elems": [ 455 | 2, 456 | 10 457 | ], 458 | "name": "one", 459 | "set_names": [ 460 | "one" 461 | ], 462 | "type": "distinctIntersection" 463 | }, 464 | { 465 | "cardinality": 0, 466 | "color": null, 467 | "degree": 1, 468 | "elems": [], 469 | "name": "three", 470 | "set_names": [ 471 | "three" 472 | ], 473 | "type": "distinctIntersection" 474 | }, 475 | { 476 | "cardinality": 1, 477 | "color": null, 478 | "degree": 1, 479 | "elems": [ 480 | 3 481 | ], 482 | "name": "two", 483 | "set_names": [ 484 | "two" 485 | ], 486 | "type": "distinctIntersection" 487 | }, 488 | { 489 | "cardinality": 0, 490 | "color": null, 491 | "degree": 2, 492 | "elems": [], 493 | "name": "(one ∩ five)", 494 | "set_names": [ 495 | "five", 496 | "one" 497 | ], 498 | "type": "distinctIntersection" 499 | }, 500 | { 501 | "cardinality": 0, 502 | "color": null, 503 | "degree": 2, 504 | "elems": [], 505 | "name": "(two ∩ five)", 506 | "set_names": [ 507 | "two", 508 | "five" 509 | ], 510 | "type": "distinctIntersection" 511 | }, 512 | { 513 | "cardinality": 0, 514 | "color": null, 515 | "degree": 2, 516 | "elems": [], 517 | "name": "(three ∩ five)", 518 | "set_names": [ 519 | "three", 520 | "five" 521 | ], 522 | "type": "distinctIntersection" 523 | }, 524 | { 525 | "cardinality": 0, 526 | "color": null, 527 | "degree": 2, 528 | "elems": [], 529 | "name": "(four ∩ five)", 530 | "set_names": [ 531 | "five", 532 | "four" 533 | ], 534 | "type": "distinctIntersection" 535 | }, 536 | { 537 | "cardinality": 0, 538 | "color": null, 539 | "degree": 2, 540 | "elems": [], 541 | "name": "(one ∩ four)", 542 | "set_names": [ 543 | "four", 544 | "one" 545 | ], 546 | "type": "distinctIntersection" 547 | }, 548 | { 549 | "cardinality": 0, 550 | "color": null, 551 | "degree": 2, 552 | "elems": [], 553 | "name": "(two ∩ four)", 554 | "set_names": [ 555 | "two", 556 | "four" 557 | ], 558 | "type": "distinctIntersection" 559 | }, 560 | { 561 | "cardinality": 0, 562 | "color": null, 563 | "degree": 2, 564 | "elems": [], 565 | "name": "(three ∩ four)", 566 | "set_names": [ 567 | "three", 568 | "four" 569 | ], 570 | "type": "distinctIntersection" 571 | }, 572 | { 573 | "cardinality": 1, 574 | "color": null, 575 | "degree": 2, 576 | "elems": [ 577 | 1 578 | ], 579 | "name": "(one ∩ two)", 580 | "set_names": [ 581 | "two", 582 | "one" 583 | ], 584 | "type": "distinctIntersection" 585 | }, 586 | { 587 | "cardinality": 0, 588 | "color": null, 589 | "degree": 2, 590 | "elems": [], 591 | "name": "(one ∩ three)", 592 | "set_names": [ 593 | "three", 594 | "one" 595 | ], 596 | "type": "distinctIntersection" 597 | }, 598 | { 599 | "cardinality": 0, 600 | "color": null, 601 | "degree": 2, 602 | "elems": [], 603 | "name": "(two ∩ three)", 604 | "set_names": [ 605 | "two", 606 | "three" 607 | ], 608 | "type": "distinctIntersection" 609 | }, 610 | { 611 | "cardinality": 0, 612 | "color": null, 613 | "degree": 3, 614 | "elems": [], 615 | "name": "(one ∩ two ∩ five)", 616 | "set_names": [ 617 | "two", 618 | "five", 619 | "one" 620 | ], 621 | "type": "distinctIntersection" 622 | }, 623 | { 624 | "cardinality": 0, 625 | "color": null, 626 | "degree": 3, 627 | "elems": [], 628 | "name": "(one ∩ three ∩ five)", 629 | "set_names": [ 630 | "three", 631 | "five", 632 | "one" 633 | ], 634 | "type": "distinctIntersection" 635 | }, 636 | { 637 | "cardinality": 0, 638 | "color": null, 639 | "degree": 3, 640 | "elems": [], 641 | "name": "(one ∩ four ∩ five)", 642 | "set_names": [ 643 | "five", 644 | "four", 645 | "one" 646 | ], 647 | "type": "distinctIntersection" 648 | }, 649 | { 650 | "cardinality": 0, 651 | "color": null, 652 | "degree": 3, 653 | "elems": [], 654 | "name": "(two ∩ three ∩ five)", 655 | "set_names": [ 656 | "two", 657 | "three", 658 | "five" 659 | ], 660 | "type": "distinctIntersection" 661 | }, 662 | { 663 | "cardinality": 0, 664 | "color": null, 665 | "degree": 3, 666 | "elems": [], 667 | "name": "(two ∩ four ∩ five)", 668 | "set_names": [ 669 | "two", 670 | "five", 671 | "four" 672 | ], 673 | "type": "distinctIntersection" 674 | }, 675 | { 676 | "cardinality": 2, 677 | "color": null, 678 | "degree": 3, 679 | "elems": [ 680 | 5, 681 | 8 682 | ], 683 | "name": "(three ∩ four ∩ five)", 684 | "set_names": [ 685 | "three", 686 | "five", 687 | "four" 688 | ], 689 | "type": "distinctIntersection" 690 | }, 691 | { 692 | "cardinality": 0, 693 | "color": null, 694 | "degree": 3, 695 | "elems": [], 696 | "name": "(one ∩ two ∩ four)", 697 | "set_names": [ 698 | "two", 699 | "four", 700 | "one" 701 | ], 702 | "type": "distinctIntersection" 703 | }, 704 | { 705 | "cardinality": 0, 706 | "color": null, 707 | "degree": 3, 708 | "elems": [], 709 | "name": "(one ∩ three ∩ four)", 710 | "set_names": [ 711 | "three", 712 | "four", 713 | "one" 714 | ], 715 | "type": "distinctIntersection" 716 | }, 717 | { 718 | "cardinality": 0, 719 | "color": null, 720 | "degree": 3, 721 | "elems": [], 722 | "name": "(two ∩ three ∩ four)", 723 | "set_names": [ 724 | "two", 725 | "three", 726 | "four" 727 | ], 728 | "type": "distinctIntersection" 729 | }, 730 | { 731 | "cardinality": 0, 732 | "color": null, 733 | "degree": 3, 734 | "elems": [], 735 | "name": "(one ∩ two ∩ three)", 736 | "set_names": [ 737 | "two", 738 | "three", 739 | "one" 740 | ], 741 | "type": "distinctIntersection" 742 | }, 743 | { 744 | "cardinality": 0, 745 | "color": null, 746 | "degree": 4, 747 | "elems": [], 748 | "name": "(one ∩ two ∩ three ∩ five)", 749 | "set_names": [ 750 | "two", 751 | "three", 752 | "five", 753 | "one" 754 | ], 755 | "type": "distinctIntersection" 756 | }, 757 | { 758 | "cardinality": 0, 759 | "color": null, 760 | "degree": 4, 761 | "elems": [], 762 | "name": "(one ∩ two ∩ four ∩ five)", 763 | "set_names": [ 764 | "two", 765 | "five", 766 | "four", 767 | "one" 768 | ], 769 | "type": "distinctIntersection" 770 | }, 771 | { 772 | "cardinality": 4, 773 | "color": null, 774 | "degree": 4, 775 | "elems": [ 776 | 6, 777 | 12, 778 | 7, 779 | 11 780 | ], 781 | "name": "(one ∩ three ∩ four ∩ five)", 782 | "set_names": [ 783 | "three", 784 | "five", 785 | "four", 786 | "one" 787 | ], 788 | "type": "distinctIntersection" 789 | }, 790 | { 791 | "cardinality": 1, 792 | "color": null, 793 | "degree": 4, 794 | "elems": [ 795 | 9 796 | ], 797 | "name": "(two ∩ three ∩ four ∩ five)", 798 | "set_names": [ 799 | "two", 800 | "three", 801 | "five", 802 | "four" 803 | ], 804 | "type": "distinctIntersection" 805 | }, 806 | { 807 | "cardinality": 0, 808 | "color": null, 809 | "degree": 4, 810 | "elems": [], 811 | "name": "(one ∩ two ∩ three ∩ four)", 812 | "set_names": [ 813 | "two", 814 | "three", 815 | "four", 816 | "one" 817 | ], 818 | "type": "distinctIntersection" 819 | }, 820 | { 821 | "cardinality": 2, 822 | "color": null, 823 | "degree": 5, 824 | "elems": [ 825 | 0, 826 | 4 827 | ], 828 | "name": "(one ∩ two ∩ three ∩ four ∩ five)", 829 | "set_names": [ 830 | "five", 831 | "one", 832 | "two", 833 | "three", 834 | "four" 835 | ], 836 | "type": "distinctIntersection" 837 | } 838 | ], 839 | "description": null, 840 | "elems": [ 841 | "a", 842 | "b", 843 | "c", 844 | "d", 845 | "e", 846 | "f", 847 | "g", 848 | "h", 849 | "i", 850 | "j", 851 | "k", 852 | "l", 853 | "m" 854 | ], 855 | "export_buttons": null, 856 | "font_family": null, 857 | "font_sizes": {}, 858 | "has_selection_color": null, 859 | "has_selection_opacity": null, 860 | "layout": "IPY_MODEL_7cea33f01d0247f288c27633e4c6e20a", 861 | "mode": "hover", 862 | "opacity": null, 863 | "padding": null, 864 | "queries": [], 865 | "query_legend": null, 866 | "selection_color": null, 867 | "sets": [ 868 | { 869 | "cardinality": 9, 870 | "color": null, 871 | "elems": [ 872 | 12, 873 | 7, 874 | 8, 875 | 6, 876 | 11, 877 | 5, 878 | 0, 879 | 9, 880 | 4 881 | ], 882 | "name": "five", 883 | "type": "set" 884 | }, 885 | { 886 | "cardinality": 9, 887 | "color": null, 888 | "elems": [ 889 | 12, 890 | 7, 891 | 8, 892 | 6, 893 | 11, 894 | 5, 895 | 0, 896 | 9, 897 | 4 898 | ], 899 | "name": "four", 900 | "type": "set" 901 | }, 902 | { 903 | "cardinality": 9, 904 | "color": null, 905 | "elems": [ 906 | 12, 907 | 7, 908 | 2, 909 | 6, 910 | 10, 911 | 1, 912 | 11, 913 | 0, 914 | 4 915 | ], 916 | "name": "one", 917 | "type": "set" 918 | }, 919 | { 920 | "cardinality": 9, 921 | "color": null, 922 | "elems": [ 923 | 12, 924 | 7, 925 | 8, 926 | 6, 927 | 11, 928 | 5, 929 | 0, 930 | 9, 931 | 4 932 | ], 933 | "name": "three", 934 | "type": "set" 935 | }, 936 | { 937 | "cardinality": 5, 938 | "color": null, 939 | "elems": [ 940 | 1, 941 | 3, 942 | 0, 943 | 9, 944 | 4 945 | ], 946 | "name": "two", 947 | "type": "set" 948 | } 949 | ], 950 | "stroke_color": null, 951 | "text_color": null, 952 | "theme": "light", 953 | "title": null, 954 | "value": null, 955 | "value_text_color": null 956 | } 957 | }, 958 | "d357f362e65849cebf0855db74ec3c2a": { 959 | "model_module": "@upsetjs/jupyter_widget", 960 | "model_module_version": "^1.3.0", 961 | "model_name": "UpSetModel", 962 | "state": { 963 | "_expression_data": false, 964 | "_model_module_version": "^1.3.0", 965 | "_render_mode": "venn", 966 | "_view_module_version": "^1.3.0", 967 | "color": null, 968 | "combinations": [ 969 | { 970 | "cardinality": 2, 971 | "color": null, 972 | "degree": 1, 973 | "elems": [ 974 | 2, 975 | 10 976 | ], 977 | "name": "one", 978 | "set_names": [ 979 | "one" 980 | ], 981 | "type": "distinctIntersection" 982 | }, 983 | { 984 | "cardinality": 2, 985 | "color": null, 986 | "degree": 1, 987 | "elems": [ 988 | 5, 989 | 8 990 | ], 991 | "name": "three", 992 | "set_names": [ 993 | "three" 994 | ], 995 | "type": "distinctIntersection" 996 | }, 997 | { 998 | "cardinality": 1, 999 | "color": null, 1000 | "degree": 1, 1001 | "elems": [ 1002 | 3 1003 | ], 1004 | "name": "two", 1005 | "set_names": [ 1006 | "two" 1007 | ], 1008 | "type": "distinctIntersection" 1009 | }, 1010 | { 1011 | "cardinality": 1, 1012 | "color": null, 1013 | "degree": 2, 1014 | "elems": [ 1015 | 1 1016 | ], 1017 | "name": "(one ∩ two)", 1018 | "set_names": [ 1019 | "one", 1020 | "two" 1021 | ], 1022 | "type": "distinctIntersection" 1023 | }, 1024 | { 1025 | "cardinality": 4, 1026 | "color": null, 1027 | "degree": 2, 1028 | "elems": [ 1029 | 6, 1030 | 12, 1031 | 7, 1032 | 11 1033 | ], 1034 | "name": "(one ∩ three)", 1035 | "set_names": [ 1036 | "one", 1037 | "three" 1038 | ], 1039 | "type": "distinctIntersection" 1040 | }, 1041 | { 1042 | "cardinality": 1, 1043 | "color": null, 1044 | "degree": 2, 1045 | "elems": [ 1046 | 9 1047 | ], 1048 | "name": "(two ∩ three)", 1049 | "set_names": [ 1050 | "two", 1051 | "three" 1052 | ], 1053 | "type": "distinctIntersection" 1054 | }, 1055 | { 1056 | "cardinality": 2, 1057 | "color": null, 1058 | "degree": 3, 1059 | "elems": [ 1060 | 0, 1061 | 4 1062 | ], 1063 | "name": "(one ∩ two ∩ three)", 1064 | "set_names": [ 1065 | "one", 1066 | "three", 1067 | "two" 1068 | ], 1069 | "type": "distinctIntersection" 1070 | } 1071 | ], 1072 | "description": null, 1073 | "elems": [ 1074 | "a", 1075 | "b", 1076 | "c", 1077 | "d", 1078 | "e", 1079 | "f", 1080 | "g", 1081 | "h", 1082 | "i", 1083 | "j", 1084 | "k", 1085 | "l", 1086 | "m" 1087 | ], 1088 | "export_buttons": null, 1089 | "font_family": null, 1090 | "font_sizes": {}, 1091 | "has_selection_color": null, 1092 | "has_selection_opacity": null, 1093 | "layout": "IPY_MODEL_eea479bcd1f14791a2fdbc117603e77b", 1094 | "mode": "hover", 1095 | "opacity": null, 1096 | "padding": null, 1097 | "queries": [], 1098 | "query_legend": null, 1099 | "selection_color": null, 1100 | "sets": [ 1101 | { 1102 | "cardinality": 9, 1103 | "color": null, 1104 | "elems": [ 1105 | 12, 1106 | 7, 1107 | 2, 1108 | 6, 1109 | 10, 1110 | 1, 1111 | 11, 1112 | 0, 1113 | 4 1114 | ], 1115 | "name": "one", 1116 | "type": "set" 1117 | }, 1118 | { 1119 | "cardinality": 9, 1120 | "color": null, 1121 | "elems": [ 1122 | 12, 1123 | 7, 1124 | 8, 1125 | 6, 1126 | 11, 1127 | 5, 1128 | 0, 1129 | 9, 1130 | 4 1131 | ], 1132 | "name": "three", 1133 | "type": "set" 1134 | }, 1135 | { 1136 | "cardinality": 5, 1137 | "color": null, 1138 | "elems": [ 1139 | 1, 1140 | 3, 1141 | 0, 1142 | 9, 1143 | 4 1144 | ], 1145 | "name": "two", 1146 | "type": "set" 1147 | } 1148 | ], 1149 | "stroke_color": null, 1150 | "text_color": null, 1151 | "theme": "light", 1152 | "title": null, 1153 | "value": null, 1154 | "value_text_color": null 1155 | } 1156 | }, 1157 | "eea479bcd1f14791a2fdbc117603e77b": { 1158 | "model_module": "@jupyter-widgets/base", 1159 | "model_module_version": "2.0.0", 1160 | "model_name": "LayoutModel", 1161 | "state": { 1162 | "align_self": "stretch", 1163 | "height": "400px" 1164 | } 1165 | }, 1166 | "f855e1e4722b48eabc2cf978408a3877": { 1167 | "model_module": "@jupyter-widgets/base", 1168 | "model_module_version": "2.0.0", 1169 | "model_name": "LayoutModel", 1170 | "state": { 1171 | "align_self": "stretch", 1172 | "height": "400px" 1173 | } 1174 | } 1175 | }, 1176 | "version_major": 2, 1177 | "version_minor": 0 1178 | } 1179 | } 1180 | }, 1181 | "nbformat": 4, 1182 | "nbformat_minor": 4 1183 | } 1184 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformIgnorePatterns: [], // transform all 3 | preset: 'ts-jest/presets/js-with-ts', 4 | testRegex: 'src/.*\\.spec\\.(ts|tsx)$', 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: './tsconfig.test.json', 8 | babelConfig: false, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@upsetjs/jupyter_widget", 3 | "version": "1.9.0", 4 | "description": "UpSet.js is a re-implementation of UpSetR to create interactive set visualizations for more than three sets", 5 | "license": "SEE LICENSE IN LICENSE.txt", 6 | "author": { 7 | "name": "Samuel Gratzl", 8 | "email": "sam@sgratzl.com", 9 | "url": "https://wwww.sgratzl.com" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "keywords": [ 15 | "jupyter", 16 | "jupyterlab", 17 | "juptyerlab notebook", 18 | "jupyterlab-extension", 19 | "widgets" 20 | ], 21 | "homepage": "https://github.com/upsetjs/upsetjs_jupyter_widget", 22 | "bugs": { 23 | "url": "https://github.com/upsetjs/upsetjs_jupyter_widget/issues" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/upsetjs/upsetjs_jupyter_widget" 28 | }, 29 | "files": [ 30 | "lib/**/*.js", 31 | "dist/*.js", 32 | "css/*.css" 33 | ], 34 | "main": "lib/index.js", 35 | "types": "./lib/index.d.ts", 36 | "scripts": { 37 | "clean": "rimraf dist build lib upsetjs_jupyter_widget/labextension \"upsetjs_jupyter_widget/nbextension/static/index*\"", 38 | "build": "yarn run build:lib && yarn run build:nbextension", 39 | "build:labextension": "mkdirp upsetjs_jupyter_widget/labextension && cd upsetjs_jupyter_widget/labextension && npm pack ../..", 40 | "build:lib": "yarn run clean && tsc -p tsconfig.build.json", 41 | "build:nbextension": "webpack --mode production", 42 | "build:all": "yarn run build:lib && yarn run build:labextension && yarn run build:nbextension", 43 | "build:p": "python setup.py clean sdist bdist_wheel", 44 | "_prepack": "yarn run build:lib", 45 | "lint": "yarn run eslint && yarn run prettier:check", 46 | "fix": "yarn run eslint:fix && yarn run prettier:write", 47 | "prettier": "prettier \"*.{md,json,yml}\" .eslintrc.js webpack* .prettierrc.js \"{src,types,scripts,.github}/**\" \"{r_package,binder}/*.yml\"", 48 | "prettier:write": "yarn run prettier --write", 49 | "prettier:check": "yarn run prettier --check", 50 | "black": "black --target-version=py38 upsetjs_jupyter_widget setup.py setupbase.py", 51 | "eslint": "eslint src --ext .ts,.tsx", 52 | "eslint:fix": "yarn run eslint --fix", 53 | "lint:p": "mypy -p upsetjs_jupyter_widget && yarn run black --check && pylint upsetjs_jupyter_widget", 54 | "fix:p": "yarn run black", 55 | "test:p": "pytest", 56 | "test:p:ci": "pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html", 57 | "nbconvert": "jupyter nbconvert examples/*.ipynb --to html", 58 | "test": "jest --passWithNoTests", 59 | "watch": "npm-run-all -p 'watch:*'", 60 | "watch:lib": "tsc -w -p tsconfig.build.json", 61 | "watch:nbextension": "webpack -w --mode development --devtool sourcemap", 62 | "docs:p": "cd docs && sphinx-build -b html \"source\" \"build\"", 63 | "release": "release-it --disable-metrics --npm.skipChecks" 64 | }, 65 | "dependencies": { 66 | "@jupyter-widgets/base": ">=2", 67 | "@upsetjs/bundle": "~1.9.1", 68 | "@upsetjs/venn.js": "^1.4.1" 69 | }, 70 | "devDependencies": { 71 | "@jupyterlab/services": "^6.0.3", 72 | "@phosphor/application": "^1.7.3", 73 | "@phosphor/widgets": "^1.9.3", 74 | "@types/backbone": "^1.4.10", 75 | "@types/jest": "^26.0.20", 76 | "@typescript-eslint/eslint-plugin": "^4.15.1", 77 | "@typescript-eslint/parser": "^4.15.1", 78 | "@yarnpkg/pnpify": "^2.4.0", 79 | "babel-eslint": "^10.1.0", 80 | "backbone": "^1.4.0", 81 | "eslint": "^7.20.0", 82 | "eslint-config-prettier": "^7.2.0", 83 | "eslint-config-react-app": "^6.0.0", 84 | "eslint-plugin-flowtype": "^5.2.0", 85 | "eslint-plugin-import": "^2.22.1", 86 | "eslint-plugin-jsx-a11y": "^6.4.1", 87 | "eslint-plugin-prettier": "^3.3.1", 88 | "eslint-plugin-react": "^7.22.0", 89 | "eslint-plugin-react-hooks": "^4.2.0", 90 | "jest": "^26.6.3", 91 | "jest-config": "^26.6.3", 92 | "mkdirp": "^1.0.4", 93 | "npm-run-all": "^4.1.5", 94 | "pnp-webpack-plugin": "^1.6.4", 95 | "prettier": "^2.2.1", 96 | "release-it": "^14.4.0", 97 | "rimraf": "^3.0.2", 98 | "ts-jest": "26.5.1", 99 | "ts-loader": "^8.0.17", 100 | "tslib": "^2.1.0", 101 | "typescript": "^4.1.5", 102 | "webpack": "^5.22.0", 103 | "webpack-cli": "^4.5.0" 104 | }, 105 | "jupyterlab": { 106 | "extension": "lib/plugin" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = upsetjs_jupyter_widget/tests examples 3 | norecursedirs = node_modules .ipynb_checkpoints 4 | -------------------------------------------------------------------------------- /scripts/bump.js: -------------------------------------------------------------------------------- 1 | const { Plugin } = require('release-it'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | function bumpFile(file, reg, replace) { 6 | console.log('bumping', file); 7 | const desc = path.resolve(file); 8 | const content = fs.readFileSync(desc).toString(); 9 | const newContent = content.replace(reg, replace); 10 | fs.writeFileSync(desc, newContent); 11 | } 12 | 13 | function bumpImpl(version) { 14 | bumpFile('./upsetjs_jupyter_widget/_frontend.py', /^MODULE_VERSION.*$/gm, `MODULE_VERSION = "^${version}"`); 15 | bumpFile( 16 | './upsetjs_jupyter_widget/_version.py', 17 | /^version_info =.*$/gm, 18 | `version_info = (${version.split('.').join(', ')}) # pylint: disable=C0103` 19 | ); 20 | bumpFile('./src/version.ts', /^export const MODULE_VERSION = .*$/gm, `export const MODULE_VERSION = '${version}';`); 21 | } 22 | 23 | class MyVersionPlugin extends Plugin { 24 | bump(version) { 25 | bumpImpl(version); 26 | } 27 | } 28 | 29 | module.exports = MyVersionPlugin; 30 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description-file = README.md 6 | license_file = LICENSE.txt 7 | 8 | [mypy-pytest.*] 9 | ignore_missing_imports = True 10 | 11 | [mypy-ipykernel.*] 12 | ignore_missing_imports = True 13 | 14 | [mypy-ipywidgets.*] 15 | ignore_missing_imports = True 16 | 17 | [mypy-traitlets.*] 18 | ignore_missing_imports = True 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | 5 | from __future__ import print_function 6 | from glob import glob 7 | import json 8 | from os.path import join as pjoin 9 | 10 | 11 | from setupbase import ( 12 | create_cmdclass, 13 | install_npm, 14 | ensure_targets, 15 | find_packages, 16 | combine_commands, 17 | ensure_python, 18 | HERE, 19 | ) 20 | 21 | from setuptools import setup 22 | 23 | 24 | # The name of the project 25 | name = "upsetjs_jupyter_widget" 26 | 27 | with open("README.md") as readme_file: 28 | readme = readme_file.read() 29 | 30 | # Ensure a valid python version 31 | ensure_python(">=3.6") 32 | 33 | with open("package.json") as pkg_file: 34 | pkg = json.load(pkg_file) 35 | 36 | nb_path = pjoin(HERE, name, "nbextension", "static") 37 | lab_path = pjoin(HERE, name, "labextension") 38 | 39 | # Representative files that should exist after a successful build 40 | jstargets = [ 41 | pjoin(nb_path, "index.js"), 42 | pjoin(HERE, "lib", "plugin.js"), 43 | ] 44 | 45 | package_data_spec = {name: ["nbextension/static/*.*js*", "labextension/*.tgz"]} 46 | 47 | data_files_spec = [ 48 | ("share/jupyter/nbextensions/@upsetjs/jupyter_widget", nb_path, "*.js*"), 49 | ("share/jupyter/lab/extensions", lab_path, "*.tgz"), 50 | ("etc/jupyter/nbconfig/notebook.d", HERE, "upsetjs_jupyter_widget.json"), 51 | ] 52 | 53 | cmdclass = create_cmdclass( 54 | "jsdeps", package_data_spec=package_data_spec, data_files_spec=data_files_spec 55 | ) 56 | cmdclass["jsdeps"] = combine_commands( 57 | install_npm(HERE, build_cmd="build:all"), 58 | ensure_targets(jstargets), 59 | ) 60 | 61 | 62 | if __name__ == "__main__": 63 | setup( 64 | name=name, 65 | description=pkg["description"], 66 | long_description=readme, 67 | long_description_content_type="text/markdown", 68 | version=pkg["version"], 69 | scripts=glob(pjoin("scripts", "*")), 70 | cmdclass=cmdclass, 71 | packages=find_packages(), 72 | author=pkg["author"]["name"], 73 | author_email=pkg["author"]["email"], 74 | url=pkg["homepage"], 75 | platforms="Linux, Mac OS X, Windows", 76 | keywords=["Jupyter", "Widgets", "IPython"], 77 | classifiers=[ 78 | "Development Status :: 4 - Beta", 79 | "Intended Audience :: Developers", 80 | "Intended Audience :: Science/Research", 81 | "Intended Audience :: System Administrators", 82 | "License :: Free For Home Use", 83 | "License :: Free for non-commercial use", 84 | "License :: Free For Educational Use", 85 | "Programming Language :: Python", 86 | "Programming Language :: Python :: 3", 87 | "Programming Language :: Python :: 3.6", 88 | "Programming Language :: Python :: 3.7", 89 | "Programming Language :: Python :: 3.8", 90 | "Framework :: IPython", 91 | "Framework :: Jupyter", 92 | "Topic :: Scientific/Engineering :: Information Analysis", 93 | "Topic :: Scientific/Engineering :: Visualization", 94 | "Typing :: Typed", 95 | ], 96 | include_package_data=True, 97 | install_requires=[ 98 | "ipywidgets>=7.5.0", 99 | ], 100 | extras_require={ 101 | "test": [ 102 | "pytest>=3.6", 103 | "pytest-cov", 104 | "nbval", 105 | ], 106 | "examples": [ 107 | # Any requirements for the examples to run 108 | ], 109 | "docs": [ 110 | "sphinx>=1.5", 111 | "recommonmark", 112 | "sphinx_rtd_theme", 113 | "nbsphinx>=0.2.13,<0.4.0", 114 | "jupyter_sphinx", 115 | "nbsphinx-link", 116 | "pytest_check_links", 117 | "pypandoc", 118 | ], 119 | }, 120 | entry_points={}, 121 | ) 122 | -------------------------------------------------------------------------------- /setupbase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) Jupyter Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | """ 8 | This file originates from the 'jupyter-packaging' package, and 9 | contains a set of useful utilities for including npm packages 10 | within a Python package. 11 | """ 12 | from collections import defaultdict 13 | from os.path import join as pjoin 14 | import io 15 | import os 16 | import functools 17 | import pipes 18 | import re 19 | import shlex 20 | import subprocess 21 | import sys 22 | 23 | 24 | # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly 25 | # update it when the contents of directories change. 26 | if os.path.exists("MANIFEST"): 27 | os.remove("MANIFEST") 28 | 29 | 30 | from distutils.cmd import Command 31 | from distutils.command.build_py import build_py 32 | from distutils.command.sdist import sdist 33 | from distutils import log 34 | 35 | from setuptools.command.develop import develop 36 | from setuptools.command.bdist_egg import bdist_egg 37 | 38 | try: 39 | from wheel.bdist_wheel import bdist_wheel 40 | except ImportError: 41 | bdist_wheel = None 42 | 43 | if sys.platform == "win32": 44 | from subprocess import list2cmdline 45 | else: 46 | 47 | def list2cmdline(cmd_list): 48 | return " ".join(map(pipes.quote, cmd_list)) 49 | 50 | 51 | __version__ = "0.2.0" 52 | 53 | # --------------------------------------------------------------------------- 54 | # Top Level Variables 55 | # --------------------------------------------------------------------------- 56 | 57 | HERE = os.path.abspath(os.path.dirname(__file__)) 58 | is_repo = os.path.exists(pjoin(HERE, ".git")) 59 | node_modules = pjoin(HERE, "node_modules") 60 | 61 | SEPARATORS = os.sep if os.altsep is None else os.sep + os.altsep 62 | 63 | npm_path = ":".join( 64 | [ 65 | pjoin(HERE, "node_modules", ".bin"), 66 | os.environ.get("PATH", os.defpath), 67 | ] 68 | ) 69 | 70 | if "--skip-npm" in sys.argv: 71 | print("Skipping npm install as requested.") 72 | skip_npm = True 73 | sys.argv.remove("--skip-npm") 74 | else: 75 | skip_npm = False 76 | 77 | 78 | # --------------------------------------------------------------------------- 79 | # Public Functions 80 | # --------------------------------------------------------------------------- 81 | 82 | 83 | def get_version(file, name="__version__"): 84 | """Get the version of the package from the given file by 85 | executing it and extracting the given `name`. 86 | """ 87 | path = os.path.realpath(file) 88 | version_ns = {} 89 | with io.open(path, encoding="utf8") as f: 90 | exec(f.read(), {}, version_ns) 91 | return version_ns[name] 92 | 93 | 94 | def ensure_python(specs): 95 | """Given a list of range specifiers for python, ensure compatibility.""" 96 | if not isinstance(specs, (list, tuple)): 97 | specs = [specs] 98 | v = sys.version_info 99 | part = "%s.%s" % (v.major, v.minor) 100 | for spec in specs: 101 | if part == spec: 102 | return 103 | try: 104 | if eval(part + spec): 105 | return 106 | except SyntaxError: 107 | pass 108 | raise ValueError("Python version %s unsupported" % part) 109 | 110 | 111 | def find_packages(top=HERE): 112 | """ 113 | Find all of the packages. 114 | """ 115 | packages = [] 116 | for d, dirs, _ in os.walk(top, followlinks=True): 117 | if os.path.exists(pjoin(d, "__init__.py")): 118 | packages.append(os.path.relpath(d, top).replace(os.path.sep, ".")) 119 | elif d != top: 120 | # Do not look for packages in subfolders if current is not a package 121 | dirs[:] = [] 122 | return packages 123 | 124 | 125 | def update_package_data(distribution): 126 | """update build_py options to get package_data changes""" 127 | build_py = distribution.get_command_obj("build_py") 128 | build_py.finalize_options() 129 | 130 | 131 | class bdist_egg_disabled(bdist_egg): 132 | """Disabled version of bdist_egg 133 | 134 | Prevents setup.py install performing setuptools' default easy_install, 135 | which it should never ever do. 136 | """ 137 | 138 | def run(self): 139 | sys.exit( 140 | "Aborting implicit building of eggs. Use `pip install .` " 141 | " to install from source." 142 | ) 143 | 144 | 145 | def create_cmdclass(prerelease_cmd=None, package_data_spec=None, data_files_spec=None): 146 | """Create a command class with the given optional prerelease class. 147 | 148 | Parameters 149 | ---------- 150 | prerelease_cmd: (name, Command) tuple, optional 151 | The command to run before releasing. 152 | package_data_spec: dict, optional 153 | A dictionary whose keys are the dotted package names and 154 | whose values are a list of glob patterns. 155 | data_files_spec: list, optional 156 | A list of (path, dname, pattern) tuples where the path is the 157 | `data_files` install path, dname is the source directory, and the 158 | pattern is a glob pattern. 159 | 160 | Notes 161 | ----- 162 | We use specs so that we can find the files *after* the build 163 | command has run. 164 | 165 | The package data glob patterns should be relative paths from the package 166 | folder containing the __init__.py file, which is given as the package 167 | name. 168 | e.g. `dict(foo=['./bar/*', './baz/**'])` 169 | 170 | The data files directories should be absolute paths or relative paths 171 | from the root directory of the repository. Data files are specified 172 | differently from `package_data` because we need a separate path entry 173 | for each nested folder in `data_files`, and this makes it easier to 174 | parse. 175 | e.g. `('share/foo/bar', 'pkgname/bizz, '*')` 176 | """ 177 | wrapped = [prerelease_cmd] if prerelease_cmd else [] 178 | if package_data_spec or data_files_spec: 179 | wrapped.append("handle_files") 180 | wrapper = functools.partial(_wrap_command, wrapped) 181 | handle_files = _get_file_handler(package_data_spec, data_files_spec) 182 | 183 | if "bdist_egg" in sys.argv: 184 | egg = wrapper(bdist_egg, strict=True) 185 | else: 186 | egg = bdist_egg_disabled 187 | 188 | cmdclass = dict( 189 | build_py=wrapper(build_py, strict=is_repo), 190 | bdist_egg=egg, 191 | sdist=wrapper(sdist, strict=True), 192 | handle_files=handle_files, 193 | ) 194 | 195 | if bdist_wheel: 196 | cmdclass["bdist_wheel"] = wrapper(bdist_wheel, strict=True) 197 | 198 | cmdclass["develop"] = wrapper(develop, strict=True) 199 | return cmdclass 200 | 201 | 202 | def command_for_func(func): 203 | """Create a command that calls the given function.""" 204 | 205 | class FuncCommand(BaseCommand): 206 | def run(self): 207 | func() 208 | update_package_data(self.distribution) 209 | 210 | return FuncCommand 211 | 212 | 213 | def run(cmd, **kwargs): 214 | """Echo a command before running it. Defaults to repo as cwd""" 215 | log.info("> " + list2cmdline(cmd)) 216 | kwargs.setdefault("cwd", HERE) 217 | kwargs.setdefault("shell", os.name == "nt") 218 | if not isinstance(cmd, (list, tuple)) and os.name != "nt": 219 | cmd = shlex.split(cmd) 220 | cmd_path = which(cmd[0]) 221 | if not cmd_path: 222 | sys.exit( 223 | "Aborting. Could not find cmd (%s) in path. " 224 | "If command is not expected to be in user's path, " 225 | "use an absolute path." % cmd[0] 226 | ) 227 | cmd[0] = cmd_path 228 | return subprocess.check_call(cmd, **kwargs) 229 | 230 | 231 | def is_stale(target, source): 232 | """Test whether the target file/directory is stale based on the source 233 | file/directory. 234 | """ 235 | if not os.path.exists(target): 236 | return True 237 | target_mtime = recursive_mtime(target) or 0 238 | return compare_recursive_mtime(source, cutoff=target_mtime) 239 | 240 | 241 | class BaseCommand(Command): 242 | """Empty command because Command needs subclasses to override too much""" 243 | 244 | user_options = [] 245 | 246 | def initialize_options(self): 247 | pass 248 | 249 | def finalize_options(self): 250 | pass 251 | 252 | def get_inputs(self): 253 | return [] 254 | 255 | def get_outputs(self): 256 | return [] 257 | 258 | 259 | def combine_commands(*commands): 260 | """Return a Command that combines several commands.""" 261 | 262 | class CombinedCommand(Command): 263 | user_options = [] 264 | 265 | def initialize_options(self): 266 | self.commands = [] 267 | for C in commands: 268 | self.commands.append(C(self.distribution)) 269 | for c in self.commands: 270 | c.initialize_options() 271 | 272 | def finalize_options(self): 273 | for c in self.commands: 274 | c.finalize_options() 275 | 276 | def run(self): 277 | for c in self.commands: 278 | c.run() 279 | 280 | return CombinedCommand 281 | 282 | 283 | def compare_recursive_mtime(path, cutoff, newest=True): 284 | """Compare the newest/oldest mtime for all files in a directory. 285 | 286 | Cutoff should be another mtime to be compared against. If an mtime that is 287 | newer/older than the cutoff is found it will return True. 288 | E.g. if newest=True, and a file in path is newer than the cutoff, it will 289 | return True. 290 | """ 291 | if os.path.isfile(path): 292 | mt = mtime(path) 293 | if newest: 294 | if mt > cutoff: 295 | return True 296 | elif mt < cutoff: 297 | return True 298 | for dirname, _, filenames in os.walk(path, topdown=False): 299 | for filename in filenames: 300 | mt = mtime(pjoin(dirname, filename)) 301 | if newest: # Put outside of loop? 302 | if mt > cutoff: 303 | return True 304 | elif mt < cutoff: 305 | return True 306 | return False 307 | 308 | 309 | def recursive_mtime(path, newest=True): 310 | """Gets the newest/oldest mtime for all files in a directory.""" 311 | if os.path.isfile(path): 312 | return mtime(path) 313 | current_extreme = None 314 | for dirname, dirnames, filenames in os.walk(path, topdown=False): 315 | for filename in filenames: 316 | mt = mtime(pjoin(dirname, filename)) 317 | if newest: # Put outside of loop? 318 | if mt >= (current_extreme or mt): 319 | current_extreme = mt 320 | elif mt <= (current_extreme or mt): 321 | current_extreme = mt 322 | return current_extreme 323 | 324 | 325 | def mtime(path): 326 | """shorthand for mtime""" 327 | return os.stat(path).st_mtime 328 | 329 | 330 | def install_npm( 331 | path=None, build_dir=None, source_dir=None, build_cmd="build", force=False, npm=None 332 | ): 333 | """Return a Command for managing an npm installation. 334 | 335 | Note: The command is skipped if the `--skip-npm` flag is used. 336 | 337 | Parameters 338 | ---------- 339 | path: str, optional 340 | The base path of the node package. Defaults to the repo root. 341 | build_dir: str, optional 342 | The target build directory. If this and source_dir are given, 343 | the JavaScript will only be build if necessary. 344 | source_dir: str, optional 345 | The source code directory. 346 | build_cmd: str, optional 347 | The npm command to build assets to the build_dir. 348 | npm: str or list, optional. 349 | The npm executable name, or a tuple of ['node', executable]. 350 | """ 351 | 352 | class NPM(BaseCommand): 353 | description = "install package.json dependencies using npm" 354 | 355 | def run(self): 356 | if skip_npm: 357 | log.info("Skipping npm-installation") 358 | return 359 | node_package = path or HERE 360 | node_modules = pjoin(node_package, "node_modules") 361 | is_yarn = os.path.exists(pjoin(node_package, "yarn.lock")) 362 | 363 | npm_cmd = npm 364 | 365 | if npm is None: 366 | if is_yarn: 367 | npm_cmd = ["yarn"] 368 | else: 369 | npm_cmd = ["npm"] 370 | 371 | if not which(npm_cmd[0]): 372 | log.error( 373 | "`{0}` unavailable. If you're running this command " 374 | "using sudo, make sure `{0}` is available to sudo".format( 375 | npm_cmd[0] 376 | ) 377 | ) 378 | return 379 | 380 | if force or is_stale(node_modules, pjoin(node_package, "package.json")): 381 | log.info( 382 | "Installing build dependencies with npm. This may " 383 | "take a while..." 384 | ) 385 | run(npm_cmd + ["install"], cwd=node_package) 386 | if build_dir and source_dir and not force: 387 | should_build = is_stale(build_dir, source_dir) 388 | else: 389 | should_build = True 390 | if should_build: 391 | run(npm_cmd + ["run", build_cmd], cwd=node_package) 392 | 393 | return NPM 394 | 395 | 396 | def ensure_targets(targets): 397 | """Return a Command that checks that certain files exist. 398 | 399 | Raises a ValueError if any of the files are missing. 400 | 401 | Note: The check is skipped if the `--skip-npm` flag is used. 402 | """ 403 | 404 | class TargetsCheck(BaseCommand): 405 | def run(self): 406 | if skip_npm: 407 | log.info("Skipping target checks") 408 | return 409 | missing = [t for t in targets if not os.path.exists(t)] 410 | if missing: 411 | raise ValueError(("missing files: %s" % missing)) 412 | 413 | return TargetsCheck 414 | 415 | 416 | # `shutils.which` function copied verbatim from the Python-3.3 source. 417 | def which(cmd, mode=os.F_OK | os.X_OK, path=None): 418 | """Given a command, mode, and a PATH string, return the path which 419 | conforms to the given mode on the PATH, or None if there is no such 420 | file. 421 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result 422 | of os.environ.get("PATH"), or can be overridden with a custom search 423 | path. 424 | """ 425 | 426 | # Check that a given file can be accessed with the correct mode. 427 | # Additionally check that `file` is not a directory, as on Windows 428 | # directories pass the os.access check. 429 | def _access_check(fn, mode): 430 | return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) 431 | 432 | # Short circuit. If we're given a full path which matches the mode 433 | # and it exists, we're done here. 434 | if _access_check(cmd, mode): 435 | return cmd 436 | 437 | path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep) 438 | 439 | if sys.platform == "win32": 440 | # The current directory takes precedence on Windows. 441 | if os.curdir not in path: 442 | path.insert(0, os.curdir) 443 | 444 | # PATHEXT is necessary to check on Windows. 445 | pathext = os.environ.get("PATHEXT", "").split(os.pathsep) 446 | # See if the given file matches any of the expected path extensions. 447 | # This will allow us to short circuit when given "python.exe". 448 | matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())] 449 | # If it does match, only test that one, otherwise we have to try 450 | # others. 451 | files = [cmd] if matches else [cmd + ext.lower() for ext in pathext] 452 | else: 453 | # On other platforms you don't have things like PATHEXT to tell you 454 | # what file suffixes are executable, so just pass on cmd as-is. 455 | files = [cmd] 456 | 457 | seen = set() 458 | for dir in path: 459 | dir = os.path.normcase(dir) 460 | if dir not in seen: 461 | seen.add(dir) 462 | for thefile in files: 463 | name = os.path.join(dir, thefile) 464 | if _access_check(name, mode): 465 | return name 466 | return None 467 | 468 | 469 | # --------------------------------------------------------------------------- 470 | # Private Functions 471 | # --------------------------------------------------------------------------- 472 | 473 | 474 | def _wrap_command(cmds, cls, strict=True): 475 | """Wrap a setup command 476 | 477 | Parameters 478 | ---------- 479 | cmds: list(str) 480 | The names of the other commands to run prior to the command. 481 | strict: boolean, optional 482 | Wether to raise errors when a pre-command fails. 483 | """ 484 | 485 | class WrappedCommand(cls): 486 | def run(self): 487 | if not getattr(self, "uninstall", None): 488 | try: 489 | [self.run_command(cmd) for cmd in cmds] 490 | except Exception: 491 | if strict: 492 | raise 493 | else: 494 | pass 495 | # update package data 496 | update_package_data(self.distribution) 497 | 498 | result = cls.run(self) 499 | return result 500 | 501 | return WrappedCommand 502 | 503 | 504 | def _get_file_handler(package_data_spec, data_files_spec): 505 | """Get a package_data and data_files handler command.""" 506 | 507 | class FileHandler(BaseCommand): 508 | def run(self): 509 | package_data = self.distribution.package_data 510 | package_spec = package_data_spec or dict() 511 | 512 | for (key, patterns) in package_spec.items(): 513 | package_data[key] = _get_package_data(key, patterns) 514 | 515 | self.distribution.data_files = _get_data_files( 516 | data_files_spec, self.distribution.data_files 517 | ) 518 | 519 | return FileHandler 520 | 521 | 522 | def _glob_pjoin(*parts): 523 | """Join paths for glob processing""" 524 | if parts[0] in (".", ""): 525 | parts = parts[1:] 526 | return pjoin(*parts).replace(os.sep, "/") 527 | 528 | 529 | def _get_data_files(data_specs, existing, top=HERE): 530 | """Expand data file specs into valid data files metadata. 531 | 532 | Parameters 533 | ---------- 534 | data_specs: list of tuples 535 | See [create_cmdclass] for description. 536 | existing: list of tuples 537 | The existing distrubution data_files metadata. 538 | 539 | Returns 540 | ------- 541 | A valid list of data_files items. 542 | """ 543 | # Extract the existing data files into a staging object. 544 | file_data = defaultdict(list) 545 | for (path, files) in existing or []: 546 | file_data[path] = files 547 | 548 | # Extract the files and assign them to the proper data 549 | # files path. 550 | for (path, dname, pattern) in data_specs or []: 551 | if os.path.isabs(dname): 552 | dname = os.path.relpath(dname, top) 553 | dname = dname.replace(os.sep, "/") 554 | offset = 0 if dname in (".", "") else len(dname) + 1 555 | files = _get_files(_glob_pjoin(dname, pattern), top=top) 556 | for fname in files: 557 | # Normalize the path. 558 | root = os.path.dirname(fname) 559 | full_path = _glob_pjoin(path, root[offset:]) 560 | print(dname, root, full_path, offset) 561 | if full_path.endswith("/"): 562 | full_path = full_path[:-1] 563 | file_data[full_path].append(fname) 564 | 565 | # Construct the data files spec. 566 | data_files = [] 567 | for (path, files) in file_data.items(): 568 | data_files.append((path, files)) 569 | return data_files 570 | 571 | 572 | def _get_files(file_patterns, top=HERE): 573 | """Expand file patterns to a list of paths. 574 | 575 | Parameters 576 | ----------- 577 | file_patterns: list or str 578 | A list of glob patterns for the data file locations. 579 | The globs can be recursive if they include a `**`. 580 | They should be relative paths from the top directory or 581 | absolute paths. 582 | top: str 583 | the directory to consider for data files 584 | 585 | Note: 586 | Files in `node_modules` are ignored. 587 | """ 588 | if not isinstance(file_patterns, (list, tuple)): 589 | file_patterns = [file_patterns] 590 | 591 | for i, p in enumerate(file_patterns): 592 | if os.path.isabs(p): 593 | file_patterns[i] = os.path.relpath(p, top) 594 | 595 | matchers = [_compile_pattern(p) for p in file_patterns] 596 | 597 | files = set() 598 | 599 | for root, dirnames, filenames in os.walk(top): 600 | # Don't recurse into node_modules 601 | if "node_modules" in dirnames: 602 | dirnames.remove("node_modules") 603 | for m in matchers: 604 | for filename in filenames: 605 | fn = os.path.relpath(_glob_pjoin(root, filename), top) 606 | fn = fn.replace(os.sep, "/") 607 | if m(fn): 608 | files.add(fn.replace(os.sep, "/")) 609 | 610 | return list(files) 611 | 612 | 613 | def _get_package_data(root, file_patterns=None): 614 | """Expand file patterns to a list of `package_data` paths. 615 | 616 | Parameters 617 | ----------- 618 | root: str 619 | The relative path to the package root from `HERE`. 620 | file_patterns: list or str, optional 621 | A list of glob patterns for the data file locations. 622 | The globs can be recursive if they include a `**`. 623 | They should be relative paths from the root or 624 | absolute paths. If not given, all files will be used. 625 | 626 | Note: 627 | Files in `node_modules` are ignored. 628 | """ 629 | if file_patterns is None: 630 | file_patterns = ["*"] 631 | return _get_files(file_patterns, _glob_pjoin(HERE, root)) 632 | 633 | 634 | def _compile_pattern(pat, ignore_case=True): 635 | """Translate and compile a glob pattern to a regular expression matcher.""" 636 | if isinstance(pat, bytes): 637 | pat_str = pat.decode("ISO-8859-1") 638 | res_str = _translate_glob(pat_str) 639 | res = res_str.encode("ISO-8859-1") 640 | else: 641 | res = _translate_glob(pat) 642 | flags = re.IGNORECASE if ignore_case else 0 643 | return re.compile(res, flags=flags).match 644 | 645 | 646 | def _iexplode_path(path): 647 | """Iterate over all the parts of a path. 648 | 649 | Splits path recursively with os.path.split(). 650 | """ 651 | (head, tail) = os.path.split(path) 652 | if not head or (not tail and head == path): 653 | if head: 654 | yield head 655 | if tail or not head: 656 | yield tail 657 | return 658 | for p in _iexplode_path(head): 659 | yield p 660 | yield tail 661 | 662 | 663 | def _translate_glob(pat): 664 | """Translate a glob PATTERN to a regular expression.""" 665 | translated_parts = [] 666 | for part in _iexplode_path(pat): 667 | translated_parts.append(_translate_glob_part(part)) 668 | os_sep_class = "[%s]" % re.escape(SEPARATORS) 669 | res = _join_translated(translated_parts, os_sep_class) 670 | return "{res}\\Z(?ms)".format(res=res) 671 | 672 | 673 | def _join_translated(translated_parts, os_sep_class): 674 | """Join translated glob pattern parts. 675 | 676 | This is different from a simple join, as care need to be taken 677 | to allow ** to match ZERO or more directories. 678 | """ 679 | res = "" 680 | for part in translated_parts[:-1]: 681 | if part == ".*": 682 | # drop separator, since it is optional 683 | # (** matches ZERO or more dirs) 684 | res += part 685 | else: 686 | res += part + os_sep_class 687 | 688 | if translated_parts[-1] == ".*": 689 | # Final part is ** 690 | res += ".+" 691 | # Follow stdlib/git convention of matching all sub files/directories: 692 | res += "({os_sep_class}?.*)?".format(os_sep_class=os_sep_class) 693 | else: 694 | res += translated_parts[-1] 695 | return res 696 | 697 | 698 | def _translate_glob_part(pat): 699 | """Translate a glob PATTERN PART to a regular expression.""" 700 | # Code modified from Python 3 standard lib fnmatch: 701 | if pat == "**": 702 | return ".*" 703 | i, n = 0, len(pat) 704 | res = [] 705 | while i < n: 706 | c = pat[i] 707 | i = i + 1 708 | if c == "*": 709 | # Match anything but path separators: 710 | res.append("[^%s]*" % SEPARATORS) 711 | elif c == "?": 712 | res.append("[^%s]?" % SEPARATORS) 713 | elif c == "[": 714 | j = i 715 | if j < n and pat[j] == "!": 716 | j = j + 1 717 | if j < n and pat[j] == "]": 718 | j = j + 1 719 | while j < n and pat[j] != "]": 720 | j = j + 1 721 | if j >= n: 722 | res.append("\\[") 723 | else: 724 | stuff = pat[i:j].replace("\\", "\\\\") 725 | i = j + 1 726 | if stuff[0] == "!": 727 | stuff = "^" + stuff[1:] 728 | elif stuff[0] == "^": 729 | stuff = "\\" + stuff 730 | res.append("[%s]" % stuff) 731 | else: 732 | res.append(re.escape(c)) 733 | return "".join(res) 734 | -------------------------------------------------------------------------------- /src/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | // Copyright (c) Jupyter Development Team. 9 | // Distributed under the terms of the Modified BSD License. 10 | 11 | import * as widgets from '@jupyter-widgets/base'; 12 | import * as services from '@jupyterlab/services'; 13 | import * as Backbone from 'backbone'; 14 | 15 | let numComms = 0; 16 | 17 | export class MockComm { 18 | target_name = 'dummy'; 19 | 20 | constructor() { 21 | this.comm_id = `mock-comm-id-${numComms}`; 22 | numComms += 1; 23 | } 24 | on_close(fn: Function | null) { 25 | this._on_close = fn; 26 | } 27 | on_msg(fn: Function | null) { 28 | this._on_msg = fn; 29 | } 30 | _process_msg(msg: services.KernelMessage.ICommMsgMsg) { 31 | if (this._on_msg) { 32 | return this._on_msg(msg); 33 | } else { 34 | return Promise.resolve(); 35 | } 36 | } 37 | close(): string { 38 | if (this._on_close) { 39 | this._on_close(); 40 | } 41 | return 'dummy'; 42 | } 43 | send(): string { 44 | return 'dummy'; 45 | } 46 | 47 | open(): string { 48 | return 'dummy'; 49 | } 50 | comm_id: string; 51 | _on_msg: Function | null = null; 52 | _on_close: Function | null = null; 53 | } 54 | 55 | export class DummyManager { 56 | constructor() { 57 | this.el = window.document.createElement('div'); 58 | } 59 | 60 | display_view(_msg: services.KernelMessage.IMessage, view: Backbone.View, _options: any) { 61 | // TODO: make this a spy 62 | // TODO: return an html element 63 | return Promise.resolve(view).then((view) => { 64 | this.el.appendChild(view.el); 65 | view.on('remove', () => console.log('view removed', view)); 66 | return view.el; 67 | }); 68 | } 69 | 70 | protected loadClass(className: string, moduleName: string, _moduleVersion: string): Promise { 71 | if (moduleName === '@jupyter-widgets/base') { 72 | if ((widgets as any)[className]) { 73 | return Promise.resolve((widgets as any)[className]); 74 | } else { 75 | return Promise.reject(`Cannot find class ${className}`); 76 | } 77 | } else if (moduleName === 'jupyter-datawidgets') { 78 | if (this.testClasses[className]) { 79 | return Promise.resolve(this.testClasses[className]); 80 | } else { 81 | return Promise.reject(`Cannot find class ${className}`); 82 | } 83 | } else { 84 | return Promise.reject(`Cannot find module ${moduleName}`); 85 | } 86 | } 87 | 88 | _get_comm_info() { 89 | return Promise.resolve({}); 90 | } 91 | 92 | _create_comm() { 93 | return Promise.resolve(new MockComm()); 94 | } 95 | 96 | el: HTMLElement; 97 | 98 | testClasses: { [key: string]: any } = {}; 99 | } 100 | 101 | export interface Constructor { 102 | new (attributes?: any, options?: any): T; 103 | } 104 | 105 | export function createTestModel(constructor: Constructor, attributes?: any): T { 106 | let id = widgets.uuid(); 107 | let widget_manager = new DummyManager(); 108 | let modelOptions = { 109 | widget_manager: widget_manager, 110 | model_id: id, 111 | }; 112 | 113 | return new constructor(attributes, modelOptions); 114 | } 115 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | // Entry point for the notebook bundle containing custom model definitions. 9 | // 10 | // Setup notebook base URL 11 | // 12 | // Some static assets may be required by the custom widget javascript. The base 13 | // url for the notebook is not known at build time and is therefore computed 14 | // dynamically. 15 | (window as any).__webpack_public_path__ = 16 | document.querySelector('body')!.getAttribute('data-base-url') + 'nbextensions/upsetjs_jupyter_widget'; 17 | 18 | export * from './index'; 19 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | // import // Add any needed widget imports here (or from controls) 9 | // '@jupyter-widgets/base'; 10 | 11 | import { createTestModel } from './__tests__/utils'; 12 | 13 | import { UpSetModel } from '.'; 14 | 15 | describe('UpSet', () => { 16 | describe('UpSetModel', () => { 17 | it('should be create able', () => { 18 | let model = createTestModel(UpSetModel); 19 | expect(model).toBeInstanceOf(UpSetModel); 20 | expect(model.get('value')).toBeUndefined(); 21 | }); 22 | 23 | // it('should be create able with a value', () => { 24 | // let state = { value: 'Foo Bar!' }; 25 | // let model = createTestModel(UpSetModel, state); 26 | // expect(model).toBeInstanceOf(UpSetModel); 27 | // expect(model.get('value')).toBe('Foo Bar!'); 28 | // }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | export * from './version'; 9 | export * from './widget'; 10 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | import { Application, IPlugin } from '@phosphor/application'; 9 | import { Widget } from '@phosphor/widgets'; 10 | import { IJupyterWidgetRegistry } from '@jupyter-widgets/base'; 11 | import * as widgetExports from './widget'; 12 | import { MODULE_NAME, MODULE_VERSION } from './version'; 13 | 14 | const EXTENSION_ID = '@upsetjs/jupyter_widget:plugin'; 15 | 16 | /** 17 | * The example plugin. 18 | */ 19 | const upsetjsPlugin: IPlugin, void> = { 20 | id: EXTENSION_ID, 21 | requires: [IJupyterWidgetRegistry as any], 22 | activate: activateWidgetExtension, 23 | autoStart: true, 24 | }; 25 | 26 | export default upsetjsPlugin; 27 | 28 | /** 29 | * Activate the widget extension. 30 | */ 31 | function activateWidgetExtension(_app: Application, registry: IJupyterWidgetRegistry): void { 32 | registry.registerWidget({ 33 | name: MODULE_NAME, 34 | version: MODULE_VERSION, 35 | exports: widgetExports, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | import { 9 | asSets, 10 | asCombinations, 11 | generateCombinations, 12 | GenerateSetCombinationsOptions, 13 | ISetCombinations, 14 | ISets, 15 | fromIndicesArray, 16 | SetCombinationType, 17 | extractFromExpression, 18 | } from '@upsetjs/bundle'; 19 | 20 | export interface IElem { 21 | name: string; 22 | attrs: { [key: string]: number | string }; 23 | } 24 | 25 | export function fixSets(sets: ISets, elems: IElem[]) { 26 | if (!sets) { 27 | return []; 28 | } 29 | return asSets( 30 | sets.map((set) => 31 | Object.assign({}, set, { 32 | elems: fromIndicesArray(set.elems, elems), 33 | }) 34 | ) 35 | ); 36 | } 37 | 38 | export function fromExpression( 39 | combinations: { name: string; cardinality: number; set_names: string[]; type: SetCombinationType }[] 40 | ) { 41 | const type = combinations[0].type; 42 | return extractFromExpression( 43 | combinations.map((set) => ({ 44 | name: set.name, 45 | set_names: set.set_names, 46 | cardinality: set.cardinality, 47 | })), 48 | (c) => c.set_names, 49 | { 50 | type, 51 | } 52 | ); 53 | } 54 | 55 | export function fixCombinations( 56 | combinations: GenerateSetCombinationsOptions | ISetCombinations | undefined, 57 | sets: ISets, 58 | elems: IElem[] 59 | ) { 60 | if (!combinations) { 61 | return null; 62 | } 63 | if (!Array.isArray(combinations)) { 64 | return generateCombinations( 65 | sets, 66 | Object.assign( 67 | { 68 | elems, 69 | }, 70 | combinations as GenerateSetCombinationsOptions 71 | ) 72 | ); 73 | } 74 | const lookup = new Map(sets.map((s) => [s.name, s])); 75 | return asCombinations( 76 | combinations.map((set) => 77 | Object.assign({}, set, { 78 | elems: fromIndicesArray(set.elems, elems), 79 | }) 80 | ), 81 | 'composite', 82 | (s: any) => s.set_names.map((si: string) => lookup.get(si)).filter(Boolean) 83 | ); 84 | } 85 | 86 | export function resolveSet( 87 | set: number[] | { name: string; type: string }, 88 | sets: ISets, 89 | combinations: ISetCombinations, 90 | elems: IElem[] 91 | ) { 92 | if (Array.isArray(set)) { 93 | return set.map((i) => elems[i]); 94 | } 95 | const ss: { name: string; type: string } = set; 96 | if (!ss.type || ss.type === 'set') { 97 | const s = sets.find((s) => s.name === ss.name); 98 | if (s) { 99 | return s; 100 | } 101 | } 102 | return combinations.find((c) => c.name === ss.name); 103 | } 104 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | /** 9 | * The _model_module_version/_view_module_version this package implements. 10 | * 11 | * The html widget manager assumes that this is the same as the npm package 12 | * version number. 13 | */ 14 | export const MODULE_VERSION = '1.9.0'; 15 | 16 | /* 17 | * The current package name. 18 | */ 19 | export const MODULE_NAME = '@upsetjs/jupyter_widget'; 20 | -------------------------------------------------------------------------------- /src/widget.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | import { DOMWidgetModel, DOMWidgetView, ISerializers } from '@jupyter-widgets/base'; 9 | 10 | import { MODULE_VERSION, MODULE_NAME } from './version'; 11 | import { 12 | isElemQuery, 13 | ISetCombinations, 14 | ISetLike, 15 | isSetQuery, 16 | render, 17 | UpSetProps, 18 | UpSetQuery, 19 | boxplotAddon, 20 | fromIndicesArray, 21 | VennDiagramProps, 22 | renderVennDiagram, 23 | renderKarnaughMap, 24 | KarnaughMapProps, 25 | categoricalAddon, 26 | createVennJSAdapter, 27 | ISets, 28 | } from '@upsetjs/bundle'; 29 | import { layout } from '@upsetjs/venn.js'; 30 | import { fixCombinations, fixSets, resolveSet, IElem, fromExpression } from './utils'; 31 | 32 | const adapter = createVennJSAdapter(layout); 33 | 34 | export class UpSetModel extends DOMWidgetModel { 35 | defaults() { 36 | return { 37 | ...super.defaults(), 38 | _model_name: UpSetModel.model_name, 39 | _model_module: UpSetModel.model_module, 40 | _model_module_version: UpSetModel.model_module_version, 41 | _view_name: UpSetModel.view_name, 42 | _view_module: UpSetModel.view_module, 43 | _view_module_version: UpSetModel.view_module_version, 44 | }; 45 | } 46 | 47 | static readonly serializers: ISerializers = { 48 | ...DOMWidgetModel.serializers, 49 | // Add any extra serializers here 50 | }; 51 | 52 | static readonly model_name = 'UpSetModel'; 53 | static readonly model_module = MODULE_NAME; 54 | static readonly model_module_version = MODULE_VERSION; 55 | static readonly view_name = 'UpSetView'; // Set to null if no view 56 | static readonly view_module = MODULE_NAME; // Set to null if no view 57 | static readonly view_module_version = MODULE_VERSION; 58 | } 59 | 60 | declare type UpSetNumericAttrSpec = { 61 | type: 'number'; 62 | name: string; 63 | domain: [number, number]; 64 | values: ReadonlyArray; 65 | elems?: ReadonlyArray; 66 | }; 67 | declare type UpSetCategoricalAttrSpec = { 68 | type: 'categorical'; 69 | name: string; 70 | categories: ReadonlyArray; 71 | values: ReadonlyArray; 72 | elems?: ReadonlyArray; 73 | }; 74 | 75 | declare type UpSetAttrSpec = UpSetNumericAttrSpec | UpSetCategoricalAttrSpec; 76 | 77 | export class UpSetView extends DOMWidgetView { 78 | private props: UpSetProps & VennDiagramProps = { 79 | sets: [], 80 | width: 300, 81 | height: 300, 82 | }; 83 | private elems: IElem[] = []; 84 | private readonly elemToIndex = new Map(); 85 | private attrs: UpSetAttrSpec[] = []; 86 | 87 | render() { 88 | this.model.on('change', this.changed_prop, this); 89 | 90 | this.updateProps(this.stateToProps()); 91 | } 92 | 93 | private changeSelection = (s: ISetLike | null) => { 94 | this.model.off('change', this.changed_prop, null); 95 | if (!s) { 96 | this.model.set('value', null); 97 | } else { 98 | const setLike: any = { 99 | name: s.name, 100 | type: s.type, 101 | cardinality: s.cardinality, 102 | elems: s.elems.map((e) => this.elemToIndex.get(e)), 103 | }; 104 | if (s.type !== 'set') { 105 | setLike.degree = s.degree; 106 | setLike.set_names = Array.from(s.sets).map((s) => s.name); 107 | } 108 | this.model.set('value', setLike); 109 | } 110 | this.props.selection = s; 111 | this.renderImpl(); 112 | this.touch(); 113 | this.model.on('change', this.changed_prop, this); 114 | }; 115 | 116 | private stateToProps(): Partial & VennDiagramProps & KarnaughMapProps> { 117 | const props: Partial & VennDiagramProps & KarnaughMapProps> = {}; 118 | const state = this.model.get_state(true); 119 | 120 | const toCamelCase = (v: string) => v.replace(/([-_]\w)/g, (g) => g[1].toUpperCase()); 121 | 122 | const toPropName = (key: string) => { 123 | if (key === 'value') { 124 | return 'selection'; 125 | } 126 | return toCamelCase(key); 127 | }; 128 | 129 | Object.keys(state).forEach((key) => { 130 | let v = state[key] as any; 131 | if (v == null || (Array.isArray(v) && v.length === 0) || key.startsWith('_') || key === 'layout') { 132 | return; 133 | } 134 | const propName = toPropName(key); 135 | if (propName === 'fontSizes') { 136 | const converted: any = {}; 137 | Object.keys(v).forEach((key: string) => { 138 | const vi = (v as any)![key]; 139 | if (vi != null) { 140 | converted[toCamelCase(key)] = typeof vi === 'number' ? `${vi}px` : String(vi); 141 | } 142 | }); 143 | v = converted; 144 | } 145 | (props as any)[propName] = v; 146 | }); 147 | return props; 148 | } 149 | 150 | private updateProps(delta: any) { 151 | if (delta) { 152 | Object.assign(this.props, delta); 153 | } 154 | this.fixProps(delta); 155 | this.renderImpl(); 156 | } 157 | 158 | private syncAddons() { 159 | if (this.attrs.length === 0) { 160 | delete this.props.setAddons; 161 | delete this.props.combinationAddons; 162 | return; 163 | } 164 | const toAddon = (attr: UpSetAttrSpec, vertical = false) => { 165 | if (attr.type === 'number') { 166 | return boxplotAddon( 167 | (v) => v.attrs[attr.name] as number, 168 | { min: attr.domain[0], max: attr.domain[1] }, 169 | { 170 | name: attr.name, 171 | quantiles: 'hinges', 172 | orient: vertical ? 'vertical' : 'horizontal', 173 | } 174 | ); 175 | } 176 | return categoricalAddon( 177 | (v) => v.attrs[attr.name] as string, 178 | { 179 | categories: attr.categories, 180 | }, 181 | { 182 | name: attr.name, 183 | orient: vertical ? 'vertical' : 'horizontal', 184 | } 185 | ); 186 | }; 187 | this.props.setAddons = this.attrs.map((attr) => toAddon(attr, false)); 188 | this.props.combinationAddons = this.attrs.map((attr) => toAddon(attr, true)); 189 | } 190 | 191 | private fixProps(delta: any) { 192 | const props = this.props; 193 | if (delta.elems) 194 | if (delta.elems != null) { 195 | this.attrs = delta.attrs ?? this.attrs; 196 | const lookups = this.attrs.map((attr) => (attr.elems ? new Map(attr.elems.map((e, i) => [e, i])) : null)); 197 | this.elems = (delta.elems as any[]).map((name, i) => { 198 | const attrs: any = {}; 199 | this.attrs.forEach( 200 | (attr, j) => (attrs[attr.name] = attr.values[lookups[j] ? lookups[j]!.get(name) ?? i : i]) 201 | ); 202 | return { name, attrs }; 203 | }); 204 | this.elemToIndex.clear(); 205 | this.elems.forEach((e, i) => this.elemToIndex.set(e, i)); 206 | this.syncAddons(); 207 | } else if (delta.attrs != null) { 208 | // only attrs same elems 209 | this.attrs = delta.attrs; 210 | const lookups = this.attrs.map((attr) => (attr.elems ? new Map(attr.elems.map((e, i) => [e, i])) : null)); 211 | this.elems.forEach((elem, i) => { 212 | const attrs: any = {}; 213 | this.attrs.forEach( 214 | (attr, j) => (attrs[attr.name] = attr.values[lookups[j] ? lookups[j]!.get(elem.name) ?? i : i]) 215 | ); 216 | elem.attrs = attrs; 217 | }); 218 | this.syncAddons(); 219 | } 220 | delete (this.props as any).elems; 221 | delete (this.props as any).attrs; 222 | 223 | if (delta.sets != null) { 224 | props.sets = fixSets(delta.sets, this.elems); 225 | } 226 | if (delta.combinations != null) { 227 | if (this.model.get('_expression_data')) { 228 | const r = fromExpression(delta.combinations); 229 | props.combinations = r.combinations as ISetCombinations; 230 | props.sets = r.sets as ISets; 231 | } else { 232 | const c = fixCombinations(delta.combinations, props.sets, this.elems); 233 | if (c == null) { 234 | delete props.combinations; 235 | } else { 236 | props.combinations = c; 237 | } 238 | } 239 | } 240 | if (delta.selection) { 241 | props.selection = resolveSet( 242 | delta.selection, 243 | props.sets ?? [], 244 | (props.combinations ?? []) as ISetCombinations, 245 | this.elems 246 | ); 247 | } 248 | if (delta.queries) { 249 | props.queries = delta.queries.map((query: UpSetQuery) => { 250 | if (isSetQuery(query)) { 251 | return Object.assign({}, query, { 252 | set: resolveSet( 253 | query.set, 254 | props.sets ?? [], 255 | (props.combinations ?? []) as ISetCombinations, 256 | this.elems 257 | )!, 258 | }); 259 | } else if (isElemQuery(query)) { 260 | return Object.assign({}, query, { 261 | elems: fromIndicesArray(query.elems as any[], this.elems), 262 | }); 263 | } 264 | return query; 265 | }); 266 | } 267 | 268 | delete props.onHover; 269 | delete props.onClick; 270 | delete props.onContextMenu; 271 | if (this.model.get('mode') === 'click') { 272 | props.onClick = this.changeSelection; 273 | } else if (this.model.get('mode') === 'hover') { 274 | props.onHover = this.changeSelection; 275 | } else if (this.model.get('mode') === 'contextMenu') { 276 | props.onContextMenu = this.changeSelection; 277 | } 278 | } 279 | 280 | private changed_prop(model: any) { 281 | this.updateProps(model.changed); 282 | } 283 | 284 | private renderImpl() { 285 | const bb = this.el.getBoundingClientRect(); 286 | const p = Object.assign({}, this.props); 287 | const renderMode = this.model.get('_render_mode'); 288 | 289 | const renderComponent = () => { 290 | if (renderMode === 'venn') { 291 | delete p.layout; 292 | renderVennDiagram(this.el, p); 293 | } else if (renderMode === 'euler') { 294 | p.layout = adapter; 295 | renderVennDiagram(this.el, p); 296 | } else if (renderMode === 'kmap') { 297 | renderKarnaughMap(this.el, p); 298 | } else { 299 | render(this.el, p); 300 | } 301 | }; 302 | 303 | if (!bb.width || !bb.height) { 304 | requestAnimationFrame(() => { 305 | const bb2 = this.el.getBoundingClientRect(); 306 | p.width = bb2.width || 600; 307 | p.height = bb2.height || 400; 308 | renderComponent(); 309 | }); 310 | return; 311 | } 312 | p.width = bb.width; 313 | p.height = bb.height; 314 | renderComponent(); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "sourceMap": true, 8 | "outDir": "lib", 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "resolveJsonModule": true, 21 | "moduleResolution": "node", 22 | "jsx": "react", 23 | "esModuleInterop": true, 24 | "rootDir": "src", 25 | "skipLibCheck": true 26 | }, 27 | "include": ["src/**/*.ts", "src/**/*.tsx"] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | // don't know why need explicit typeroots but otherwise it fails 5 | "typeRoots": ["node_modules/@types"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "module": "commonjs", 6 | "allowJs": true, 7 | "target": "es5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "@upsetjs/jupyter_widget/extension": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | UpSet.js Jupyter Widget 9 | """ 10 | 11 | from .widget import ( 12 | UpSetJSWidget, 13 | UpSetJSVennDiagramWidget, 14 | UpSetJSEulerDiagramWidget, 15 | UpSetJSKarnaughMapWidget, 16 | ) 17 | from ._model import ( 18 | UpSetBaseSet, 19 | UpSetQuery, 20 | UpSetSet, 21 | UpSetSetCombination, 22 | UpSetSetComposite, 23 | UpSetSetDistinctIntersection, 24 | UpSetSetIntersection, 25 | UpSetSetLike, 26 | UpSetSetType, 27 | UpSetSetUnion, 28 | ) 29 | from ._version import __version__, version_info 30 | 31 | from .nbextension import _jupyter_nbextension_paths 32 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/_array.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | helper function for UpSet 9 | """ 10 | import typing as t 11 | 12 | 13 | def compress_index_array(indices: t.Iterable[int]) -> t.Union[t.List[int], str]: 14 | """ 15 | compresses an indices array similar to a python range expression 16 | """ 17 | elems: t.List[int] = list(indices) 18 | if len(elems) <= 1: 19 | return elems 20 | 21 | encoded: t.List[str] = [] 22 | 23 | sub_slice: t.List[int] = [] 24 | 25 | def push(): 26 | if len(sub_slice) == 1: 27 | encoded.append(str(sub_slice[0])) 28 | elif len(sub_slice) == 2 and sub_slice[-1] < 10: 29 | encoded.append(str(sub_slice[0])) 30 | encoded.append(str(sub_slice[1])) 31 | else: 32 | encoded.append(f"{sub_slice[0]}+{len(sub_slice) - 1}") 33 | return [] 34 | 35 | for j, index in enumerate(elems): 36 | if j > 0: 37 | expected = sub_slice[-1] + 1 38 | if index != expected: 39 | # slice break 40 | sub_slice = push() 41 | sub_slice.append(index) 42 | 43 | push() 44 | 45 | compressed = ",".join(encoded) 46 | 47 | if len(compressed) < len(elems) * 0.6: 48 | return compressed 49 | return elems 50 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/_frontend.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | Information about the frontend package of the widgets. 9 | """ 10 | 11 | MODULE_NAME = "@upsetjs/jupyter_widget" 12 | MODULE_VERSION = "^1.9.0" 13 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/_generate.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | generate set intersection helper 9 | """ 10 | import typing as t 11 | from itertools import combinations 12 | from ._model import ( 13 | T, 14 | UpSetSet, 15 | UpSetSetIntersection, 16 | UpSetSetUnion, 17 | UpSetSetDistinctIntersection, 18 | ) 19 | 20 | 21 | def generate_intersections( 22 | sets: t.Sequence[UpSetSet[T]], 23 | min_degree: int = 0, 24 | max_degree: t.Optional[int] = None, 25 | empty: bool = False, 26 | elems: t.Optional[t.List[T]] = None, 27 | colors: t.Mapping[str, str] = None, 28 | ) -> t.List[UpSetSetIntersection[T]]: 29 | """ 30 | generate intersections 31 | """ 32 | color_lookup = colors or {} 33 | 34 | def compute_intersection(combo: t.List[UpSetSet[T]]): 35 | if len(combo) == 0: 36 | if not elems: 37 | return [] 38 | return {e for e in elems if all(e not in c.elems for c in sets)} 39 | if len(combo) == 1: 40 | return combo[0].elems 41 | first = combo[0].elems 42 | for uset in combo: 43 | first = first.intersection(uset.elems) 44 | return first 45 | 46 | set_intersections: t.List[UpSetSetIntersection[T]] = [] 47 | max_degree = max_degree or len(sets) 48 | for i in range(min_degree, max_degree + 1): 49 | for combo in combinations(sets, i): 50 | set_list = list(combo) 51 | degree = len(set_list) 52 | intersection = compute_intersection(set_list) 53 | if len(intersection) == 0 and not empty: 54 | continue 55 | name = ( 56 | set_list[0].name 57 | if degree == 1 58 | else f"({' ∩ '.join(c.name for c in set_list)})" 59 | ) 60 | set_intersections.append( 61 | UpSetSetIntersection[T]( 62 | name, 63 | frozenset(intersection), 64 | sets=frozenset(set_list), 65 | color=color_lookup.get( 66 | name, color_lookup.get("&".join(s.name for s in set_list)) 67 | ), 68 | ) 69 | ) 70 | return set_intersections 71 | 72 | 73 | def generate_distinct_intersections( 74 | sets: t.Sequence[UpSetSet[T]], 75 | min_degree: int = 0, 76 | max_degree: t.Optional[int] = None, 77 | empty: bool = False, 78 | elems: t.Optional[t.List[T]] = None, 79 | colors: t.Mapping[str, str] = None, 80 | ) -> t.List[UpSetSetDistinctIntersection[T]]: 81 | """ 82 | generate distinct intersections 83 | """ 84 | set_intersections = generate_intersections( 85 | sets, min_degree, max_degree, empty, elems, colors=colors 86 | ) 87 | 88 | def remove_others( 89 | elems: t.FrozenSet[T], contained_sets: t.FrozenSet[UpSetSet[T]] 90 | ) -> t.List[T]: 91 | others = [s for s in sets if s not in contained_sets] 92 | return [e for e in elems if all(e not in o.elems for o in others)] 93 | 94 | return [ 95 | UpSetSetDistinctIntersection[T]( 96 | s.name, 97 | frozenset(remove_others(s.elems, s.sets)), 98 | sets=s.sets, 99 | color=s.color, 100 | ) 101 | for s in set_intersections 102 | ] 103 | 104 | 105 | def generate_unions( 106 | sets: t.Sequence[UpSetSet[T]], 107 | min_degree: int = 0, 108 | max_degree: t.Optional[int] = None, 109 | empty: bool = False, 110 | elems: t.Optional[t.List[T]] = None, 111 | colors: t.Mapping[str, str] = None, 112 | ) -> t.List[UpSetSetUnion[T]]: 113 | """ 114 | generate unions 115 | """ 116 | color_lookup = colors or {} 117 | 118 | def compute_union(combo: t.List[UpSetSet[T]]): 119 | if len(combo) == 0: 120 | return elems 121 | if len(combo) == 1: 122 | return combo[0].elems 123 | first = combo[0].elems 124 | for uset in combo: 125 | first = first.union(uset.elems) 126 | return first 127 | 128 | set_unions: t.List[UpSetSetUnion[T]] = [] 129 | for i in range(min_degree, len(sets) if max_degree is None else (max_degree + 1)): 130 | for combo in combinations(sets, i): 131 | set_list = list(combo) 132 | degree = len(set_list) 133 | union = compute_union(set_list) 134 | if len(union) == 0 and not empty: 135 | continue 136 | name = ( 137 | set_list[0].name 138 | if degree == 1 139 | else f"({' ∪ '.join(c.name for c in set_list)})" 140 | ) 141 | set_unions.append( 142 | UpSetSetUnion[T]( 143 | name, 144 | frozenset(union), 145 | sets=frozenset(set_list), 146 | color=color_lookup.get( 147 | name, color_lookup.get("&".join(s.name for s in set_list)) 148 | ), 149 | ) 150 | ) 151 | return set_unions 152 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/_model.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | model definitions for UpSet 9 | """ 10 | import typing as t 11 | from enum import Enum 12 | 13 | T = t.TypeVar("T") # pylint: disable=invalid-name 14 | 15 | 16 | class UpSetSetType(Enum): 17 | """ 18 | enum of which type the set is 19 | """ 20 | 21 | SET = ("set",) 22 | INTERSECTION = "intersection" 23 | DISTINCT_INTERSECTION = "distinctIntersection" 24 | UNION = "union" 25 | COMPOSITE = "composite" 26 | 27 | def __str__(self): 28 | if self == UpSetSetType.SET: 29 | return "set" 30 | if self == UpSetSetType.INTERSECTION: 31 | return "intersection" 32 | if self == UpSetSetType.DISTINCT_INTERSECTION: 33 | return "distinctIntersection" 34 | if self == UpSetSetType.UNION: 35 | return "union" 36 | return "composite" 37 | 38 | 39 | class UpSetBaseSet(t.Generic[T]): 40 | """ 41 | a set base class 42 | """ 43 | 44 | set_type: UpSetSetType 45 | name: str 46 | color: t.Optional[str] 47 | elems: t.FrozenSet[T] 48 | cardinality: int 49 | 50 | def __init__( 51 | self, 52 | set_type: UpSetSetType, 53 | name: str, 54 | elems: t.Optional[t.FrozenSet[T]] = None, 55 | color: t.Optional[str] = None, 56 | cardinality: t.Optional[int] = None, 57 | ): 58 | super().__init__() 59 | self.set_type = set_type 60 | self.name = name 61 | self.color = color 62 | self.elems = elems or frozenset() 63 | self.cardinality = cardinality or len(self.elems) 64 | 65 | 66 | class UpSetSet(UpSetBaseSet[T]): 67 | """ 68 | a set representation within UpSet 69 | """ 70 | 71 | def __init__( 72 | self, 73 | name: str = "", 74 | elems: t.Optional[t.FrozenSet[T]] = None, 75 | color: t.Optional[str] = None, 76 | cardinality: t.Optional[int] = None, 77 | ): 78 | super().__init__(UpSetSetType.SET, name, elems, color, cardinality) 79 | 80 | @property 81 | def degree(self): 82 | """ 83 | the degree of this set, i.e., the number of contained sets 84 | """ 85 | return 1 86 | 87 | def __repr__(self): 88 | return f"UpSetSet(name={self.name}, cardinality={self.cardinality}, elems={set(self.elems)})" 89 | 90 | 91 | class UpSetSetCombination(UpSetBaseSet[T]): 92 | """ 93 | a set combination within UpSet.js like an intersection 94 | """ 95 | 96 | sets: t.FrozenSet[UpSetSet[T]] 97 | """ 98 | the set of UpSetSets this set combination is composed of 99 | """ 100 | 101 | def __init__( 102 | self, 103 | set_type: UpSetSetType, 104 | name: str = "", 105 | elems: t.Optional[t.FrozenSet[T]] = None, 106 | sets: t.Optional[t.FrozenSet[UpSetSet[T]]] = None, 107 | color: t.Optional[str] = None, 108 | cardinality: t.Optional[int] = None, 109 | ): 110 | super().__init__(set_type, name, elems, color, cardinality) 111 | self.sets = sets or frozenset() 112 | 113 | @property 114 | def degree(self): 115 | """ 116 | the degree of this set, i.e., the number of contained sets 117 | """ 118 | return len(self.sets) 119 | 120 | def __repr__(self): 121 | set_names = {s.name for s in self.sets} 122 | return f"{self.__class__.__name__}(name={self.name}, sets={set_names}, cardinality={self.cardinality}, elems={set(self.elems)})" 123 | 124 | 125 | class UpSetSetIntersection(UpSetSetCombination[T]): 126 | """ 127 | a set intersection representation in UpSet 128 | """ 129 | 130 | def __init__( 131 | self, 132 | name: str = "", 133 | elems: t.Optional[t.FrozenSet[T]] = None, 134 | sets: t.Optional[t.FrozenSet[UpSetSet[T]]] = None, 135 | color: t.Optional[str] = None, 136 | cardinality: t.Optional[int] = None, 137 | ): 138 | super().__init__( 139 | UpSetSetType.INTERSECTION, name, elems, sets, color, cardinality 140 | ) 141 | 142 | 143 | class UpSetSetDistinctIntersection(UpSetSetCombination[T]): 144 | """ 145 | a distinct set intersection representation in UpSet 146 | """ 147 | 148 | def __init__( 149 | self, 150 | name: str = "", 151 | elems: t.Optional[t.FrozenSet[T]] = None, 152 | sets: t.Optional[t.FrozenSet[UpSetSet[T]]] = None, 153 | color: t.Optional[str] = None, 154 | cardinality: t.Optional[int] = None, 155 | ): 156 | super().__init__( 157 | UpSetSetType.DISTINCT_INTERSECTION, name, elems, sets, color, cardinality 158 | ) 159 | 160 | 161 | class UpSetSetUnion(UpSetSetCombination[T]): 162 | """ 163 | a set union representation in UpSet 164 | """ 165 | 166 | def __init__( 167 | self, 168 | name: str = "", 169 | elems: t.Optional[t.FrozenSet[T]] = None, 170 | sets: t.Optional[t.FrozenSet[UpSetSet[T]]] = None, 171 | color: t.Optional[str] = None, 172 | cardinality: t.Optional[int] = None, 173 | ): 174 | super().__init__(UpSetSetType.UNION, name, elems, sets, color, cardinality) 175 | 176 | 177 | class UpSetSetComposite(UpSetSetCombination[T]): 178 | """ 179 | a set composite representation in UpSet 180 | """ 181 | 182 | def __init__( 183 | self, 184 | name: str = "", 185 | elems: t.Optional[t.FrozenSet[T]] = None, 186 | sets: t.Optional[t.FrozenSet[UpSetSet[T]]] = None, 187 | color: t.Optional[str] = None, 188 | cardinality: t.Optional[int] = None, 189 | ): 190 | super().__init__(UpSetSetType.COMPOSITE, name, elems, sets, color, cardinality) 191 | 192 | 193 | UpSetSetLike = t.Union[ 194 | UpSetSet[T], UpSetSetIntersection[T], UpSetSetUnion[T], UpSetSetComposite[T] 195 | ] 196 | 197 | 198 | class UpSetQuery(t.Generic[T]): 199 | """ 200 | a query representation in UpSet 201 | """ 202 | 203 | name: str 204 | color: str 205 | set: t.Optional[UpSetSetLike[T]] 206 | elems: t.Optional[t.FrozenSet[T]] 207 | 208 | def __init__( 209 | self, 210 | name: str, 211 | color: str, 212 | upset: t.Optional[UpSetSetLike[T]] = None, 213 | elems: t.Optional[t.FrozenSet[T]] = None, 214 | ): 215 | super().__init__() 216 | self.type = type 217 | self.name = name 218 | self.color = color 219 | self.set = upset 220 | self.elems = elems 221 | 222 | def __repr__(self): 223 | if self.set: 224 | return ( 225 | f"UpSetSetQuery(name={self.name}, color={self.color}, set={self.set})" 226 | ) 227 | return f"UpSetSetQuery(name={self.name}, color={self.color}, elems={set(self.elems)})" 228 | 229 | 230 | class UpSetFontSizes: 231 | """ 232 | helper structure for specifying font sizes 233 | """ 234 | 235 | chart_label: t.Union[None, str, int] = None 236 | axis_tick: t.Union[None, str, int] = None 237 | set_label: t.Union[None, str, int] = None 238 | bar_label: t.Union[None, str, int] = None 239 | export_label: t.Union[None, str, int] = None 240 | value_label: t.Union[None, str, int] = None 241 | legend: t.Union[None, str, int] = None 242 | title: t.Union[None, str, int] = None 243 | description: t.Union[None, str, int] = None 244 | 245 | def __init__(self, **kwargs): 246 | super().__init__() 247 | self.__dict__.update(kwargs) 248 | 249 | def __copy__(self): 250 | return UpSetFontSizes(**self.__dict__) 251 | 252 | def copy(self): 253 | """ 254 | returns a copy / clone of this instance 255 | """ 256 | return UpSetFontSizes(**self.__dict__) 257 | 258 | def to_json(self): 259 | """ 260 | converts this instance to a regular dictionary for transfering 261 | """ 262 | return self.__dict__ 263 | 264 | 265 | class UpSetAttribute(t.Generic[T]): 266 | """ 267 | helper structure for specifying font sizes 268 | """ 269 | 270 | attr_type: str 271 | name: str 272 | domain: t.Optional[t.Tuple[float, float]] 273 | categories: t.Optional[t.List[t.Union[str, t.Dict]]] 274 | elems: t.Optional[t.List[T]] 275 | values: t.Union[t.List[str], t.List[float]] 276 | 277 | def __init__( 278 | self, 279 | attr_type: str, 280 | name: str, 281 | values: t.Union[t.List[str], t.List[float]], 282 | domain: t.Optional[t.Tuple[float, float]] = None, 283 | categories: t.Optional[t.List[t.Union[str, t.Dict]]] = None, 284 | elems: t.Optional[t.List[T]] = None, 285 | ): 286 | super().__init__() 287 | self.attr_type = attr_type 288 | self.name = name 289 | self.values = values 290 | self.domain = domain 291 | self.categories = categories 292 | self.elems = elems 293 | 294 | def to_json(self): 295 | """ 296 | converts this instance to a regular dictionary for transfering 297 | """ 298 | base = dict( 299 | type=self.attr_type, values=self.values, name=self.name, elems=self.elems 300 | ) 301 | if self.attr_type == "number": 302 | base["domain"] = self.domain 303 | else: 304 | base["categories"] = self.categories 305 | return base 306 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/_version.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | UpSet.js Jupyter Widget 9 | """ 10 | 11 | version_info = (1, 9, 0) # pylint: disable=C0103 12 | __version__ = ".".join(map(str, version_info)) 13 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/nbextension/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | UpSet.js Jupyter Widget 9 | """ 10 | 11 | 12 | def _jupyter_nbextension_paths(): 13 | return [ 14 | { 15 | "section": "notebook", 16 | "src": "nbextension/static", 17 | "dest": "@upsetjs/jupyter_widget", 18 | "require": "@upsetjs/jupyter_widget/extension", 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/nbextension/static/extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @upsetjs/jupyter_widget 3 | * https://github.com/upsetjs/upsetjs_jupyter_widget 4 | * 5 | * Copyright (c) 2021 Samuel Gratzl 6 | */ 7 | 8 | // Entry point for the notebook bundle containing custom model definitions. 9 | // 10 | define(function () { 11 | 'use strict'; 12 | 13 | window['requirejs'].config({ 14 | map: { 15 | '*': { 16 | '@upsetjs/jupyter_widget': 'nbextensions/@upsetjs/jupyter_widget/index', 17 | }, 18 | }, 19 | }); 20 | // Export the required load_ipython_extension function 21 | return { 22 | load_ipython_extension: function () { }, 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | # coding: utf-8 8 | # Copyright (c) Samuel Gratzl. 9 | """ 10 | test suit 11 | """ 12 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | # coding: utf-8 8 | # Copyright (c) Samuel Gratzl. 9 | # pylint: disable=W0221,C0103,C0116,W0212,signature-differs 10 | """ 11 | test case 12 | """ 13 | 14 | 15 | import pytest 16 | 17 | from ipykernel.comm import Comm # type: ignore 18 | from ipywidgets import Widget # type: ignore 19 | 20 | 21 | class MockComm(Comm): 22 | """A mock Comm object. 23 | 24 | Can be used to inspect calls to Comm's open/send/close methods. 25 | """ 26 | 27 | comm_id = "a-b-c-d" 28 | kernel = "Truthy" 29 | 30 | def __init__(self, *args, **kwargs): 31 | """ 32 | test case 33 | """ 34 | self.log_open = [] 35 | self.log_send = [] 36 | self.log_close = [] 37 | super().__init__(*args, **kwargs) 38 | 39 | def open(self, *args, **kwargs): 40 | """ 41 | test case 42 | """ 43 | self.log_open.append((args, kwargs)) 44 | 45 | def send(self, *args, **kwargs): 46 | """ 47 | test case 48 | """ 49 | self.log_send.append((args, kwargs)) 50 | 51 | def close(self, *args, **kwargs): 52 | """ 53 | test case 54 | """ 55 | self.log_close.append((args, kwargs)) 56 | 57 | 58 | _widget_attrs = {} 59 | undefined = object() 60 | 61 | 62 | @pytest.fixture 63 | def mock_comm(): 64 | _widget_attrs["_comm_default"] = getattr(Widget, "_comm_default", undefined) 65 | Widget._comm_default = lambda self: MockComm() 66 | _widget_attrs["_ipython_display_"] = Widget._ipython_display_ 67 | 68 | def raise_not_implemented(*args, **kwargs): 69 | raise NotImplementedError() 70 | 71 | Widget._ipython_display_ = raise_not_implemented 72 | 73 | yield MockComm() 74 | 75 | for attr, value in _widget_attrs.items(): 76 | if value is undefined: 77 | delattr(Widget, attr) 78 | else: 79 | setattr(Widget, attr, value) 80 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/tests/test_nbextension_path.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | # coding: utf-8 8 | # Copyright (c) Samuel Gratzl. 9 | """ 10 | test case 11 | """ 12 | 13 | 14 | def test_nbextension_path(): 15 | """ 16 | test case 17 | """ 18 | # Check that magic function can be imported from package root: 19 | # pylint: disable=C0415 20 | from upsetjs_jupyter_widget import _jupyter_nbextension_paths 21 | 22 | # Ensure that it can be called without incident: 23 | path = _jupyter_nbextension_paths() 24 | # Some sanity checks: 25 | assert len(path) == 1 26 | assert isinstance(path[0], dict) 27 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/tests/test_widget.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | # coding: utf-8 8 | # Copyright (c) Samuel Gratzl. 9 | """ 10 | test case 11 | """ 12 | 13 | from ..widget import UpSetJSWidget # pylint: disable=C0415 14 | 15 | 16 | def test_upset_creation_blank(): 17 | """ 18 | test case 19 | """ 20 | widget = UpSetJSWidget() 21 | assert widget.value is None 22 | -------------------------------------------------------------------------------- /upsetjs_jupyter_widget/widget.py: -------------------------------------------------------------------------------- 1 | # coding: utf- 8 2 | # @upsetjs/jupyter_widget 3 | # https://github.com/upsetjs/upsetjs_jupyter_widget 4 | # 5 | # Copyright (c) 2021 Samuel Gratzl 6 | 7 | """ 8 | UpSet.js Jupyter Widget 9 | """ 10 | 11 | import typing as t 12 | 13 | from ipywidgets import DOMWidget, Layout, ValueWidget, register # type: ignore 14 | from ipywidgets.widgets.trait_types import Color # type: ignore 15 | from traitlets import ( # type: ignore 16 | Bool, 17 | Dict, 18 | Enum, 19 | Float, 20 | Int, 21 | List, 22 | Tuple, 23 | Unicode, 24 | Union, 25 | default, 26 | Instance, 27 | ) 28 | 29 | from ._frontend import MODULE_NAME, MODULE_VERSION 30 | from ._model import ( 31 | T, 32 | UpSetQuery, 33 | UpSetSet, 34 | UpSetSetType, 35 | UpSetSetCombination, 36 | UpSetSetComposite, 37 | UpSetSetIntersection, 38 | UpSetSetDistinctIntersection, 39 | UpSetSetLike, 40 | UpSetSetUnion, 41 | UpSetFontSizes, 42 | UpSetAttribute, 43 | ) 44 | from ._generate import ( 45 | generate_unions, 46 | generate_intersections, 47 | generate_distinct_intersections, 48 | ) 49 | from ._array import compress_index_array 50 | 51 | 52 | __all__ = ["UpSetJSWidget"] 53 | 54 | 55 | def _sort_sets( 56 | sets: t.Sequence[UpSetSet[T]], 57 | order_by: str, 58 | limit: t.Optional[int] = None, 59 | ) -> t.List[UpSetSet[T]]: 60 | key = None 61 | if order_by == "cardinality": 62 | key = lambda s: (-s.cardinality, s.name) 63 | else: 64 | key = lambda s: s.name 65 | out_list = sorted(sets, key=key) 66 | if limit is not None: 67 | return out_list[:limit] 68 | return out_list 69 | 70 | 71 | def _sort_combinations( 72 | combinations: t.Sequence[UpSetSetCombination[T]], 73 | sets: t.Sequence[UpSetSet[T]], 74 | order_by: t.Union[str, t.Sequence[str]], 75 | limit: t.Optional[int] = None, 76 | ) -> t.List[UpSetSetCombination[T]]: 77 | def to_key(order: str): 78 | if order == "cardinality": 79 | return lambda s: -s.cardinality 80 | if order == "degree": 81 | return lambda s: s.degree 82 | if order == "group": 83 | return lambda c: next( 84 | (i for i, s in enumerate(sets) if s in c.sets), len(sets) 85 | ) 86 | return lambda s: s.name 87 | 88 | keys = ( 89 | [to_key(order_by)] 90 | if isinstance(order_by, str) 91 | else [to_key(v) for v in order_by] 92 | ) 93 | out_list = sorted(combinations, key=lambda s: tuple([k(s) for k in keys])) 94 | if limit is not None: 95 | return out_list[:limit] 96 | return out_list 97 | 98 | 99 | def _to_set_list(arr: t.List[UpSetSet], model: "UpSetJSWidget"): 100 | return [ 101 | dict( 102 | type=str(s.set_type), 103 | name=s.name, 104 | color=s.color, 105 | cardinality=s.cardinality, 106 | elems=compress_index_array(model.elem_to_index[e] for e in s.elems), 107 | ) 108 | for s in arr 109 | ] 110 | 111 | 112 | def _to_combination_list(arr: t.List[UpSetSetCombination], model: "UpSetJSWidget"): 113 | return [ 114 | dict( 115 | type=str(s.set_type), 116 | name=s.name, 117 | color=s.color, 118 | cardinality=s.cardinality, 119 | degree=s.degree, 120 | set_names=[c.name for c in s.sets], 121 | elems=compress_index_array(model.elem_to_index[e] for e in s.elems), 122 | ) 123 | for s in arr 124 | ] 125 | 126 | 127 | def _to_query_list(arr: t.List[UpSetQuery], model: "UpSetJSWidget"): 128 | def _to_query(query: UpSetQuery): 129 | query_dict: t.Dict = dict(name=query.name, color=query.color) 130 | if query.set: 131 | query_dict["set"] = dict(name=query.set.name, type=str(query.set.set_type)) 132 | else: 133 | query_dict["elems"] = compress_index_array( 134 | model.elem_to_index[e] for e in query.elems or [] 135 | ) 136 | return query_dict 137 | 138 | return [_to_query(q) for q in arr] 139 | 140 | 141 | def _create_combination( 142 | cs_name: str, 143 | combination_type: UpSetSetType, 144 | count: int, 145 | sets: t.FrozenSet[UpSetSet], 146 | color_lookup: t.Mapping[str, str], 147 | ) -> UpSetSetCombination: 148 | if combination_type == UpSetSetType.DISTINCT_INTERSECTION: 149 | return UpSetSetDistinctIntersection[T]( 150 | cs_name, 151 | sets=sets, 152 | color=color_lookup.get(cs_name), 153 | cardinality=count, 154 | ) 155 | if combination_type == UpSetSetType.INTERSECTION: 156 | return UpSetSetIntersection[T]( 157 | cs_name, 158 | sets=sets, 159 | color=color_lookup.get(cs_name), 160 | cardinality=count, 161 | ) 162 | if combination_type == UpSetSetType.UNION: 163 | return UpSetSetUnion[T]( 164 | cs_name, 165 | sets=sets, 166 | color=color_lookup.get(cs_name), 167 | cardinality=count, 168 | ) 169 | return UpSetSetComposite[T]( 170 | cs_name, 171 | sets=sets, 172 | color=color_lookup.get(cs_name), 173 | cardinality=count, 174 | ) 175 | 176 | 177 | class UpSetJSBaseWidget(ValueWidget, DOMWidget, t.Generic[T]): 178 | """UpSet.js Base Widget""" 179 | 180 | _model_name = Unicode("UpSetModel").tag(sync=True) 181 | _model_module = Unicode(MODULE_NAME).tag(sync=True) 182 | _model_module_version = Unicode(MODULE_VERSION).tag(sync=True) 183 | _view_name = Unicode("UpSetView").tag(sync=True) 184 | _view_module = Unicode(MODULE_NAME).tag(sync=True) 185 | _view_module_version = Unicode(MODULE_VERSION).tag(sync=True) 186 | 187 | mode: str = Enum( 188 | ("hover", "click", "static", "contextMenu"), default_value="hover" 189 | ).tag(sync=True) 190 | """ 191 | interactivity mode of the widget whether the plot is static, reacts on hover or click events 192 | """ 193 | padding: float = Float(None, allow_none=True).tag(sync=True) 194 | """ 195 | padding within the svg 196 | """ 197 | 198 | elems: t.List[T] = List(default_value=[]).tag(sync=True) 199 | elem_to_index: t.Dict[T, int] = {} 200 | 201 | sets: t.List[UpSetSet[T]] = List( 202 | Instance(UpSetSet), 203 | default_value=[], 204 | ).tag(sync=True, to_json=_to_set_list) 205 | 206 | combinations: t.List[UpSetSetCombination[T]] = List( 207 | Instance(UpSetSetCombination), 208 | default_value=[], 209 | ).tag(sync=True, to_json=_to_combination_list) 210 | 211 | value: t.Union[None, t.Mapping, t.List[int]] = Union( 212 | (Dict(allow_none=True, default_value=None), List(Int(), default_value=[])) 213 | ).tag(sync=True) 214 | _selection: t.Union[None, t.FrozenSet[T], UpSetSetLike[T]] = None 215 | 216 | queries: t.List[UpSetQuery[T]] = List(Instance(UpSetQuery), default_value=[]).tag( 217 | sync=True, to_json=_to_query_list 218 | ) 219 | 220 | theme: str = Enum(("light", "dark", "vega"), default_value="light").tag(sync=True) 221 | selection_color: str = Union( 222 | (Color(None, allow_none=True), Unicode(None, allow_none=True)) 223 | ).tag(sync=True) 224 | has_selection_color: str = Color(None, allow_none=True).tag(sync=True) 225 | opacity: float = Float(None, allow_none=True).tag(sync=True) 226 | has_selection_opacity: float = Float(None, allow_none=True).tag(sync=True) 227 | 228 | color: str = Color(None, allow_none=True).tag(sync=True) 229 | text_color: str = Color(None, allow_none=True).tag(sync=True) 230 | 231 | query_legend: bool = Bool(None, allow_none=True).tag(sync=True) 232 | export_buttons: bool = Bool(None, allow_none=True).tag(sync=True) 233 | font_family: str = Unicode(None, allow_none=True).tag(sync=True) 234 | font_sizes: UpSetFontSizes = Instance(UpSetFontSizes).tag( 235 | sync=True, to_json=lambda v, _: v.to_json() 236 | ) 237 | 238 | title: str = Unicode(None, allow_none=True).tag(sync=True) 239 | description: str = Unicode(None, allow_none=True).tag(sync=True) 240 | 241 | _expression_data: bool = Bool(None, allow_none=True).tag(sync=True) 242 | 243 | def __init__(self, **kwargs): 244 | super().__init__(**kwargs) 245 | self.observe(self._sync_value, "value") 246 | 247 | def _base_copy(self, clone: "UpSetJSBaseWidget"): 248 | clone.title = self.title 249 | clone.description = self.description 250 | clone.mode = self.mode 251 | clone.padding = self.padding 252 | clone.elems = list(self.elems) 253 | clone.elem_to_index = self.elem_to_index.copy() 254 | clone.sets = list(self.sets) 255 | clone.combinations = list(self.combinations) 256 | clone.queries = list(self.queries) 257 | # pylint: disable=protected-access 258 | clone._expression_data = self._expression_data 259 | 260 | clone.value = self.value 261 | clone.selection = self.selection 262 | 263 | clone.theme = self.theme 264 | clone.selection_color = self.selection_color 265 | clone.color = self.color 266 | clone.has_selection_color = self.has_selection_color 267 | clone.opacity = self.opacity 268 | clone.has_selection_opacity = self.has_selection_opacity 269 | clone.text_color = self.text_color 270 | 271 | clone.query_legend = self.query_legend 272 | clone.export_buttons = self.export_buttons 273 | clone.font_family = self.font_family 274 | clone.font_sizes = self.font_sizes.copy() 275 | 276 | def _sync_value(self, evt: t.Any): 277 | self._selection = self._value_to_selection(evt["new"]) 278 | 279 | def get_interact_value(self): 280 | """Return the value for this widget which should be passed to 281 | interactive functions. Custom widgets can change this method 282 | to process the raw value ``self.value``. 283 | """ 284 | return self.selection 285 | 286 | def _value_to_selection(self, value): 287 | if value is None: 288 | return None 289 | if isinstance(value, list): 290 | return frozenset([self.elems[i] for i in value]) 291 | if isinstance(value, dict): 292 | set_type = value.get("type", "set") 293 | name = value["name"] 294 | 295 | if set_type == "set": 296 | found_set = next((s for s in self.sets if s.name == name), None) 297 | if found_set: 298 | return found_set 299 | else: 300 | found_set = next( 301 | ( 302 | s 303 | for s in self.combinations 304 | if s.name == name and str(s.set_type) == set_type 305 | ), 306 | None, 307 | ) 308 | if found_set: 309 | return found_set 310 | 311 | # generate since not found 312 | elems = frozenset(self.elems[i] for i in value.get("elems", [])) 313 | set_by_name = {s.name: s for s in self.sets} 314 | sets = frozenset(set_by_name[n] for n in value.get("set_names", [])) 315 | if set_type == "set": 316 | return UpSetSet[T](name, elems) 317 | if set_type == "intersection": 318 | return UpSetSetIntersection[T](name, elems, sets) 319 | if set_type == "distinctIntersection": 320 | return UpSetSetDistinctIntersection[T](name, elems, sets) 321 | if set_type == "union": 322 | return UpSetSetUnion[T](name, elems, sets) 323 | return UpSetSetComposite[T](name, elems, sets) 324 | return None 325 | 326 | @property 327 | def selection(self) -> t.Union[None, t.FrozenSet[T], UpSetSetLike[T]]: 328 | """ 329 | the current selection 330 | """ 331 | return self._selection 332 | 333 | @selection.setter 334 | def selection(self, value: t.Union[None, t.FrozenSet[T], UpSetSetLike[T]]): 335 | self._selection = value 336 | 337 | self.unobserve(self._sync_value, "value") 338 | 339 | if value is None: 340 | self.value = None 341 | elif isinstance(value, (list, set, frozenset)): 342 | self.value = [self.elem_to_index[e] for e in value] 343 | elif isinstance(value, UpSetSet): 344 | self.value = dict(type="set", name=value.name) 345 | else: 346 | assert isinstance( 347 | value, 348 | ( 349 | UpSetSetIntersection, 350 | UpSetSetUnion, 351 | UpSetSetComposite, 352 | UpSetSetDistinctIntersection, 353 | ), 354 | ) 355 | self.value = dict(type=str(value.set_type), name=value.name) 356 | # unknown 357 | self.observe(self._sync_value, "value") 358 | 359 | def on_selection_changed(self, callback): 360 | """ 361 | add callback listener to listen for selection changes 362 | """ 363 | self.observe(lambda _: callback(self.selection), "value") 364 | 365 | def clear_queries(self): 366 | """ 367 | deletes the list of queries 368 | """ 369 | self.queries = [] 370 | 371 | def append_query( 372 | self, 373 | name: str, 374 | color: str, 375 | upset: t.Optional[UpSetSetLike[T]] = None, 376 | elems: t.Optional[t.FrozenSet[T]] = None, 377 | ) -> "UpSetJSWidget": 378 | """ 379 | adds another UpSetQuery to be visualized 380 | """ 381 | query: UpSetQuery[T] 382 | if upset is not None: 383 | query = UpSetQuery[T](name, color, upset=upset) 384 | else: 385 | query = UpSetQuery[T](name, color, elems=elems or frozenset()) 386 | self.queries = self.queries + [query] 387 | return self 388 | 389 | @property 390 | def width(self) -> t.Union[str, int]: 391 | """ 392 | get the widget width 393 | """ 394 | if self.layout.width.endswidth("px"): 395 | return int(self.layout.width[:-2]) 396 | return self.layout.width 397 | 398 | @width.setter 399 | def width(self, value: t.Union[str, int]): 400 | if isinstance(value, int): 401 | self.layout.width = f"{value}px" 402 | else: 403 | self.layout.width = value 404 | 405 | @property 406 | def height(self) -> t.Union[str, int]: 407 | """ 408 | get the widget height 409 | """ 410 | if self.layout.height.endswidth("px"): 411 | return int(self.layout.height[:-2]) 412 | return self.layout.height 413 | 414 | @height.setter 415 | def height(self, value: t.Union[str, int]): 416 | if isinstance(value, int): 417 | self.layout.height = f"{value}px" 418 | else: 419 | self.layout.height = value 420 | 421 | @default("layout") 422 | def _default_layout(self): # pylint: disable=no-self-use 423 | return Layout(height="400px", align_self="stretch") 424 | 425 | @default("font_sizes") 426 | def _default_font_sizes(self): # pylint: disable=no-self-use 427 | return UpSetFontSizes() 428 | 429 | def _from_dict( 430 | self, 431 | sets: t.Mapping[str, t.Sequence[T]], 432 | order_by: str = "cardinality", 433 | limit: t.Optional[int] = None, 434 | colors: t.Mapping[str, str] = None, 435 | ) -> t.List[UpSetSet[T]]: 436 | """ 437 | generates the list of sets from a dict 438 | """ 439 | elems: t.Set[T] = set() 440 | for set_elems in sets.values(): 441 | elems.update(set_elems) 442 | self.elems = sorted(elems) # type: ignore 443 | self.elem_to_index = {e: i for i, e in enumerate(self.elems)} 444 | self._expression_data = False 445 | color_lookup = colors or {} 446 | 447 | base_sets: t.List[UpSetSet[T]] = [ 448 | UpSetSet[T](name=k, elems=frozenset(v), color=color_lookup.get(k)) 449 | for k, v in sets.items() 450 | ] 451 | self.clear_queries() 452 | self.selection = None 453 | self.sets = _sort_sets(base_sets, order_by, limit) 454 | 455 | return base_sets if limit is None else self.sets 456 | 457 | def from_expression( 458 | self, 459 | combinations: t.Mapping[str, int], 460 | combination_type: UpSetSetType = UpSetSetType.INTERSECTION, 461 | symbol: str = "&", 462 | order_by: t.Union[str, t.Sequence[str]] = "cardinality", 463 | set_order_by: str = "cardinality", 464 | colors: t.Mapping[str, str] = None, 465 | ) -> "UpSetJSBaseWidget": 466 | """ 467 | generates the list of sets from a dict 468 | """ 469 | self.elems = [] 470 | self.elem_to_index = {} 471 | self._expression_data = True 472 | color_lookup = colors or {} 473 | 474 | base_sets: t.Dict[str, UpSetSet[T]] = {} 475 | set_intersections: t.List[UpSetSetCombination[T]] = [] 476 | 477 | for cs_name, count in combinations.items(): 478 | contained_sets: t.List[UpSetSet[T]] = [] 479 | 480 | for set_name in cs_name.split(symbol): 481 | if set_name not in base_sets: 482 | base_sets[set_name] = UpSetSet[T]( 483 | set_name, 484 | elems=None, 485 | color=color_lookup.get(set_name), 486 | cardinality=0, 487 | ) 488 | contained_sets.append(base_sets[set_name]) 489 | 490 | if combination_type == UpSetSetType.DISTINCT_INTERSECTION: 491 | for s_obj in contained_sets: 492 | s_obj.cardinality += count 493 | elif len(contained_sets) == 1: 494 | contained_sets[0].cardinality = count 495 | elif combination_type == UpSetSetType.INTERSECTION: 496 | for s_obj in contained_sets: 497 | s_obj.cardinality = max(s_obj.cardinality, count) 498 | elif combination_type == UpSetSetType.UNION: 499 | for s_obj in contained_sets: 500 | s_obj.cardinality = min(s_obj.cardinality, count) 501 | 502 | set_intersections.append( 503 | _create_combination( 504 | cs_name, 505 | combination_type, 506 | count, 507 | frozenset(contained_sets), 508 | color_lookup, 509 | ) 510 | ) 511 | 512 | self.clear_queries() 513 | self.selection = None 514 | self.sets = _sort_sets(list(base_sets.values()), set_order_by, None) 515 | self.combinations = _sort_combinations( 516 | set_intersections, self.sets, order_by, None 517 | ) 518 | return self 519 | 520 | def _from_dataframe( 521 | self, 522 | data_frame: t.Any, 523 | attributes: t.Union[t.Sequence[str], t.Any, None] = None, 524 | order_by: str = "cardinality", 525 | limit: t.Optional[int] = None, 526 | colors: t.Mapping[str, str] = None, 527 | ) -> t.List[UpSetSet[T]]: 528 | """ 529 | generates the list of sets from a dataframe 530 | """ 531 | self.elems = sorted(data_frame.index) 532 | self.elem_to_index = {e: i for i, e in enumerate(self.elems)} 533 | self._expression_data = False 534 | color_lookup = colors or {} 535 | 536 | def to_set(name: str, series): 537 | elems = series[series.astype(bool)].index 538 | return UpSetSet[T]( 539 | name=name, elems=frozenset(elems), color=color_lookup.get(name) 540 | ) 541 | 542 | attribute_columns = attributes if isinstance(attributes, (list, tuple)) else [] 543 | 544 | base_sets: t.List[UpSetSet[T]] = [ 545 | to_set(name, series) 546 | for name, series in data_frame.items() 547 | if name not in attribute_columns 548 | ] 549 | self.clear_queries() 550 | self.selection = None 551 | self.sets = _sort_sets(base_sets, order_by, limit) 552 | return base_sets if limit is None else self.sets 553 | 554 | 555 | @register 556 | class UpSetJSWidget(UpSetJSBaseWidget, t.Generic[T]): 557 | """UpSet.js Widget""" 558 | 559 | _render_mode = Unicode("upset").tag(sync=True) 560 | 561 | bar_padding: float = Float(None, allow_none=True).tag(sync=True) 562 | """ 563 | padding argument for scaleBand (0..1) 564 | """ 565 | dot_padding: float = Float(None, allow_none=True).tag(sync=True) 566 | width_ratios: t.Tuple[float, float, float] = Tuple( 567 | Float(), Float(), Float(), default_value=(0.25, 0.1, 0.65) 568 | ).tag(sync=True) 569 | height_ratios: t.Tuple[float, float] = Tuple( 570 | Float(), Float(), default_value=(0.6, 0.4) 571 | ).tag(sync=True) 572 | 573 | attrs: t.List[UpSetAttribute[T]] = List( 574 | Instance(UpSetAttribute), default_value=[] 575 | ).tag(sync=True, to_json=lambda v, _: [vi.to_json() for vi in v]) 576 | 577 | alternating_background_color: str = Color(None, allow_none=True).tag(sync=True) 578 | hover_hint_color: str = Color(None, allow_none=True).tag(sync=True) 579 | not_member_color: str = Color(None, allow_none=True).tag(sync=True) 580 | 581 | bar_label_offset: float = Float(None, allow_none=True).tag(sync=True) 582 | set_name_axis_offset: float = Float(None, allow_none=True).tag(sync=True) 583 | combination_name_axis_offset: float = Float(None, allow_none=True).tag(sync=True) 584 | 585 | numeric_scale: str = Enum(("linear", "log"), default_value="linear").tag(sync=True) 586 | band_scale: str = Enum(("band"), default_value="band").tag(sync=True) 587 | 588 | set_name: str = Unicode(None, allow_none=True).tag(sync=True) 589 | combination_name: str = Unicode(None, allow_none=True).tag(sync=True) 590 | 591 | def copy(self) -> "UpSetJSWidget": 592 | """ 593 | returns a copy of itself 594 | """ 595 | clone = UpSetJSWidget[T]() # pylint: disable=unsubscriptable-object 596 | self._base_copy(clone) 597 | clone.bar_padding = self.bar_padding 598 | clone.dot_padding = self.dot_padding 599 | clone.width_ratios = self.width_ratios 600 | clone.height_ratios = self.height_ratios 601 | 602 | clone.alternating_background_color = self.alternating_background_color 603 | clone.hover_hint_color = self.hover_hint_color 604 | clone.not_member_color = self.not_member_color 605 | 606 | clone.bar_label_offset = self.bar_label_offset 607 | clone.set_name_axis_offset = self.set_name_axis_offset 608 | clone.combination_name_axis_offset = self.combination_name_axis_offset 609 | 610 | clone.numeric_scale = self.numeric_scale 611 | clone.band_scale = self.band_scale 612 | 613 | clone.set_name = self.set_name 614 | clone.combination_name = self.combination_name 615 | 616 | return clone 617 | 618 | def from_dict( 619 | self, 620 | sets: t.Mapping[str, t.Sequence[T]], 621 | order_by: str = "cardinality", 622 | limit: t.Optional[int] = None, 623 | colors: t.Mapping[str, str] = None, 624 | ) -> "UpSetJSWidget": 625 | """ 626 | generates the list of sets from a dict 627 | """ 628 | base_sets = super()._from_dict(sets, order_by, limit, colors) 629 | self.attrs = [] 630 | return self._generate_intersections(base_sets, order_by, colors) 631 | 632 | def from_dataframe( 633 | self, 634 | data_frame: t.Any, 635 | attributes: t.Union[t.Sequence[str], t.Any, None] = None, 636 | order_by: str = "cardinality", 637 | limit: t.Optional[int] = None, 638 | colors: t.Mapping[str, str] = None, 639 | ) -> "UpSetJSWidget": 640 | """ 641 | generates the list of sets from a dataframe 642 | """ 643 | base_sets = super()._from_dataframe( 644 | data_frame, attributes, order_by, limit, colors 645 | ) 646 | 647 | def as_attr(name: str, values: t.List): 648 | attr_type = ( 649 | "categorical" if not values or isinstance(values[0], str) else "number" 650 | ) 651 | domain = (min(values), max(values)) if attr_type == "number" else None 652 | categories = sorted(set(values)) if attr_type == "categorical" else None 653 | return UpSetAttribute[T]( 654 | attr_type, name, values, domain=domain, categories=categories 655 | ) 656 | 657 | if attributes is not None: 658 | attribute_df = ( 659 | data_frame.loc[:, attributes] 660 | if isinstance(attributes, (list, tuple)) 661 | else attributes 662 | ) 663 | self.attrs = [ 664 | as_attr(name, series.tolist()) for name, series in attribute_df.items() 665 | ] 666 | else: 667 | self.attrs = [] 668 | 669 | return self._generate_intersections(base_sets, order_by, colors) 670 | 671 | def _generate_intersections( 672 | self, 673 | base_sets: t.List[UpSetSet[T]], 674 | order_by: t.Union[str, t.Sequence[str]] = "cardinality", 675 | colors: t.Mapping[str, str] = None, 676 | ) -> "UpSetJSWidget": 677 | """ 678 | customize the generation of the sets 679 | """ 680 | set_intersections = generate_intersections( 681 | base_sets, 0, None, False, self.elems, colors=colors 682 | ) 683 | 684 | self.combinations = _sort_combinations( 685 | set_intersections, self.sets, order_by, None 686 | ) 687 | return self 688 | 689 | def generate_intersections( 690 | self, 691 | min_degree: int = 0, 692 | max_degree: t.Optional[int] = None, 693 | empty: bool = False, 694 | order_by: t.Union[str, t.Sequence[str]] = "cardinality", 695 | limit: t.Optional[int] = None, 696 | colors: t.Mapping[str, str] = None, 697 | ) -> "UpSetJSWidget": 698 | """ 699 | customize the generation of the sets 700 | """ 701 | set_intersections = generate_intersections( 702 | self.sets, min_degree, max_degree, empty, self.elems, colors=colors 703 | ) 704 | 705 | self.combinations = _sort_combinations( 706 | set_intersections, self.sets, order_by, limit 707 | ) 708 | return self 709 | 710 | def generate_distinct_intersections( 711 | self, 712 | min_degree: int = 0, 713 | max_degree: t.Optional[int] = None, 714 | empty: bool = False, 715 | order_by: t.Union[str, t.Sequence[str]] = "cardinality", 716 | limit: t.Optional[int] = None, 717 | colors: t.Mapping[str, str] = None, 718 | ) -> "UpSetJSWidget": 719 | """ 720 | customize the generation of the sets 721 | """ 722 | set_intersections = generate_distinct_intersections( 723 | self.sets, 724 | min_degree, 725 | max_degree, 726 | empty, 727 | self.elems, 728 | colors=colors, 729 | ) 730 | 731 | self.combinations = _sort_combinations( 732 | set_intersections, self.sets, order_by, limit 733 | ) 734 | return self 735 | 736 | def generate_unions( 737 | self, 738 | min_degree: int = 0, 739 | max_degree: t.Optional[int] = None, 740 | empty: bool = False, 741 | order_by: t.Union[str, t.Sequence[str]] = "cardinality", 742 | limit: t.Optional[int] = None, 743 | colors: t.Mapping[str, str] = None, 744 | ) -> "UpSetJSWidget": 745 | """ 746 | customize the generation of the sets 747 | """ 748 | set_unions = generate_unions( 749 | self.sets, 750 | min_degree, 751 | max_degree, 752 | empty, 753 | self.elems, 754 | colors=colors, 755 | ) 756 | 757 | self.combinations = _sort_combinations(set_unions, self.sets, order_by, limit) 758 | return self 759 | 760 | def clear_attributes(self) -> "UpSetJSWidget": 761 | """ 762 | deletes the list of attributes 763 | """ 764 | self.attrs = [] 765 | return self 766 | 767 | def append_numeric_attribute( 768 | self, 769 | name: str, 770 | values: t.List[float], 771 | min_value: t.Optional[float] = None, 772 | max_value: t.Optional[float] = None, 773 | ) -> "UpSetJSWidget": 774 | """ 775 | adds another numerical UpSetAttribute to be visualized 776 | """ 777 | domain = ( 778 | min_value if min_value is not None else min(values), 779 | max_value if max_value is not None else max(values), 780 | ) 781 | self.attrs = self.attrs + [UpSetAttribute[T]("number", name, values, domain)] 782 | return self 783 | 784 | def append_categorical_attribute( 785 | self, 786 | name: str, 787 | values: t.List[str], 788 | categories: t.Optional[t.List[t.Union[str, t.Dict]]] = None, 789 | ) -> "UpSetJSWidget": 790 | """ 791 | adds another categorical UpSetAttribute to be visualized 792 | """ 793 | cats = categories if categories is not None else list(sorted(set(values))) 794 | self.attrs = self.attrs + [ 795 | UpSetAttribute[T]("categorical", name, values, categories=cats) 796 | ] 797 | return self 798 | 799 | 800 | @register 801 | class UpSetJSVennDiagramWidget(UpSetJSBaseWidget, t.Generic[T]): 802 | """UpSet.js Venn Diagram Widget""" 803 | 804 | _render_mode = Unicode("venn").tag(sync=True) 805 | 806 | value_text_color: str = Color(None, allow_none=True).tag(sync=True) 807 | stroke_color: str = Color(None, allow_none=True).tag(sync=True) 808 | 809 | def copy(self) -> "UpSetJSVennDiagramWidget": 810 | """ 811 | returns a copy of itself 812 | """ 813 | clone = UpSetJSVennDiagramWidget[T]() # pylint: disable=unsubscriptable-object 814 | self._base_copy(clone) 815 | clone.value_text_color = self.value_text_color 816 | clone.stroke_color = self.stroke_color 817 | 818 | return clone 819 | 820 | def from_dict( 821 | self, 822 | sets: t.Mapping[str, t.Sequence[T]], 823 | order_by: str = "cardinality", 824 | limit: t.Optional[int] = None, 825 | colors: t.Mapping[str, str] = None, 826 | ) -> "UpSetJSVennDiagramWidget": 827 | """ 828 | generates the list of sets from a dict 829 | """ 830 | base_sets = super()._from_dict(sets, order_by, limit, colors) 831 | return self._generate_distinct_intersections(base_sets, colors=colors) 832 | 833 | def from_dataframe( 834 | self, 835 | data_frame: t.Any, 836 | attributes: t.Union[t.Sequence[str], t.Any, None] = None, 837 | order_by: str = "cardinality", 838 | limit: t.Optional[int] = None, 839 | colors: t.Mapping[str, str] = None, 840 | ) -> "UpSetJSVennDiagramWidget": 841 | """ 842 | generates the list of sets from a dataframe 843 | """ 844 | base_sets = super()._from_dataframe( 845 | data_frame, attributes, order_by, limit, colors 846 | ) 847 | 848 | return self._generate_distinct_intersections(base_sets, colors=colors) 849 | 850 | def _generate_distinct_intersections( 851 | self, 852 | base_sets: t.List[UpSetSet[T]], 853 | colors: t.Mapping[str, str] = None, 854 | ) -> "UpSetJSVennDiagramWidget": 855 | set_intersections = generate_distinct_intersections( 856 | base_sets, 1, None, True, self.elems, colors=colors 857 | ) 858 | 859 | self.combinations = _sort_combinations( 860 | set_intersections, self.sets, ["degree", "group"] 861 | ) 862 | return self 863 | 864 | 865 | @register 866 | class UpSetJSEulerDiagramWidget(UpSetJSVennDiagramWidget, t.Generic[T]): 867 | """UpSet.js Euler Diagram Widget""" 868 | 869 | def __init__(self, **kwargs): 870 | super().__init__(**kwargs) 871 | self._render_mode = "euler" 872 | 873 | def copy(self) -> "UpSetJSEulerDiagramWidget": 874 | """ 875 | returns a copy of itself 876 | """ 877 | clone = UpSetJSEulerDiagramWidget[T]() # pylint: disable=unsubscriptable-object 878 | self._base_copy(clone) 879 | clone.value_text_color = self.value_text_color 880 | clone.stroke_color = self.stroke_color 881 | 882 | return clone 883 | 884 | 885 | @register 886 | class UpSetJSKarnaughMapWidget(UpSetJSBaseWidget, t.Generic[T]): 887 | """UpSet.js Karnaugh Map Widget""" 888 | 889 | _render_mode = Unicode("kmap").tag(sync=True) 890 | 891 | bar_padding: float = Float(None, allow_none=True).tag(sync=True) 892 | """ 893 | padding argument for scaleBand (0..1) 894 | """ 895 | stroke_color: str = Color(None, allow_none=True).tag(sync=True) 896 | numeric_scale: str = Enum(("linear", "log"), default_value="linear").tag(sync=True) 897 | 898 | def copy(self) -> "UpSetJSKarnaughMapWidget": 899 | """ 900 | returns a copy of itself 901 | """ 902 | clone = UpSetJSKarnaughMapWidget[T]() # pylint: disable=unsubscriptable-object 903 | self._base_copy(clone) 904 | clone.bar_padding = self.bar_padding 905 | clone.stroke_color = self.stroke_color 906 | clone.numeric_scale = self.numeric_scale 907 | 908 | return clone 909 | 910 | def from_dict( 911 | self, 912 | sets: t.Mapping[str, t.Sequence[T]], 913 | order_by: str = "cardinality", 914 | limit: t.Optional[int] = None, 915 | colors: t.Mapping[str, str] = None, 916 | ) -> "UpSetJSKarnaughMapWidget": 917 | """ 918 | generates the list of sets from a dict 919 | """ 920 | base_sets = super()._from_dict(sets, order_by, limit, colors) 921 | return self._generate_distinct_intersections(base_sets, colors=colors) 922 | 923 | def from_dataframe( 924 | self, 925 | data_frame: t.Any, 926 | attributes: t.Union[t.Sequence[str], t.Any, None] = None, 927 | order_by: str = "cardinality", 928 | limit: t.Optional[int] = None, 929 | colors: t.Mapping[str, str] = None, 930 | ) -> "UpSetJSKarnaughMapWidget": 931 | """ 932 | generates the list of sets from a dataframe 933 | """ 934 | base_sets = super()._from_dataframe( 935 | data_frame, attributes, order_by, limit, colors 936 | ) 937 | 938 | return self._generate_distinct_intersections(base_sets, colors=colors) 939 | 940 | def _generate_distinct_intersections( 941 | self, 942 | base_sets: t.List[UpSetSet[T]], 943 | colors: t.Mapping[str, str] = None, 944 | ) -> "UpSetJSKarnaughMapWidget": 945 | set_intersections = generate_distinct_intersections( 946 | base_sets, 1, None, True, self.elems, colors=colors 947 | ) 948 | 949 | self.combinations = _sort_combinations( 950 | set_intersections, self.sets, ["degree", "group"] 951 | ) 952 | return self 953 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const version = require('./package.json').version; 3 | const PnpWebpackPlugin = require('pnp-webpack-plugin'); 4 | 5 | // Custom webpack rules 6 | const rules = [{ test: /\.ts$/, loader: 'ts-loader', options: { configFile: 'tsconfig.build.json' } }]; 7 | 8 | // Packages that shouldn't be bundled but loaded at runtime 9 | const externals = ['@jupyter-widgets/base']; 10 | 11 | const resolve = { 12 | // Add '.ts' and '.tsx' as resolvable extensions. 13 | extensions: ['.webpack.js', '.web.js', '.ts', '.js'], 14 | plugins: [PnpWebpackPlugin], 15 | }; 16 | const resolveLoader = { 17 | plugins: [PnpWebpackPlugin.moduleLoader(module)], 18 | }; 19 | 20 | module.exports = [ 21 | /** 22 | * Notebook extension 23 | * 24 | * This bundle only contains the part of the JavaScript that is run on load of 25 | * the notebook. 26 | */ 27 | { 28 | entry: './src/extension.ts', 29 | output: { 30 | filename: 'index.js', 31 | path: path.resolve(__dirname, 'upsetjs_jupyter_widget', 'nbextension', 'static'), 32 | libraryTarget: 'amd', 33 | }, 34 | module: { 35 | rules: rules, 36 | }, 37 | devtool: 'source-map', 38 | externals, 39 | resolve, 40 | resolveLoader, 41 | }, 42 | 43 | /** 44 | * Embeddable upsetjs_jupyter_widget bundle 45 | * 46 | * This bundle is almost identical to the notebook extension bundle. The only 47 | * difference is in the configuration of the webpack public path for the 48 | * static assets. 49 | * 50 | * The target bundle is always `dist/index.js`, which is the path required by 51 | * the custom widget embedder. 52 | */ 53 | { 54 | entry: './src/index.ts', 55 | output: { 56 | filename: 'index.js', 57 | path: path.resolve(__dirname, 'dist'), 58 | libraryTarget: 'amd', 59 | library: '@upsetjs/jupyter_widget', 60 | publicPath: 'https://unpkg.com/@upsetjs/jupyter_widget@' + version + '/dist/', 61 | }, 62 | devtool: 'source-map', 63 | module: { 64 | rules: rules, 65 | }, 66 | externals, 67 | resolve, 68 | resolveLoader, 69 | }, 70 | 71 | /** 72 | * Documentation widget bundle 73 | * 74 | * This bundle is used to embed widgets in the package documentation. 75 | */ 76 | { 77 | entry: './src/index.ts', 78 | output: { 79 | filename: 'embed-bundle.js', 80 | path: path.resolve(__dirname, 'docs', 'source', '_static'), 81 | library: '@upsetjs/jupyter_widget', 82 | libraryTarget: 'amd', 83 | }, 84 | module: { 85 | rules: rules, 86 | }, 87 | devtool: 'source-map', 88 | externals, 89 | resolve, 90 | resolveLoader, 91 | }, 92 | ]; 93 | --------------------------------------------------------------------------------