├── .Rbuildignore ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── bug.md │ ├── feature-request.md │ └── feature.md └── workflows │ └── release.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── dashvis.iml ├── git_toolbox_prj.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── watcherTasks.xml ├── .npmignore ├── .prettierrc ├── .pylintrc ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-APACHE-2.0 ├── MANIFEST.in ├── NAMESPACE ├── R └── dashvis.R ├── README.md ├── _validate_init.py ├── assets └── vis-network.min.css ├── dashvis ├── __init__.py ├── _imports_.py ├── dashvis.min.js.map ├── metadata.json └── stylesheets.py ├── deps └── dashvis.min.js.map ├── inst └── deps │ └── dashvis.min.js.map ├── man └── dashvis.Rd ├── package-lock.json ├── package.json ├── pyproject.toml ├── pytest.ini ├── readme_images └── DashVis_Logo.png ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── src ├── demo │ ├── App.js │ └── index.js └── lib │ ├── components │ └── DashNetwork.react.js │ └── index.js ├── tests ├── __init__.py ├── requirements.txt ├── test_01_network_data.py └── test_usage.py ├── usage.py ├── usage_examples ├── 01_network_data.py ├── 02_options.py ├── 03_browser_events.py ├── 04_physics_events.py ├── 05_other_events.py ├── 06_custom_events.py ├── 07_coordinate_conversion.py ├── 08_clustering.py ├── 09_manipulation.py ├── 10_nodes_edges_info.py ├── 11_physics_control.py ├── 12_selection_demo.py ├── 13_viewport_control.py ├── __init__.py └── _common.py ├── webpack.config.js └── webpack.serve.config.js /.Rbuildignore: -------------------------------------------------------------------------------- 1 | # ignore JS config files/folders 2 | node_modules/ 3 | coverage/ 4 | src/ 5 | lib/ 6 | .babelrc 7 | .builderrc 8 | .eslintrc 9 | .npmignore 10 | .editorconfig 11 | .eslintignore 12 | .prettierrc 13 | .circleci 14 | .github 15 | 16 | # demo folder has special meaning in R 17 | # this should hopefully make it still 18 | # allow for the possibility to make R demos 19 | demo/.*\.js 20 | demo/.*\.html 21 | demo/.*\.css 22 | 23 | # ignore Python files/folders 24 | setup.py 25 | usage.py 26 | setup.py 27 | requirements.txt 28 | MANIFEST.in 29 | CHANGELOG.md 30 | test/ 31 | # CRAN has weird LICENSE requirements 32 | LICENSE.txt 33 | ^.*\.Rproj$ 34 | ^\.Rproj\.user$ 35 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "env": { 4 | "production": { 5 | "plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel"] 6 | }, 7 | "development": { 8 | "plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel"] 9 | }, 10 | "test": { 11 | "plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel-test"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.css 2 | registerServiceWorker.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "prettier"], 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "blockBindings": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "destructuring": true, 13 | "forOf": true, 14 | "generators": true, 15 | "modules": true, 16 | "templateStrings": true, 17 | "jsx": true 18 | } 19 | }, 20 | "env": { 21 | "browser": true, 22 | "es6": true, 23 | "jasmine": true, 24 | "jest": true, 25 | "node": true 26 | }, 27 | "globals": { 28 | "jest": true 29 | }, 30 | "plugins": [ 31 | "react", 32 | "import" 33 | ], 34 | "rules": { 35 | "accessor-pairs": ["error"], 36 | "block-scoped-var": ["error"], 37 | "consistent-return": ["error"], 38 | "curly": ["error", "all"], 39 | "default-case": ["error"], 40 | "dot-location": ["off"], 41 | "dot-notation": ["error"], 42 | "eqeqeq": ["error"], 43 | "guard-for-in": ["off"], 44 | "import/named": ["off"], 45 | "import/no-duplicates": ["error"], 46 | "import/no-named-as-default": ["error"], 47 | "new-cap": ["error"], 48 | "no-alert": [1], 49 | "no-caller": ["error"], 50 | "no-case-declarations": ["error"], 51 | "no-console": ["off"], 52 | "no-div-regex": ["error"], 53 | "no-dupe-keys": ["error"], 54 | "no-else-return": ["error"], 55 | "no-empty-pattern": ["error"], 56 | "no-eq-null": ["error"], 57 | "no-eval": ["error"], 58 | "no-extend-native": ["error"], 59 | "no-extra-bind": ["error"], 60 | "no-extra-boolean-cast": ["error"], 61 | "no-inline-comments": ["error"], 62 | "no-implicit-coercion": ["error"], 63 | "no-implied-eval": ["error"], 64 | "no-inner-declarations": ["off"], 65 | "no-invalid-this": ["error"], 66 | "no-iterator": ["error"], 67 | "no-labels": ["error"], 68 | "no-lone-blocks": ["error"], 69 | "no-loop-func": ["error"], 70 | "no-multi-str": ["error"], 71 | "no-native-reassign": ["error"], 72 | "no-new": ["error"], 73 | "no-new-func": ["error"], 74 | "no-new-wrappers": ["error"], 75 | "no-param-reassign": ["error"], 76 | "no-process-env": ["warn"], 77 | "no-proto": ["error"], 78 | "no-redeclare": ["error"], 79 | "no-return-assign": ["error"], 80 | "no-script-url": ["error"], 81 | "no-self-compare": ["error"], 82 | "no-sequences": ["error"], 83 | "no-shadow": ["off"], 84 | "no-throw-literal": ["error"], 85 | "no-undefined": ["error"], 86 | "no-unused-expressions": ["error"], 87 | "no-use-before-define": ["error", "nofunc"], 88 | "no-useless-call": ["error"], 89 | "no-useless-concat": ["error"], 90 | "no-with": ["error"], 91 | "prefer-const": ["error"], 92 | "radix": ["error"], 93 | "react/jsx-no-duplicate-props": ["error"], 94 | "react/jsx-no-undef": ["error"], 95 | "react/jsx-uses-react": ["error"], 96 | "react/jsx-uses-vars": ["error"], 97 | "react/no-did-update-set-state": ["error"], 98 | "react/no-direct-mutation-state": ["error"], 99 | "react/no-is-mounted": ["error"], 100 | "react/no-unknown-property": ["error"], 101 | "react/prefer-es6-class": ["error", "always"], 102 | "react/prop-types": "error", 103 | "valid-jsdoc": ["off"], 104 | "yoda": ["error"], 105 | "spaced-comment": ["error", "always", { 106 | "block": { 107 | "exceptions": ["*"] 108 | } 109 | }], 110 | "no-unused-vars": ["error", { 111 | "args": "after-used", 112 | "argsIgnorePattern": "^_", 113 | "caughtErrorsIgnorePattern": "^e$" 114 | }], 115 | "no-magic-numbers": ["error", { 116 | "ignoreArrayIndexes": true, 117 | "ignore": [-1, 0, 1, 2, 3, 100, 10, 0.5] 118 | }], 119 | "no-underscore-dangle": ["off"] 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Tell us about a bug you may have identified in Dash Bootstrap Components 4 | --- 5 | 6 | Before opening: 7 | 8 | - [Search for duplicate or closed issues](https://github.com/lewkoo/dashvis/issues?utf8=%E2%9C%93&q=is%3Aissue) 9 | - Read the [contributing guidelines](https://github.com/lewkoo/dashvis/blob/main/.github/CONTRIBUTING.md) 10 | 11 | Please fill out the below information as much as possible. 12 | 13 | - dash version: `#x.x.x` 14 | - dash-bootstrap-components version: `#x.x.x` 15 | - components affected by bug: 16 | 17 | ### What is happening? 18 | 19 | 20 | 21 | ### What should be happening? 22 | 23 | 24 | 25 | ### Code 26 | 27 | 30 | 31 | ```python 32 | # your code here 33 | ``` 34 | 35 | ### Error messages 36 | 37 | ```bash 38 | paste any error messages here 39 | ``` 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | Before opening: 2 | 3 | - [Search for duplicate or closed issues](https://github.com/lewkoo/dashvis/issues?utf8=%E2%9C%93&q=is%3Aissue) 4 | - Read the [contributing guidelines](https://github.com/lewkoo/dashvis/blob/main/.github/CONTRIBUTING.md) 5 | 6 | Please fill out the below information as much as possible. 7 | 8 | - dash version: `#x.x.x` 9 | - dash-bootstrap-components version: `#x.x.x` 10 | - components affected by bug: 11 | 12 | ### What is happening? 13 | 14 | 15 | 16 | ### What should be happening? 17 | 18 | 19 | 20 | ### Code 21 | 22 | 25 | 26 | ```python 27 | # your code here 28 | ``` 29 | 30 | ### Error messages 31 | 32 | ```bash 33 | paste any error messages here 34 | ``` 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Tell us about a feature you would like to see in Dash Bootstrap Components 4 | --- 5 | 6 | Before opening: 7 | 8 | - [Search for duplicate or closed issues](https://github.com/lewkoo/dashvis/issues?utf8=%E2%9C%93&q=is%3Aissue) 9 | - Read the [contributing guidelines](https://github.com/lewkoo/dashvis/blob/main/.github/CONTRIBUTING.md) 10 | 11 | Feature requests must include: 12 | 13 | - As much detail as possible for what we should add and why it's important to 14 | include it in Dash Bootstrap Components 15 | - Relevant links to prior art, screenshots, or live demos whenever possible 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | Before opening: 2 | 3 | - [Search for duplicate or closed issues](https://github.com/lewkoo/dashvis/issues?utf8=%E2%9C%93&q=is%3Aissue) 4 | - Read the [contributing guidelines](https://github.com/lewkoo/dashvis/blob/main/.github/CONTRIBUTING.md) 5 | 6 | Feature requests must include: 7 | 8 | - As much detail as possible for what we should add and why it's important to 9 | include it in Dash Bootstrap Components 10 | - Relevant links to prior art, screenshots, or live demos whenever possible 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: get-npm-version 16 | id: package-version 17 | uses: martinbeentjes/npm-get-version-action@main 18 | 19 | - name: Use Node.js 12 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 12.x 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | - name: Set up Python 3.8 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: 3.8 30 | 31 | - name: Install Python dependencies 32 | run: python -m pip install build 33 | 34 | - name: Install Python dependencies 35 | run: python -m pip install -r requirements.txt 36 | 37 | - name: Build dashvis 38 | run: npm run build 39 | 40 | - uses: JS-DevTools/npm-publish@v1 41 | with: 42 | token: ${{ secrets.NPM_TOKEN }} 43 | 44 | - name: Build dashvis package 45 | run: python -m build --sdist --wheel --outdir dist/ 46 | 47 | - name: Publish distribution 📦 to Test PyPI 48 | uses: pypa/gh-action-pypi-publish@master 49 | with: 50 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 51 | repository_url: https://test.pypi.org/legacy/ 52 | - name: Publish distribution 📦 to PyPI 53 | if: startsWith(github.ref, 'refs/heads/main') 54 | uses: pypa/gh-action-pypi-publish@master 55 | with: 56 | password: ${{ secrets.PYPI_API_TOKEN }} 57 | 58 | - name: Create GitHub release 59 | id: create-release 60 | if: startsWith(github.ref, 'refs/heads/main') 61 | uses: actions/create-release@v1 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | tag_name: ${{ steps.package-version.outputs.current-version }} 66 | release_name: Release ${{ steps.package-version.outputs.current-version }} 67 | body: ${{ github.event.head_commit.message }} 68 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Webpack 3 | .build_cache 4 | ### VisualStudioCode template 5 | .vscode/* 6 | !.vscode/settings.json 7 | !.vscode/tasks.json 8 | !.vscode/launch.json 9 | !.vscode/extensions.json 10 | ### JetBrains template 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 12 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 13 | 14 | # User-specific stuff 15 | .idea/**/workspace.xml 16 | .idea/**/tasks.xml 17 | .idea/**/usage.statistics.xml 18 | .idea/**/dictionaries 19 | .idea/**/shelf 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | ### Node template 72 | # Logs 73 | logs 74 | *.log 75 | npm-debug.log* 76 | yarn-debug.log* 77 | yarn-error.log* 78 | 79 | # Runtime data 80 | pids 81 | *.pid 82 | *.seed 83 | *.pid.lock 84 | 85 | # Directory for instrumented libs generated by jscoverage/JSCover 86 | lib-cov 87 | 88 | # Coverage directory used by tools like istanbul 89 | coverage 90 | 91 | # nyc test coverage 92 | .nyc_output 93 | 94 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 95 | .grunt 96 | 97 | # Bower dependency directory (https://bower.io/) 98 | bower_components 99 | 100 | # node-waf configuration 101 | .lock-wscript 102 | 103 | # Compiled binary addons (https://nodejs.org/api/addons.html) 104 | build/Release 105 | 106 | # Dependency directories 107 | node_modules/ 108 | jspm_packages/ 109 | 110 | # TypeScript v1 declaration files 111 | typings/ 112 | 113 | # Optional npm cache directory 114 | .npm 115 | 116 | # Optional eslint cache 117 | .eslintcache 118 | 119 | # Optional REPL history 120 | .node_repl_history 121 | 122 | # Output of 'npm pack' 123 | *.tgz 124 | 125 | # Yarn Integrity file 126 | .yarn-integrity 127 | 128 | # dotenv environment variables file 129 | .env 130 | 131 | # parcel-bundler cache (https://parceljs.org/) 132 | .cache 133 | 134 | # next.js build output 135 | .next 136 | 137 | # nuxt.js build output 138 | .nuxt 139 | 140 | # vuepress build output 141 | .vuepress/dist 142 | 143 | # Serverless directories 144 | .serverless 145 | ### Python template 146 | # Byte-compiled / optimized / DLL files 147 | __pycache__/ 148 | *.py[cod] 149 | *$py.class 150 | 151 | # C extensions 152 | *.so 153 | 154 | # Distribution / packaging 155 | .Python 156 | build/ 157 | develop-eggs/ 158 | dist/ 159 | downloads/ 160 | eggs/ 161 | .eggs/ 162 | lib64/ 163 | parts/ 164 | sdist/ 165 | var/ 166 | wheels/ 167 | *.egg-info/ 168 | .installed.cfg 169 | *.egg 170 | MANIFEST 171 | 172 | # PyInstaller 173 | # Usually these files are written by a python script from a template 174 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 175 | *.manifest 176 | *.spec 177 | 178 | # Installer logs 179 | pip-log.txt 180 | pip-delete-this-directory.txt 181 | 182 | # Unit test / coverage reports 183 | htmlcov/ 184 | .tox/ 185 | .coverage 186 | .coverage.* 187 | nosetests.xml 188 | coverage.xml 189 | *.cover 190 | .hypothesis/ 191 | .pytest_cache/ 192 | 193 | # Translations 194 | *.mo 195 | *.pot 196 | 197 | # Django stuff: 198 | local_settings.py 199 | db.sqlite3 200 | 201 | # Flask stuff: 202 | instance/ 203 | .webassets-cache 204 | 205 | # Scrapy stuff: 206 | .scrapy 207 | 208 | # Sphinx documentation 209 | docs/_build/ 210 | 211 | # PyBuilder 212 | target/ 213 | 214 | # Jupyter Notebook 215 | .ipynb_checkpoints 216 | 217 | # pyenv 218 | .python-version 219 | 220 | # celery beat schedule file 221 | celerybeat-schedule 222 | 223 | # SageMath parsed files 224 | *.sage.py 225 | 226 | # Environments 227 | .venv 228 | env/ 229 | venv/ 230 | ENV/ 231 | env.bak/ 232 | venv.bak/ 233 | 234 | # Spyder project settings 235 | .spyderproject 236 | .spyproject 237 | 238 | # Rope project settings 239 | .ropeproject 240 | 241 | # mkdocs documentation 242 | /site 243 | 244 | # mypy 245 | .mypy_cache/ 246 | ### SublimeText template 247 | # Cache files for Sublime Text 248 | *.tmlanguage.cache 249 | *.tmPreferences.cache 250 | *.stTheme.cache 251 | 252 | # Workspace files are user-specific 253 | *.sublime-workspace 254 | 255 | # Project files should be checked into the repository, unless a significant 256 | # proportion of contributors will probably not be using Sublime Text 257 | # *.sublime-project 258 | 259 | # SFTP configuration file 260 | sftp-config.json 261 | 262 | # Package control specific files 263 | Package Control.last-run 264 | Package Control.ca-list 265 | Package Control.ca-bundle 266 | Package Control.system-ca-bundle 267 | Package Control.cache/ 268 | Package Control.ca-certs/ 269 | Package Control.merged-ca-bundle 270 | Package Control.user-ca-bundle 271 | oscrypto-ca-bundle.crt 272 | bh_unicode_properties.cache 273 | 274 | # Sublime-github package stores a github token in this file 275 | # https://packagecontrol.io/packages/sublime-github 276 | GitHub.sublime-settings 277 | 278 | # Julia manifest file names 279 | Manifest.toml 280 | JuliaManifest.toml 281 | .DS_Store 282 | /deps/ 283 | /inst/deps/ 284 | /man/ 285 | /R/ 286 | /src/jl/ 287 | 288 | dashvis/*hmr.js 289 | dashvis/*hmr.json 290 | dashvis/output.js 291 | inst/deps/*hmr.js 292 | inst/deps/*hmr.json 293 | inst/deps/output.js 294 | 295 | demos/*.html 296 | demos/*.ipynb 297 | 298 | # js artifacts 299 | node_modules 300 | lib 301 | demo-lib 302 | 303 | # testing 304 | .nox 305 | .pytest_cache 306 | 307 | # dashvis artifacts 308 | dashvis/bundle.js 309 | dashvis/metadata.json 310 | dashvis/_components/ 311 | 312 | # python build artifacts 313 | __pycache__ 314 | *.pyc 315 | dist 316 | *.egg-info 317 | 318 | # release artifacts 319 | changelog.tmp 320 | 321 | # generated documentation files 322 | docs/templates/generated/**/*.html 323 | 324 | # vendored example apps 325 | docs/examples/vendor 326 | 327 | # R build artifacts 328 | inst 329 | man 330 | R/* 331 | DESCRIPTION 332 | NAMESPACE 333 | !R/icons.R 334 | !R/themes.R 335 | 336 | # Julia build artifacts 337 | deps/ 338 | src/*.jl 339 | src/jl 340 | Project.toml 341 | examples/**/Manifest.toml 342 | !examples/**/Project.toml 343 | /src/Dashvis.jl 344 | /R/internal.R 345 | /Project.toml 346 | /inst/deps/dashvis.min.js 347 | /DESCRIPTION 348 | /deps/dashvis.min.js 349 | /dashvis-test 350 | /dashvis-venv 351 | /dashvis/DashNetwork.py 352 | /dashvis/dashvis.min.js 353 | /dashvis/dashvis.min.js.LICENSE.txt 354 | /dashvis/package-info.json 355 | /.vscode 356 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/dashvis.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # misc 8 | .DS_Store 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Development folders and files 19 | public 20 | src 21 | scripts 22 | config 23 | .travis.yml 24 | CHANGELOG.md 25 | README.md 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "bracketSpacing": false, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 21 | # number of processors available to use. 22 | jobs=0 23 | 24 | # Control the amount of potential inferred values when inferring a single 25 | # object. This can help the performance when dealing with large functions or 26 | # complex, nested conditions. 27 | limit-inference-results=100 28 | 29 | # List of plugins (as comma separated values of python modules names) to load, 30 | # usually to register additional checkers. 31 | load-plugins= 32 | 33 | # Pickle collected data for later comparisons. 34 | persistent=yes 35 | 36 | # Specify a configuration file. 37 | #rcfile= 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape, 142 | import-error 143 | 144 | # Enable the message, report, category or checker with the given id(s). You can 145 | # either give multiple identifier separated by comma (,) or put this option 146 | # multiple time (only on the command line, not in the configuration file where 147 | # it should appear only once). See also the "--disable" option for examples. 148 | enable=c-extension-no-member 149 | 150 | 151 | [REPORTS] 152 | 153 | # Python expression which should return a note less than 10 (10 is the highest 154 | # note). You have access to the variables errors warning, statement which 155 | # respectively contain the number of errors / warnings messages and the total 156 | # number of statements analyzed. This is used by the global evaluation report 157 | # (RP0004). 158 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 159 | 160 | # Template used to display messages. This is a python new-style format string 161 | # used to format the message information. See doc for all details. 162 | #msg-template= 163 | 164 | # Set the output format. Available formats are text, parseable, colorized, json 165 | # and msvs (visual studio). You can also give a reporter class, e.g. 166 | # mypackage.mymodule.MyReporterClass. 167 | output-format=text 168 | 169 | # Tells whether to display a full report or only the messages. 170 | reports=no 171 | 172 | # Activate the evaluation score. 173 | score=yes 174 | 175 | 176 | [REFACTORING] 177 | 178 | # Maximum number of nested blocks for function / method body 179 | max-nested-blocks=5 180 | 181 | # Complete name of functions that never returns. When checking for 182 | # inconsistent-return-statements if a never returning function is called then 183 | # it will be considered as an explicit return statement and no message will be 184 | # printed. 185 | never-returning-functions=sys.exit 186 | 187 | 188 | [VARIABLES] 189 | 190 | # List of additional names supposed to be defined in builtins. Remember that 191 | # you should avoid defining new builtins when possible. 192 | additional-builtins= 193 | 194 | # Tells whether unused global variables should be treated as a violation. 195 | allow-global-unused-variables=yes 196 | 197 | # List of strings which can identify a callback function by name. A callback 198 | # name must start or end with one of those strings. 199 | callbacks=cb_, 200 | _cb 201 | 202 | # A regular expression matching the name of dummy variables (i.e. expected to 203 | # not be used). 204 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 205 | 206 | # Argument names that match this expression will be ignored. Default to name 207 | # with leading underscore. 208 | ignored-argument-names=_.*|^ignored_|^unused_ 209 | 210 | # Tells whether we should check for unused import in __init__ files. 211 | init-import=no 212 | 213 | # List of qualified module names which can have objects that can redefine 214 | # builtins. 215 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 216 | 217 | 218 | [BASIC] 219 | 220 | # Naming style matching correct argument names. 221 | argument-naming-style=snake_case 222 | 223 | # Regular expression matching correct argument names. Overrides argument- 224 | # naming-style. 225 | #argument-rgx= 226 | 227 | # Naming style matching correct attribute names. 228 | attr-naming-style=snake_case 229 | 230 | # Regular expression matching correct attribute names. Overrides attr-naming- 231 | # style. 232 | #attr-rgx= 233 | 234 | # Bad variable names which should always be refused, separated by a comma. 235 | bad-names=foo, 236 | bar, 237 | baz, 238 | toto, 239 | tutu, 240 | tata 241 | 242 | # Naming style matching correct class attribute names. 243 | class-attribute-naming-style=any 244 | 245 | # Regular expression matching correct class attribute names. Overrides class- 246 | # attribute-naming-style. 247 | #class-attribute-rgx= 248 | 249 | # Naming style matching correct class names. 250 | class-naming-style=PascalCase 251 | 252 | # Regular expression matching correct class names. Overrides class-naming- 253 | # style. 254 | #class-rgx= 255 | 256 | # Naming style matching correct constant names. 257 | const-naming-style=UPPER_CASE 258 | 259 | # Regular expression matching correct constant names. Overrides const-naming- 260 | # style. 261 | #const-rgx= 262 | 263 | # Minimum line length for functions/classes that require docstrings, shorter 264 | # ones are exempt. 265 | docstring-min-length=-1 266 | 267 | # Naming style matching correct function names. 268 | function-naming-style=snake_case 269 | 270 | # Regular expression matching correct function names. Overrides function- 271 | # naming-style. 272 | #function-rgx= 273 | 274 | # Good variable names which should always be accepted, separated by a comma. 275 | good-names=i, 276 | j, 277 | k, 278 | ex, 279 | Run, 280 | _ 281 | 282 | # Include a hint for the correct naming format with invalid-name. 283 | include-naming-hint=yes 284 | 285 | # Naming style matching correct inline iteration names. 286 | inlinevar-naming-style=any 287 | 288 | # Regular expression matching correct inline iteration names. Overrides 289 | # inlinevar-naming-style. 290 | #inlinevar-rgx= 291 | 292 | # Naming style matching correct method names. 293 | method-naming-style=snake_case 294 | 295 | # Regular expression matching correct method names. Overrides method-naming- 296 | # style. 297 | #method-rgx= 298 | 299 | # Naming style matching correct module names. 300 | module-naming-style=snake_case 301 | 302 | # Regular expression matching correct module names. Overrides module-naming- 303 | # style. 304 | #module-rgx= 305 | 306 | # Colon-delimited sets of names that determine each other's naming style when 307 | # the name regexes allow several styles. 308 | name-group= 309 | 310 | # Regular expression which should only match function or class names that do 311 | # not require a docstring. 312 | no-docstring-rgx=^_ 313 | 314 | # List of decorators that produce properties, such as abc.abstractproperty. Add 315 | # to this list to register other decorators that produce valid properties. 316 | # These decorators are taken in consideration only for invalid-name. 317 | property-classes=abc.abstractproperty 318 | 319 | # Naming style matching correct variable names. 320 | variable-naming-style=snake_case 321 | 322 | # Regular expression matching correct variable names. Overrides variable- 323 | # naming-style. 324 | #variable-rgx= 325 | 326 | 327 | [SPELLING] 328 | 329 | # Limits count of emitted suggestions for spelling mistakes. 330 | max-spelling-suggestions=4 331 | 332 | # Spelling dictionary name. Available dictionaries: none. To make it working 333 | # install python-enchant package.. 334 | spelling-dict= 335 | 336 | # List of comma separated words that should not be checked. 337 | spelling-ignore-words= 338 | 339 | # A path to a file that contains private dictionary; one word per line. 340 | spelling-private-dict-file= 341 | 342 | # Tells whether to store unknown words to indicated private dictionary in 343 | # --spelling-private-dict-file option instead of raising a message. 344 | spelling-store-unknown-words=no 345 | 346 | 347 | [TYPECHECK] 348 | 349 | # List of decorators that produce context managers, such as 350 | # contextlib.contextmanager. Add to this list to register other decorators that 351 | # produce valid context managers. 352 | contextmanager-decorators=contextlib.contextmanager 353 | 354 | # List of members which are set dynamically and missed by pylint inference 355 | # system, and so shouldn't trigger E1101 when accessed. Python regular 356 | # expressions are accepted. 357 | generated-members= 358 | 359 | # Tells whether missing members accessed in mixin class should be ignored. A 360 | # mixin class is detected if its name ends with "mixin" (case insensitive). 361 | ignore-mixin-members=yes 362 | 363 | # Tells whether to warn about missing members when the owner of the attribute 364 | # is inferred to be None. 365 | ignore-none=yes 366 | 367 | # This flag controls whether pylint should warn about no-member and similar 368 | # checks whenever an opaque object is returned when inferring. The inference 369 | # can return multiple potential results while evaluating a Python object, but 370 | # some branches might not be evaluated, which results in partial inference. In 371 | # that case, it might be useful to still emit no-member and other checks for 372 | # the rest of the inferred objects. 373 | ignore-on-opaque-inference=yes 374 | 375 | # List of class names for which member attributes should not be checked (useful 376 | # for classes with dynamically set attributes). This supports the use of 377 | # qualified names. 378 | ignored-classes=optparse.Values,thread._local,_thread._local 379 | 380 | # List of module names for which member attributes should not be checked 381 | # (useful for modules/projects where namespaces are manipulated during runtime 382 | # and thus existing member attributes cannot be deduced by static analysis. It 383 | # supports qualified module names, as well as Unix pattern matching. 384 | ignored-modules= 385 | 386 | # Show a hint with possible names when a member name was not found. The aspect 387 | # of finding the hint is based on edit distance. 388 | missing-member-hint=yes 389 | 390 | # The minimum edit distance a name should have in order to be considered a 391 | # similar match for a missing member name. 392 | missing-member-hint-distance=1 393 | 394 | # The total number of similar names that should be taken in consideration when 395 | # showing a hint for a missing member. 396 | missing-member-max-choices=1 397 | 398 | 399 | [FORMAT] 400 | 401 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 402 | expected-line-ending-format= 403 | 404 | # Regexp for a line that is allowed to be longer than the limit. 405 | ignore-long-lines=^\s*(# )??$ 406 | 407 | # Number of spaces of indent required inside a hanging or continued line. 408 | indent-after-paren=4 409 | 410 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 411 | # tab). 412 | indent-string=' ' 413 | 414 | # Maximum number of characters on a single line. 415 | max-line-length=120 416 | 417 | # Maximum number of lines in a module. 418 | max-module-lines=1000 419 | 420 | # List of optional constructs for which whitespace checking is disabled. `dict- 421 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 422 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 423 | # `empty-line` allows space-only lines. 424 | no-space-check=trailing-comma, 425 | dict-separator 426 | 427 | # Allow the body of a class to be on the same line as the declaration if body 428 | # contains single statement. 429 | single-line-class-stmt=no 430 | 431 | # Allow the body of an if to be on the same line as the test if there is no 432 | # else. 433 | single-line-if-stmt=no 434 | 435 | 436 | [LOGGING] 437 | 438 | # Format style used to check logging format string. `old` means using % 439 | # formatting, while `new` is for `{}` formatting. 440 | logging-format-style=old 441 | 442 | # Logging modules to check that the string format arguments are in logging 443 | # function parameter format. 444 | logging-modules=logging 445 | 446 | 447 | [MISCELLANEOUS] 448 | 449 | # List of note tags to take in consideration, separated by a comma. 450 | notes=FIXME, 451 | XXX, 452 | TODO 453 | 454 | 455 | [SIMILARITIES] 456 | 457 | # Ignore comments when computing similarities. 458 | ignore-comments=yes 459 | 460 | # Ignore docstrings when computing similarities. 461 | ignore-docstrings=yes 462 | 463 | # Ignore imports when computing similarities. 464 | ignore-imports=no 465 | 466 | # Minimum lines number of a similarity. 467 | min-similarity-lines=4 468 | 469 | 470 | [IMPORTS] 471 | 472 | # Allow wildcard imports from modules that define __all__. 473 | allow-wildcard-with-all=no 474 | 475 | # Analyse import fallback blocks. This can be used to support both Python 2 and 476 | # 3 compatible code, which means that the block might have code that exists 477 | # only in one or another interpreter, leading to false positives when analysed. 478 | analyse-fallback-blocks=no 479 | 480 | # Deprecated modules which should not be used, separated by a comma. 481 | deprecated-modules=optparse,tkinter.tix 482 | 483 | # Create a graph of external dependencies in the given file (report RP0402 must 484 | # not be disabled). 485 | ext-import-graph= 486 | 487 | # Create a graph of every (i.e. internal and external) dependencies in the 488 | # given file (report RP0402 must not be disabled). 489 | import-graph= 490 | 491 | # Create a graph of internal dependencies in the given file (report RP0402 must 492 | # not be disabled). 493 | int-import-graph= 494 | 495 | # Force import order to recognize a module as part of the standard 496 | # compatibility libraries. 497 | known-standard-library= 498 | 499 | # Force import order to recognize a module as part of a third party library. 500 | known-third-party=enchant 501 | 502 | 503 | [DESIGN] 504 | 505 | # Maximum number of arguments for function / method. 506 | max-args=5 507 | 508 | # Maximum number of attributes for a class (see R0902). 509 | max-attributes=7 510 | 511 | # Maximum number of boolean expressions in an if statement. 512 | max-bool-expr=5 513 | 514 | # Maximum number of branch for function / method body. 515 | max-branches=12 516 | 517 | # Maximum number of locals for function / method body. 518 | max-locals=15 519 | 520 | # Maximum number of parents for a class (see R0901). 521 | max-parents=7 522 | 523 | # Maximum number of public methods for a class (see R0904). 524 | max-public-methods=20 525 | 526 | # Maximum number of return / yield for function / method body. 527 | max-returns=6 528 | 529 | # Maximum number of statements in function / method body. 530 | max-statements=50 531 | 532 | # Minimum number of public methods for a class (see R0903). 533 | min-public-methods=2 534 | 535 | 536 | [CLASSES] 537 | 538 | # List of method names used to declare (i.e. assign) instance attributes. 539 | defining-attr-methods=__init__, 540 | __new__, 541 | setUp 542 | 543 | # List of member names, which should be excluded from the protected access 544 | # warning. 545 | exclude-protected=_asdict, 546 | _fields, 547 | _replace, 548 | _source, 549 | _make 550 | 551 | # List of valid names for the first argument in a class method. 552 | valid-classmethod-first-arg=cls 553 | 554 | # List of valid names for the first argument in a metaclass class method. 555 | valid-metaclass-classmethod-first-arg=cls 556 | 557 | 558 | [EXCEPTIONS] 559 | 560 | # Exceptions that will emit a warning when being caught. Defaults to 561 | # "Exception". 562 | overgeneral-exceptions=Exception 563 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to dashvis 2 | 3 | Looking to contribute something to dashvis? **Here's how you can help.** 4 | 5 | Please take a moment to review this document in order to make the contribution 6 | process easy and effective for everyone involved. 7 | 8 | The developers of dashvis work on this project in their spare 9 | time. Following these guidelines will help minimise the effort required to 10 | address your issue or assess your patches and features. Your help in making our 11 | lives easier is very much appreciated! 12 | 13 | ## Using the issue tracker 14 | 15 | The [issue tracker](https://github.com/lewkoo/dashvis/issues) is 16 | the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests) 17 | and [submitting pull requests](#pull-requests), but please respect the following 18 | restrictions: 19 | 20 | - Please **do not** use the issue tracker for personal support requests. Stack 21 | Overflow ([`plotly-dash`](https://stackoverflow.com/questions/tagged/plotly-dash) tag), 22 | or the [Plotly Community Forum](https://community.plot.ly) are better places to get help. 23 | 24 | - Please **do not** derail or troll issues. Keep the discussion on topic and 25 | respect the opinions of others. 26 | 27 | ## Bug reports 28 | 29 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 30 | Good bug reports are extremely helpful, so thanks! 31 | 32 | Guidelines for bug reports: 33 | 34 | 1. **Use the GitHub issue search** — check if the issue has already been 35 | reported. 36 | 37 | 2. **Check if the issue has been fixed** — try to reproduce it using the 38 | latest `main` or development branch in the repository. 39 | 40 | 3. **Isolate the problem** — ideally create a minimal working example that 41 | demonstrates the issue. 42 | 43 | A good bug report shouldn't leave others needing to chase you up for more 44 | information. Please try to be as detailed as possible in your report. What is 45 | your environment? Which versions of `dash` and `dash-bootstrap-components` do 46 | you have installed? What steps will reproduce the issue? What browser(s) and OS 47 | experience the problem? Do other browsers show the bug differently? What 48 | would you expect to be the outcome? All these details will help us to fix any 49 | potential bugs. We have a bug report template you can use to ensure you don't 50 | miss out any of these details when filing a [new bug report][new-bug-report]. 51 | 52 | ## Feature requests 53 | 54 | Feature requests are welcome. But take a moment to find out whether your idea 55 | fits with the scope and aims of the project. It's up to _you_ to make a strong 56 | case to convince the project's developers of the merits of this feature. Please 57 | provide as much detail and context as possible. We have a feature request 58 | template you can use when filing a [new feature request][new-feature-request]. 59 | 60 | ## Pull requests 61 | 62 | Good pull requests - patches, improvements, new features - are a fantastic 63 | help. They should remain focused in scope and avoid containing unrelated 64 | commits. 65 | 66 | **Please ask first** before embarking on any significant pull request (e.g. 67 | implementing features, refactoring code, porting to a different language), 68 | otherwise you risk spending a lot of time working on something that the 69 | project's developers might not want to merge into the project. 70 | 71 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the 72 | project (indentation, accurate comments, etc.) and any other requirements 73 | (such as test coverage). 74 | 75 | Adhering to the following process is the best way to get your work 76 | included in the project: 77 | 78 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, 79 | and configure the remotes: 80 | 81 | ```bash 82 | # Clone your fork of the repo into the current directory 83 | git clone https://github.com//dash-bootstrap-components.git 84 | # Navigate to the newly cloned directory 85 | cd dash-bootstrap-components 86 | # Assign the original repo to a remote called "upstream" 87 | git remote add upstream https://github.com/lewkoo/dashvis.git 88 | ``` 89 | 90 | 2. If you cloned a while ago, get the latest changes from upstream: 91 | 92 | ```bash 93 | git checkout main 94 | git pull upstream main 95 | ``` 96 | 97 | 3. Create a new topic branch (off the main project development branch) to 98 | contain your feature, change, or fix: 99 | 100 | ```bash 101 | git checkout -b 102 | ``` 103 | 104 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 105 | message guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 106 | or your code is unlikely be merged into the main project. Use Git's 107 | [interactive rebase](https://help.github.com/articles/about-git-rebase/) 108 | feature to tidy up your commits before making them public. 109 | 110 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 111 | 112 | ```bash 113 | git pull [--rebase] upstream main 114 | ``` 115 | 116 | 6. Push your topic branch up to your fork: 117 | 118 | ```bash 119 | git push origin 120 | ``` 121 | 122 | 7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/) 123 | with a clear title and description against the `main` branch. 124 | 125 | **IMPORTANT**: By submitting a patch, you agree to allow the project owners to 126 | license your work under the terms of the [Apache 2.0 License](../LICENSE). 127 | 128 | ## Code guidelines 129 | 130 | ### Python 131 | 132 | We use [`nox`](https://nox.thea.codes/en/stable/) to test and lint Python code. 133 | 134 | ```sh 135 | pip install nox 136 | ``` 137 | 138 | Code is linted using `black`, `flake8`, and `isort`. Run the linters with 139 | 140 | ```sh 141 | nox -s lint 142 | ``` 143 | 144 | Many formatting issues can be fixed automatically by `black` and `isort`. Run 145 | them with 146 | 147 | ```sh 148 | nox -s format 149 | ``` 150 | 151 | Finally you can run the Python tests locally for a particular version of Python 152 | with 153 | 154 | ```sh 155 | nox -s test-3.8 156 | ``` 157 | 158 | and similarly for other versions. 159 | 160 | ### JS 161 | 162 | Prettier to format JavaScript code as configured in `.prettierrc`. You can lint 163 | your code with 164 | 165 | ```bash 166 | npm run lint 167 | ``` 168 | 169 | and format it automatically with 170 | 171 | ```bash 172 | npm run format 173 | ``` 174 | 175 | ### Run tests 176 | 177 | Run `npm run test` before committing to ensure your changes pass our tests. 178 | 179 | ## Building dash-bootstrap-components locally 180 | 181 | To build _dash-bootstrap-components_ locally, first install the Python 182 | development dependencies 183 | 184 | ```sh 185 | python -m pip install -r requirements.txt 186 | ``` 187 | 188 | Then install JavaScript dependencies 189 | 190 | ```sh 191 | npm install 192 | ``` 193 | 194 | You can now build Python, R and Julia packages with 195 | 196 | ```sh 197 | npm run build 198 | ``` 199 | 200 | ## License 201 | 202 | By contributing your code, you agree to license your contribution under the 203 | [Apache 2.0 license](../LICENSE). 204 | 205 | [new-bug-report]: https://github.com/lewkoo/dashvis/issues/new?template=bug.md 206 | [new-feature-request]: https://github.com/lewkoo/dashvis/issues/new?template=feature.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Levko Ivanchuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE-2.0: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2022] [Levko Ivanchuk] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include dashvis/dashvis.min.js 2 | include dashvis/dashvis.min.js.map 3 | include dashvis/async-*.js 4 | include dashvis/async-*.js.map 5 | include dashvis/*-shared.js 6 | include dashvis/*-shared.js.map 7 | include dashvis/metadata.json 8 | include dashvis/package-info.json 9 | include README.md 10 | include LICENSE 11 | include package.json 12 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # AUTO GENERATED FILE - DO NOT EDIT 2 | 3 | export(DashNetwork) 4 | export(dashvis) 5 | export(dashNetwork) -------------------------------------------------------------------------------- /R/dashvis.R: -------------------------------------------------------------------------------- 1 | # AUTO GENERATED FILE - DO NOT EDIT 2 | 3 | #' @export 4 | dashvis <- function(id=NULL, label=NULL, value=NULL) { 5 | 6 | props <- list(id=id, label=label, value=value) 7 | if (length(props) > 0) { 8 | props <- props[!vapply(props, is.null, logical(1))] 9 | } 10 | component <- list( 11 | props = props, 12 | type = 'Dashvis', 13 | namespace = 'dashvis', 14 | propNames = c('id', 'label', 'value'), 15 | package = 'dashvis' 16 | ) 17 | 18 | structure(component, class = c('dash_component', 'list')) 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | vis.js logo 4 | 5 |

6 | 7 |

DashVis Component

8 | 9 |

10 | Full implementation of vis.js network framework for Plotly Dash 11 |
12 | Explore the documentation 13 | · 14 | Report a bug 15 | · 16 | Request a feature 17 |
18 |
19 | GitHub Actions 20 | GitHub 21 | npm 22 | PyPI 23 | PyPI - Python Version 24 |

25 | 26 | A full implementation of [vis.js][visjs-homepage] Network component for [Dash Plotly][dash-homepage]. Useful for displaying dynamic, automatically organised, customizable network views. 27 | 28 | ## Table of contents 29 | 30 | - [Table of contents](#table-of-contents) 31 | - [Installation](#installation) 32 | - [PyPI](#pypi) 33 | - [Quick start](#quick-start) 34 | - [See how it is used](#see-how-it-is-used) 35 | - [Advanced examples](#advanced-examples) 36 | - [Linking a stylesheet](#linking-a-stylesheet) 37 | - [Contributing](#contributing) 38 | - [Future work 🔨](#future-work-) 39 | 40 | ## Installation 41 | 42 | ### PyPI 43 | 44 | You can install _dashvis_ with `pip`: 45 | 46 | ```sh 47 | pip install dashvis 48 | ``` 49 | 50 | ## Quick start 51 | 52 | _dashvis_ exposes a single component, _DashNetwork_. Simply create one and include it in your Dash layout. Simplest example: 53 | 54 | ```python 55 | import dash 56 | from dash import html 57 | from dashvis import DashNetwork 58 | 59 | app = dash.Dash() 60 | app.layout = html.Div([ 61 | DashNetwork() 62 | ]) 63 | 64 | if __name__ == '__main__': 65 | app.run_server(debug=True) 66 | ``` 67 | 68 | ## See how it is used 69 | 70 | A simple usage example is provided in `usage.py`. 71 | 72 | 1. Run: 73 | ```shell 74 | python -m venv dashvis-venv 75 | source dashvis-venv/bin/activate 76 | pip install -r requirements.txt 77 | npm install 78 | npm run build 79 | python usage.py 80 | ``` 81 | 2. Visit in your web browser 82 | 83 | ## Advanced examples 84 | 85 | Examples which cover the entire API of `vis.js` are available in `usage_examples` folder. 86 | Running them requires PYTHONPATH to be made aware of `dashvis`. 87 | Simply run: 88 | ```shell 89 | export PYTHONPATH="${PYTHONPATH}:./dashvis" 90 | ```` 91 | and then run any example from repo root directory of the repository: 92 | ```shell 93 | python usage_examples/.py 94 | ``` 95 | 96 | ## Linking a stylesheet 97 | 98 | dashvis doesn't come with CSS included. 99 | If you enable network manipulation or navigation features, you need to include a stylesheet to draw those components of 100 | the network. 101 | 102 | For convenience, links to vis.js stylesheets are included for you and can be used as follows: 103 | 104 | ```python 105 | import dash 106 | import dashvis.stylesheets 107 | 108 | app = dash.Dash(external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET]) 109 | ``` 110 | 111 | ## Contributing 112 | 113 | See [CONTRIBUTING.md](./CONTRIBUTING.md) 114 | 115 | ## Future work 🔨 116 | 117 | - [x] Update `usage.py` 118 | - [x] Fix two disabled `vis.js` function calls 119 | - [ ] Write tests 120 | - [ ] Add tests and code coverage badges 121 | - [x] Update this README 122 | 123 | [dash-homepage]: https://dash.plotly.com/ 124 | [visjs-homepage]: https://visjs.github.io/vis-network/docs/network/ 125 | [bug-report]: https://github.com/lewkoo/dashvis/issues/new?template=bug.md 126 | [feature-request]: https://github.com/lewkoo/dashvis/issues/new?template=feature.md 127 | [contribution-guide]: https://github.com/lewkoo/dashvis/blob/main/.github/CONTRIBUTING.md 128 | -------------------------------------------------------------------------------- /_validate_init.py: -------------------------------------------------------------------------------- 1 | """ 2 | DO NOT MODIFY 3 | This file is used to validate your publish settings. 4 | """ 5 | from __future__ import print_function 6 | 7 | import os 8 | import sys 9 | import importlib 10 | 11 | 12 | components_package = 'dashvis' 13 | 14 | components_lib = importlib.import_module(components_package) 15 | 16 | missing_dist_msg = 'Warning {} was not found in `{}.__init__.{}`!!!' 17 | missing_manifest_msg = ''' 18 | Warning {} was not found in `MANIFEST.in`! 19 | It will not be included in the build! 20 | ''' 21 | 22 | with open('MANIFEST.in', 'r') as f: 23 | manifest = f.read() 24 | 25 | 26 | def check_dist(dist, filename): 27 | # Support the dev bundle. 28 | if filename.endswith('dev.js'): 29 | return True 30 | 31 | return any( 32 | filename in x 33 | for d in dist 34 | for x in ( 35 | [d.get('relative_package_path')] 36 | if not isinstance(d.get('relative_package_path'), list) 37 | else d.get('relative_package_path') 38 | ) 39 | ) 40 | 41 | 42 | def check_manifest(filename): 43 | return filename in manifest 44 | 45 | 46 | def check_file(dist, filename): 47 | if not check_dist(dist, filename): 48 | print( 49 | missing_dist_msg.format(filename, components_package, '_js_dist'), 50 | file=sys.stderr 51 | ) 52 | if not check_manifest(filename): 53 | print(missing_manifest_msg.format(filename), 54 | file=sys.stderr) 55 | 56 | 57 | for cur, _, files in os.walk(components_package): 58 | for f in files: 59 | 60 | if f.endswith('js'): 61 | # noinspection PyProtectedMember 62 | check_file(components_lib._js_dist, f) 63 | elif f.endswith('css'): 64 | # noinspection PyProtectedMember 65 | check_file(components_lib._css_dist, f) 66 | elif not f.endswith('py'): 67 | check_manifest(f) 68 | -------------------------------------------------------------------------------- /dashvis/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function as _ 2 | 3 | import os as _os 4 | import sys as _sys 5 | import json 6 | 7 | import dash as _dash 8 | 9 | # noinspection PyUnresolvedReferences 10 | from ._imports_ import * 11 | from ._imports_ import __all__ 12 | 13 | if not hasattr(_dash, '__plotly_dash') and not hasattr(_dash, 'development'): 14 | print('Dash was not successfully imported. ' 15 | 'Make sure you don\'t have a file ' 16 | 'named \n"dash.py" in your current directory.', file=_sys.stderr) 17 | _sys.exit(1) 18 | 19 | _basepath = _os.path.dirname(__file__) 20 | _filepath = _os.path.abspath(_os.path.join(_basepath, 'package-info.json')) 21 | with open(_filepath) as f: 22 | package = json.load(f) 23 | 24 | package_name = package['name'].replace(' ', '_').replace('-', '_') 25 | __version__ = package['version'] 26 | 27 | _current_path = _os.path.dirname(_os.path.abspath(__file__)) 28 | 29 | _this_module = _sys.modules[__name__] 30 | 31 | async_resources = [] 32 | 33 | _js_dist = [] 34 | 35 | _js_dist.extend( 36 | [ 37 | { 38 | "relative_package_path": "async-{}.js".format(async_resource), 39 | "external_url": ( 40 | "https://unpkg.com/{0}@{2}" 41 | "/{1}/async-{3}.js" 42 | ).format(package_name, __name__, __version__, async_resource), 43 | "namespace": package_name, 44 | "async": True, 45 | } 46 | for async_resource in async_resources 47 | ] 48 | ) 49 | 50 | # TODO: Figure out if unpkg link works 51 | _js_dist.extend( 52 | [ 53 | { 54 | "relative_package_path": "async-{}.js.map".format(async_resource), 55 | "external_url": ( 56 | "https://unpkg.com/{0}@{2}" 57 | "/{1}/async-{3}.js.map" 58 | ).format(package_name, __name__, __version__, async_resource), 59 | "namespace": package_name, 60 | "dynamic": True, 61 | } 62 | for async_resource in async_resources 63 | ] 64 | ) 65 | 66 | _js_dist.extend( 67 | [ 68 | { 69 | 'relative_package_path': 'dashvis.min.js', 70 | 'external_url': 'https://unpkg.com/{0}@{2}/{1}/{1}.min.js'.format( 71 | package_name, __name__, __version__), 72 | 'namespace': package_name 73 | }, 74 | { 75 | 'relative_package_path': 'dashvis.min.js.map', 76 | 'external_url': 'https://unpkg.com/{0}@{2}/{1}/{1}.min.js.map'.format( 77 | package_name, __name__, __version__), 78 | 'namespace': package_name, 79 | 'dynamic': True 80 | } 81 | ] 82 | ) 83 | 84 | _css_dist = [] 85 | 86 | 87 | for _component in __all__: 88 | setattr(locals()[_component], '_js_dist', _js_dist) 89 | setattr(locals()[_component], '_css_dist', _css_dist) 90 | -------------------------------------------------------------------------------- /dashvis/_imports_.py: -------------------------------------------------------------------------------- 1 | from .DashNetwork import DashNetwork 2 | from .stylesheets import VIS_NETWORK_STYLESHEET 3 | 4 | __all__ = [ 5 | "DashNetwork" 6 | ] -------------------------------------------------------------------------------- /dashvis/stylesheets.py: -------------------------------------------------------------------------------- 1 | VIS_NETWORK_STYLESHEET = "https://unpkg.com/vis-network@9.1.2/styles/vis-network.min.css" 2 | -------------------------------------------------------------------------------- /man/dashvis.Rd: -------------------------------------------------------------------------------- 1 | % Auto-generated: do not edit by hand 2 | \name{dashvis} 3 | 4 | \alias{dashvis} 5 | 6 | \title{Dashvis component} 7 | 8 | \description{ 9 | ExampleComponent is an example component. It takes a property, `label`, and displays it. It renders an input with the property `value` which is editable by the user. 10 | } 11 | 12 | \usage{ 13 | dashvis(id=NULL, label=NULL, value=NULL) 14 | } 15 | 16 | \arguments{ 17 | \item{id}{Character. The ID used to identify this component in Dash callbacks.} 18 | 19 | \item{label}{Character. A label that will be printed when this component is rendered.} 20 | 21 | \item{value}{Character. The value displayed in the input.} 22 | } 23 | 24 | \value{named list of JSON elements corresponding to React.js properties and their values} 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashvis", 3 | "version": "0.1.14", 4 | "description": "Dash Plotly implementation of vis.js library", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/lewkoo/dashvis.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/lewkoo/dashvis/issues" 11 | }, 12 | "homepage": "https://github.com/lewkoo/dashvis", 13 | "main": "build/index.js", 14 | "scripts": { 15 | "set_options": "export NODE_OPTIONS=--openssl-legacy-provider", 16 | "start": "npm run build && python usage.py", 17 | "validate-init": "python _validate_init.py", 18 | "prepublishOnly": "npm run validate-init", 19 | "publish:npm": "npm publish", 20 | "publish:pypi": "python -m twine upload dist/*", 21 | "publish:all": "nrm run publish:npm && npm run publish:pypi", 22 | "build:devjs": "npm run set_options && webpack --mode development", 23 | "build:js": "npm run set_options && webpack --mode production", 24 | "build:backends": "dash-generate-components ./src/lib/components dashvis -p package-info.json --r-prefix '' --jl-prefix '' --ignore \\.test\\.", 25 | "build:backends-activated": "(. dashvis-venv/bin/activate || dashvis-venv\\scripts\\activate && npm run build:backends)", 26 | "build": "npm run build:js && npm run build:backends", 27 | "build:dev": "npm run build:devjs && npm run build:backends", 28 | "build:activated": "npm run build:js && npm run build:backends-activated", 29 | "build:dev:activated": "npm run build:devjs && npm run build:backends-activated", 30 | "build:python": "python -m build --wheel --sdist", 31 | "build:all": "npm run build && npm run build:python", 32 | "build-and-publish": "npm run build:all && npm run publish:all" 33 | }, 34 | "author": "Levko Ivanchuk ", 35 | "license": "MIT, Apache-2.0", 36 | "dependencies": { 37 | "ramda": "^0.26.1", 38 | "vis-data": "^7.1.4", 39 | "vis-network": "^9.1.2" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.5.4", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.5.4", 44 | "@babel/preset-env": "^7.5.4", 45 | "@babel/preset-react": "^7.0.0", 46 | "@plotly/dash-component-plugins": "^1.2.0", 47 | "@plotly/webpack-dash-dynamic-import": "^1.2.0", 48 | "babel-eslint": "^10.0.2", 49 | "babel-loader": "^8.0.6", 50 | "copyfiles": "^2.1.1", 51 | "css-loader": "^6.8.1", 52 | "eslint": "^6.0.1", 53 | "eslint-config-prettier": "^6.0.0", 54 | "eslint-plugin-import": "^2.18.0", 55 | "eslint-plugin-react": "^7.14.2", 56 | "prop-types": "^15.7.2", 57 | "react": "^16.8.6", 58 | "react-docgen": "^4.1.1", 59 | "react-dom": "^16.8.6", 60 | "style-loader": "^0.23.1", 61 | "styled-jsx": "^5.1.2", 62 | "terser-webpack-plugin": "^2.3.0", 63 | "webpack": "^5.0.0", 64 | "webpack-cli": "^5.1.4" 65 | }, 66 | "engines": { 67 | "node": ">=8.11.0", 68 | "npm": ">=6.1.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=46.4.0", "wheel"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests/ 3 | addopts = -rsxX -vv 4 | log_format = %(asctime)s | %(levelname)s | %(name)s:%(lineno)d | %(message)s 5 | log_cli_level = ERROR 6 | -------------------------------------------------------------------------------- /readme_images/DashVis_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewkoo/dashvis/836a6b2f4b2f28f5259558485026863f1ab31dfa/readme_images/DashVis_Logo.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # dash is required to call `build:py` 2 | dash[dev,testing]>=2.0.0 3 | wheel 4 | dash-ace 5 | build 6 | twine 7 | pylint -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # dash is required to call `build:py` 2 | dash[dev]>=2.0.0 3 | dash-ace -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import json 2 | from setuptools import setup 3 | 4 | 5 | with open('package.json') as f: 6 | package = json.load(f) 7 | 8 | package_name = package["name"].replace(" ", "_").replace("-", "_") 9 | 10 | package_author = package['author'] 11 | package_author_name = package_author.split(" <")[0] 12 | package_author_email = package_author.split(" <")[1][:-1] 13 | 14 | with open('README.md', 'r', encoding='utf-8') as fh: 15 | long_description = fh.read() 16 | 17 | setup( 18 | name=package_name, 19 | version=package["version"], 20 | author=package_author_name, 21 | author_email=package_author_email, 22 | description=package.get('description', package_name), 23 | keywords='', 24 | long_description=long_description, 25 | long_description_content_type='text/markdown', 26 | url=package['homepage'], 27 | project_urls={ 28 | 'Documentation': 'https://github.com/lewkoo/dashvis/wiki', 29 | 'Bug Reports': 'https://github.com/lewkoo/dashvis/issues', 30 | 'Source Code': 'https://github.com/lewkoo/dashvis', 31 | 'Discussions': 'https://github.com/lewkoo/dashvis/discussions', 32 | 'Pull Requests': 'https://github.com/lewkoo/dashvis/pulls' 33 | }, 34 | packages=[package_name], 35 | include_package_data=True, 36 | license=package['license'], 37 | readme="README.md", 38 | install_requires=[], 39 | classifiers=[ 40 | # see https://pypi.org/classifiers/ 41 | 'Development Status :: 1 - Planning', 42 | 'Environment :: Web Environment', 43 | 'Framework :: Dash', 44 | 'Intended Audience :: Developers', 45 | 'License :: OSI Approved :: Apache Software License', 46 | 'License :: OSI Approved :: MIT License', 47 | 'Topic :: Software Development :: Build Tools', 48 | 'Programming Language :: Python :: 3.8', 49 | 'Programming Language :: Python :: 3.9', 50 | 'Programming Language :: Python :: 3.10', 51 | 'Programming Language :: Python :: 3 :: Only', 52 | 'Operating System :: OS Independent', 53 | ], 54 | python_requires='>=3.8', 55 | ) 56 | -------------------------------------------------------------------------------- /src/demo/App.js: -------------------------------------------------------------------------------- 1 | /* eslint no-magic-numbers: 0 */ 2 | import React, {Component} from 'react'; 3 | 4 | import {DashNetwork } from '../lib'; 5 | 6 | class App extends Component { 7 | 8 | constructor() { 9 | super(); 10 | this.state = { 11 | value: '' 12 | }; 13 | this.setProps = this.setProps.bind(this); 14 | } 15 | 16 | setProps(newProps) { 17 | this.setState(newProps); 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | 27 |
28 | ) 29 | } 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /src/demo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import DashNetwork from './components/DashNetwork.react'; 3 | 4 | export { 5 | DashNetwork 6 | }; 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewkoo/dashvis/836a6b2f4b2f28f5259558485026863f1ab31dfa/tests/__init__.py -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # Packages needed to run the tests. 2 | # Switch into a virtual environment 3 | # pip install -r requirements.txt 4 | 5 | dash[dev,testing]>=1.15.0 6 | -------------------------------------------------------------------------------- /tests/test_01_network_data.py: -------------------------------------------------------------------------------- 1 | from dash.testing.application_runners import import_app 2 | from selenium.webdriver import ActionChains 3 | from selenium.webdriver import Keys 4 | 5 | new_data_input = '''{ 6 | "nodes": [ 7 | { 8 | "id": 1, 9 | "cid": 1, 10 | "label": "Node 1", 11 | "title": "This is Node 1" 12 | }, 13 | { 14 | "id": 2, 15 | "cid": 1, 16 | "label": "Node 2", 17 | "title": "This is Node 2" 18 | }, 19 | { 20 | "id": 3, 21 | "cid": 1, 22 | "label": "Node 3", 23 | "title": "This is Node 3" 24 | } 25 | ], 26 | "edges": [ 27 | { 28 | "from": 1, 29 | "to": 3 30 | }, 31 | { 32 | "from": 1, 33 | "to": 2 34 | } 35 | ] 36 | } 37 | ''' 38 | 39 | 40 | def test_render_component(dash_duo): 41 | # Start a dash app contained as the variable `app` in `usage.py` 42 | app = import_app('usage_examples.01_network_data') 43 | dash_duo.start_server(app) 44 | 45 | # Get the generated component input with selenium 46 | # The html input will be a children of the #input dash component 47 | dash_duo.wait_for_element_by_id("network-data-input", timeout=None) 48 | data_input = dash_duo.find_element('#network-data-input > textarea') 49 | 50 | # ActionChains(dash_duo.get_webdriver()) \ 51 | # .move_to_element(data_input) \ 52 | # .double_click(data_input) \ 53 | # .key_down(Keys.CONTROL) \ 54 | # .send_keys('a') \ 55 | # .key_up(Keys.CONTROL) \ 56 | # .send_keys(Keys.DELETE) \ 57 | # .double_click(data_input) \ 58 | # .send_keys(new_data_input) \ 59 | # .perform() 60 | # # data_input.click() 61 | # # data_input.send_keys(Keys.CONTROL + "a") 62 | # # data_input.send_keys(Keys.DELETE) 63 | # 64 | # # data_input.send_keys(new_data_input) 65 | # 66 | # dash_duo.wait_for_text_to_equal('#network-data-input > textarea', new_data_input) 67 | # 68 | # # Clear the input 69 | # # dash_duo.clear_input(my_component) 70 | # 71 | # # Send keys to the custom input. 72 | # # my_component.send_keys('Hello dash') 73 | # 74 | # # Wait for the text to equal, if after the timeout (default 10 seconds) 75 | # # the text is not equal it will fail the test. 76 | # # dash_duo.wait_for_text_to_equal('#output', 'You have entered Hello dash') 77 | -------------------------------------------------------------------------------- /tests/test_usage.py: -------------------------------------------------------------------------------- 1 | # from dash.testing.application_runners import import_app 2 | # 3 | # 4 | # # Basic test for the component rendering. 5 | # # The dash_duo pytest fixture is installed with dash (v1.0+) 6 | # def test_render_component(dash_duo): 7 | # # Start a dash app contained as the variable `app` in `usage.py` 8 | # app = import_app('usage') 9 | # dash_duo.start_server(app) 10 | # 11 | # # Get the generated component input with selenium 12 | # # The html input will be a children of the #input dash component 13 | # my_component = dash_duo.find_element('#input > input') 14 | # 15 | # assert 'my-value' == my_component.get_attribute('value') 16 | # 17 | # # Clear the input 18 | # dash_duo.clear_input(my_component) 19 | # 20 | # # Send keys to the custom input. 21 | # my_component.send_keys('Hello dash') 22 | # 23 | # # Wait for the text to equal, if after the timeout (default 10 seconds) 24 | # # the text is not equal it will fail the test. 25 | # dash_duo.wait_for_text_to_equal('#output', 'You have entered Hello dash') 26 | -------------------------------------------------------------------------------- /usage.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import dash 4 | import dash_ace 5 | from dash import html 6 | from dash.dependencies import Input, Output 7 | from dashvis import DashNetwork 8 | from usage_examples._common import * 9 | 10 | app = dash.Dash(__name__) 11 | 12 | network = DashNetwork( 13 | id='network', 14 | style={'height': '400px'}, 15 | options=default_options_, 16 | enableHciEvents=False, 17 | enablePhysicsEvents=False, 18 | enableOtherEvents=False 19 | ) 20 | 21 | data = { 22 | 'nodes': [ 23 | {'id': 1, 'cid': 1, 'label': 'Node 1', 'title': 'This is Node 1'}, 24 | {'id': 2, 'cid': 1, 'label': 'Node 2', 'title': 'This is Node 2'}, 25 | {'id': 3, 'cid': 1, 'label': 'Node 3', 'title': 'This is Node 3'}, 26 | {'id': 4, 'label': 'Node 4', 'title': 'This is Node 4'}, 27 | {'id': 5, 'label': 'Node 5', 'title': 'This is Node 5'} 28 | ], 29 | 'edges': [ 30 | {'from': 1, 'to': 3}, 31 | {'from': 1, 'to': 2}, 32 | {'from': 2, 'to': 4}, 33 | {'from': 2, 'to': 5} 34 | ] 35 | } 36 | 37 | app.layout = html.Div([ 38 | html.Header("This demo demonstrates how to inject network data into the Dash network component. \n" 39 | "Feel free to change the data structure in the editor below and see it being updated live in the graph"), 40 | network, 41 | html.Br(), 42 | html.Table([ 43 | html.Tbody([ 44 | html.Tr([ 45 | html.Td([ 46 | html.Label("Network data input"), 47 | dash_ace.DashAceEditor( 48 | id='network-data-input', 49 | value='''{ 50 | "nodes": [ 51 | { 52 | "id": 1, 53 | "cid": 1, 54 | "label": "Node 1", 55 | "title": "This is Node 1" 56 | }, 57 | { 58 | "id": 2, 59 | "cid": 1, 60 | "label": "Node 2", 61 | "title": "This is Node 2" 62 | }, 63 | { 64 | "id": 3, 65 | "cid": 1, 66 | "label": "Node 3", 67 | "title": "This is Node 3" 68 | }, 69 | { 70 | "id": 4, 71 | "label": "Node 4", 72 | "title": "This is Node 4" 73 | }, 74 | { 75 | "id": 5, 76 | "label": "Node 5", 77 | "title": "This is Node 5" 78 | } 79 | ], 80 | "edges": [ 81 | { 82 | "from": 1, 83 | "to": 3 84 | }, 85 | { 86 | "from": 1, 87 | "to": 2 88 | }, 89 | { 90 | "from": 2, 91 | "to": 4 92 | }, 93 | { 94 | "from": 2, 95 | "to": 5 96 | } 97 | ] 98 | } 99 | ''', 100 | theme='github', 101 | mode='python', 102 | tabSize=4, 103 | height='400px', 104 | enableBasicAutocompletion=False, 105 | enableLiveAutocompletion=False, 106 | placeholder='Python code ...' 107 | ) 108 | ]), 109 | html.Td([ 110 | html.Label("Network data output"), 111 | dash_ace.DashAceEditor( 112 | id='network-data-output', 113 | value="", 114 | theme='github', 115 | mode='python', 116 | readOnly=True, 117 | tabSize=4, 118 | height='400px', 119 | enableBasicAutocompletion=False, 120 | enableLiveAutocompletion=False, 121 | placeholder='Python code ...' 122 | ) 123 | ]) 124 | ]) 125 | ]) 126 | ]), 127 | ]) 128 | 129 | server = app.server 130 | 131 | 132 | @app.callback(Output('network-data-output', 'value'), 133 | Input('network', 'data')) 134 | def from_network_to_output(data): 135 | return json.dumps(data, sort_keys=False, indent=2) 136 | 137 | 138 | @app.callback( 139 | Output('network', 'data'), 140 | Input('network-data-input', 'value'), 141 | ) 142 | def from_input_to_network(data): 143 | if str_to_dict(data) != {}: 144 | return str_to_dict(data) 145 | else: 146 | raise dash.exceptions.PreventUpdate() 147 | 148 | 149 | if __name__ == '__main__': 150 | app.run_server(debug=True) 151 | -------------------------------------------------------------------------------- /usage_examples/01_network_data.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import dash 4 | import dash_ace 5 | from dash import html 6 | from dash.dependencies import Input, Output 7 | from dashvis import DashNetwork 8 | from usage_examples._common import * 9 | 10 | app = dash.Dash(__name__) 11 | 12 | network = DashNetwork( 13 | id='network', 14 | style={'height': '400px'}, 15 | options=default_options_, 16 | enableHciEvents=False, 17 | enablePhysicsEvents=False, 18 | enableOtherEvents=False 19 | ) 20 | 21 | data = { 22 | 'nodes': [ 23 | {'id': 1, 'cid': 1, 'label': 'Node 1', 'title': 'This is Node 1'}, 24 | {'id': 2, 'cid': 1, 'label': 'Node 2', 'title': 'This is Node 2'}, 25 | {'id': 3, 'cid': 1, 'label': 'Node 3', 'title': 'This is Node 3'}, 26 | {'id': 4, 'label': 'Node 4', 'title': 'This is Node 4'}, 27 | {'id': 5, 'label': 'Node 5', 'title': 'This is Node 5'} 28 | ], 29 | 'edges': [ 30 | {'from': 1, 'to': 3}, 31 | {'from': 1, 'to': 2}, 32 | {'from': 2, 'to': 4}, 33 | {'from': 2, 'to': 5} 34 | ] 35 | } 36 | 37 | app.layout = html.Div([ 38 | html.Header("This demo demonstrates how to inject network data into the Dash network component. \n" 39 | "Feel free to change the data structure in the editor below and see it being updated live in the graph"), 40 | network, 41 | html.Br(), 42 | html.Table([ 43 | html.Tbody([ 44 | html.Tr([ 45 | html.Td([ 46 | html.Label("Network data input"), 47 | dash_ace.DashAceEditor( 48 | id='network-data-input', 49 | value='''{ 50 | "nodes": [ 51 | { 52 | "id": 1, 53 | "cid": 1, 54 | "label": "Node 1", 55 | "title": "This is Node 1" 56 | }, 57 | { 58 | "id": 2, 59 | "cid": 1, 60 | "label": "Node 2", 61 | "title": "This is Node 2" 62 | }, 63 | { 64 | "id": 3, 65 | "cid": 1, 66 | "label": "Node 3", 67 | "title": "This is Node 3" 68 | }, 69 | { 70 | "id": 4, 71 | "label": "Node 4", 72 | "title": "This is Node 4" 73 | }, 74 | { 75 | "id": 5, 76 | "label": "Node 5", 77 | "title": "This is Node 5" 78 | } 79 | ], 80 | "edges": [ 81 | { 82 | "from": 1, 83 | "to": 3 84 | }, 85 | { 86 | "from": 1, 87 | "to": 2 88 | }, 89 | { 90 | "from": 2, 91 | "to": 4 92 | }, 93 | { 94 | "from": 2, 95 | "to": 5 96 | } 97 | ] 98 | } 99 | ''', 100 | theme='github', 101 | mode='python', 102 | tabSize=4, 103 | height='400px', 104 | enableBasicAutocompletion=False, 105 | enableLiveAutocompletion=False, 106 | placeholder='Python code ...' 107 | ) 108 | ]), 109 | html.Td([ 110 | html.Label("Network data output"), 111 | dash_ace.DashAceEditor( 112 | id='network-data-output', 113 | value="", 114 | theme='github', 115 | mode='python', 116 | readOnly=True, 117 | tabSize=4, 118 | height='400px', 119 | enableBasicAutocompletion=False, 120 | enableLiveAutocompletion=False, 121 | placeholder='Python code ...' 122 | ) 123 | ]) 124 | ]) 125 | ]) 126 | ]), 127 | ]) 128 | 129 | server = app.server 130 | 131 | 132 | @app.callback(Output('network-data-output', 'value'), 133 | Input('network', 'data')) 134 | def from_network_to_output(data): 135 | return json.dumps(data, sort_keys=False, indent=2) 136 | 137 | 138 | @app.callback( 139 | Output('network', 'data'), 140 | Input('network-data-input', 'value'), 141 | ) 142 | def from_input_to_network(data): 143 | if str_to_dict(data) != {}: 144 | return str_to_dict(data) 145 | else: 146 | raise dash.exceptions.PreventUpdate() 147 | 148 | 149 | if __name__ == '__main__': 150 | app.run_server(debug=True) 151 | -------------------------------------------------------------------------------- /usage_examples/02_options.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | import dash 5 | import dash_ace 6 | import dashvis.stylesheets 7 | from dash import html 8 | from dash.dependencies import Input, Output 9 | from dashvis import DashNetwork 10 | from usage_examples._common import default_options_ 11 | 12 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET]) 13 | 14 | # Enable vis.js network built-it configurator UI 15 | network_options = default_options_ 16 | network_options['configure']['enabled'] = True 17 | 18 | network = DashNetwork( 19 | id='network', 20 | style={'height': '400px'}, 21 | options=network_options, 22 | enableHciEvents=False, 23 | enablePhysicsEvents=False, 24 | enableOtherEvents=['configChange'] 25 | ) 26 | 27 | app.layout = html.Div([ 28 | html.Header("This demo enables vis-network built-in configurator UI and shows how one can subscribe " 29 | "to configuration changed events to get notified whenever network configuration is altered."), 30 | network, 31 | html.Br(), 32 | html.Table([ 33 | html.Tbody([ 34 | html.Tr([ 35 | html.Td([ 36 | html.Label("Configuration changed event output:"), 37 | dash_ace.DashAceEditor( 38 | id='configuration-changed-event-output', 39 | value="", 40 | theme='github', 41 | mode='python', 42 | readOnly=True, 43 | tabSize=4, 44 | height='400px', 45 | enableBasicAutocompletion=False, 46 | enableLiveAutocompletion=False, 47 | placeholder='Python code ...' 48 | ) 49 | ]), 50 | html.Td([ 51 | html.Label("Get options from configurator output:"), 52 | dash_ace.DashAceEditor( 53 | id='get-options-from-configurator-output', 54 | value="", 55 | theme='github', 56 | mode='python', 57 | readOnly=True, 58 | tabSize=4, 59 | height='400px', 60 | enableBasicAutocompletion=False, 61 | enableLiveAutocompletion=False, 62 | placeholder='Python code ...' 63 | ) 64 | ]) 65 | ]) 66 | ]) 67 | ]), 68 | ]) 69 | 70 | server = app.server 71 | 72 | 73 | @app.callback( 74 | Output('configuration-changed-event-output', 'value'), 75 | Input('network', 'configChange'), 76 | ) 77 | def from_input_to_network(data): 78 | return json.dumps(data, sort_keys=False, indent=2) 79 | 80 | 81 | @app.callback( 82 | Output('get-options-from-configurator-output', 'value'), 83 | Input('network', 'getOptionsFromConfigurator'), 84 | ) 85 | def from_input_to_network(data): 86 | return json.dumps(data, sort_keys=False, indent=2) 87 | 88 | 89 | if __name__ == '__main__': 90 | app.run_server(debug=True) 91 | -------------------------------------------------------------------------------- /usage_examples/03_browser_events.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pprint 3 | from datetime import datetime 4 | 5 | import dash 6 | import dash_ace 7 | import dashvis.stylesheets 8 | from dash import dcc 9 | from dash import html 10 | from dash.dependencies import Input, Output 11 | from dashvis import DashNetwork 12 | from usage_examples._common import default_options_ 13 | 14 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 15 | suppress_callback_exceptions=True) 16 | 17 | network = DashNetwork( 18 | id='network', 19 | style={'height': '400px'}, 20 | options=default_options_, 21 | enableHciEvents=True, 22 | enablePhysicsEvents=False, 23 | enableOtherEvents=False 24 | ) 25 | 26 | app.layout = html.Div([ 27 | html.Header("This demo shows how one can process various user-driven events from the network component."), 28 | network, 29 | html.Br(), 30 | html.H4("Select HCI type callback event type below: "), 31 | html.Div([ 32 | dcc.Tabs(id="hci-event-listeners-tabs", value='hci-event-listener-tabs', vertical=True, children=[ 33 | dcc.Tab(label='Click', value='click-tab'), 34 | dcc.Tab(label='Double Click', value='double-click-tab'), 35 | dcc.Tab(label='On Context', value='on-context-tab'), 36 | dcc.Tab(label='Hold and Release', value='hold-release-tab'), 37 | dcc.Tab(label='Select', value='select-tab'), 38 | dcc.Tab(label='Select - Deselect Node and Edge', value='select-deselect-tab'), 39 | dcc.Tab(label='Dragging', value='dragging-tab'), 40 | dcc.Tab(label='Control node dragging', value='control-node-dragging-tab'), 41 | dcc.Tab(label='Hover - Blur Node and Edge', value='hover-blur-tab'), 42 | dcc.Tab(label='Zoom', value='zoom-tab'), 43 | dcc.Tab(label='Popup', value='popup-tab'), 44 | ]), 45 | html.Div(id='hci-event-listeners-tabs-content') 46 | ], style={'width': '100%', 'display': 'flex'}), 47 | ]) 48 | 49 | 50 | @app.callback(Output('hci-event-listeners-tabs-content', 'children'), 51 | Input('hci-event-listeners-tabs', 'value')) 52 | def render_hci_content(tab): 53 | if tab == 'click-tab': 54 | return dcc.Markdown(id='click_output') 55 | elif tab == 'double-click-tab': 56 | return dcc.Markdown(id='double_click_output') 57 | elif tab == 'on-context-tab': 58 | return dcc.Markdown(id='on_context_output') 59 | elif tab == 'hold-release-tab': 60 | return html.Div([ 61 | dcc.Markdown(id='hold_output'), 62 | dcc.Markdown(id='release_output') 63 | ]) 64 | elif tab == 'select-tab': 65 | return dcc.Markdown(id='select_output') 66 | elif tab == 'select-deselect-tab': 67 | return html.Div([ 68 | dcc.Markdown(id='select_node_output'), 69 | dcc.Markdown(id='select_edge_output'), 70 | dash_ace.DashAceEditor( 71 | id='deselect_node_output', 72 | value="", 73 | theme='github', 74 | mode='python', 75 | readOnly=True, 76 | tabSize=4, 77 | height='400px', 78 | enableBasicAutocompletion=False, 79 | enableLiveAutocompletion=False, 80 | placeholder='Python code ...' 81 | ), 82 | dash_ace.DashAceEditor( 83 | id='deselect_edge_output', 84 | value="", 85 | theme='github', 86 | mode='python', 87 | readOnly=True, 88 | tabSize=4, 89 | height='400px', 90 | enableBasicAutocompletion=False, 91 | enableLiveAutocompletion=False, 92 | placeholder='Python code ...' 93 | ), 94 | ]) 95 | elif tab == 'dragging-tab': 96 | return html.Div([ 97 | dcc.Markdown(id='drag_start_output'), 98 | dcc.Markdown(id='dragging_output'), 99 | dcc.Markdown(id='drag_end_output') 100 | ]) 101 | elif tab == 'control-node-dragging-tab': 102 | return html.Div([ 103 | dcc.Markdown(id='control_node_dragging_output'), 104 | dcc.Markdown(id='control_node_drag_end_output') 105 | ]) 106 | elif tab == 'hover-blur-tab': 107 | return html.Div([ 108 | dcc.Markdown(id='hover_node_output'), 109 | dcc.Markdown(id='blur_node_output'), 110 | dcc.Markdown(id='hover_edge_output'), 111 | dcc.Markdown(id='blur_edge_output') 112 | ]) 113 | elif tab == 'zoom-tab': 114 | return html.Div([ 115 | dcc.Markdown(id='zoom_output') 116 | ]) 117 | elif tab == 'popup-tab': 118 | return html.Div([ 119 | dcc.Markdown(id='show_popup_output'), 120 | dcc.Markdown(id='hide_popup_output') 121 | ]) 122 | 123 | 124 | @app.callback( 125 | Output('click_output', 'children'), 126 | Input('network', 'click') 127 | ) 128 | def capture_click_output(click): 129 | return ''' 130 | Click event produced: 131 | ``` 132 | {} 133 | ```'''.format(pprint.pformat(click, indent=4, width=200, compact=False, sort_dicts=True)) 134 | 135 | 136 | @app.callback( 137 | Output('double_click_output', 'children'), 138 | Input('network', 'doubleClick') 139 | ) 140 | def capture_double_click_output(double_click): 141 | return ''' 142 | Double click event produced: 143 | ``` 144 | {} 145 | ```'''.format(pprint.pformat(double_click, indent=4, width=200, compact=False, sort_dicts=True)) 146 | 147 | 148 | @app.callback( 149 | Output('on_context_output', 'children'), 150 | Input('network', 'oncontext') 151 | ) 152 | def capture_on_context_output(oncontext): 153 | return ''' 154 | On context event produced: 155 | ``` 156 | {} 157 | ```'''.format(pprint.pformat(oncontext, indent=4, width=200, compact=False, sort_dicts=True)) 158 | 159 | 160 | @app.callback( 161 | Output('hold_output', 'children'), 162 | [Input('network', 'hold')] 163 | ) 164 | def capture_hold_output(hold): 165 | return ''' 166 | Hold event produced: 167 | ``` 168 | {} 169 | ```'''.format(pprint.pformat(hold, indent=4, width=200, compact=False, sort_dicts=True)) 170 | 171 | 172 | @app.callback( 173 | Output('release_output', 'children'), 174 | [Input('network', 'release')] 175 | ) 176 | def capture_release_output(release): 177 | return ''' 178 | Release event produced: 179 | ``` 180 | {} 181 | ```'''.format(pprint.pformat(release, indent=4, width=200, compact=False, sort_dicts=True)) 182 | 183 | 184 | @app.callback( 185 | Output('select_output', 'children'), 186 | Input('network', 'select') 187 | ) 188 | def capture_select_output(select): 189 | return ''' 190 | Selection event produced: 191 | ``` 192 | {} 193 | ```'''.format(pprint.pformat(select, indent=4, width=200, compact=False, sort_dicts=True)) 194 | 195 | 196 | @app.callback( 197 | Output('select_node_output', 'children'), 198 | Input('network', 'selectNode') 199 | ) 200 | def capture_select_node_output(select_node): 201 | return ''' 202 | Select node event produced: 203 | ``` 204 | {} 205 | ```'''.format(pprint.pformat(select_node, indent=4, width=200, compact=False, sort_dicts=True)) 206 | 207 | 208 | @app.callback( 209 | Output('deselect_node_output', 'value'), 210 | Input('network', 'deselectNode') 211 | ) 212 | def capture_deselect_node_output(deselect_node): 213 | 214 | if deselect_node is not None: 215 | return json.dumps(deselect_node, sort_keys=False, indent=4) 216 | else: 217 | raise dash.exceptions.PreventUpdate 218 | 219 | @app.callback( 220 | Output('select_edge_output', 'children'), 221 | Input('network', 'selectEdge') 222 | ) 223 | def capture_select_edge_output(select_edge): 224 | return ''' 225 | Select edge event produced: 226 | ``` 227 | {} 228 | ```'''.format(pprint.pformat(select_edge, indent=4, width=200, compact=False, sort_dicts=True)) 229 | 230 | 231 | @app.callback( 232 | Output('deselect_edge_output', 'value'), 233 | Input('network', 'deselectEdge') 234 | ) 235 | def capture_deselect_output(deselect_edge): 236 | 237 | if deselect_edge is not None: 238 | return json.dumps(deselect_edge, sort_keys=False, indent=4) 239 | else: 240 | raise dash.exceptions.PreventUpdate 241 | 242 | @app.callback( 243 | Output('drag_start_output', 'children'), 244 | Input('network', 'dragStart') 245 | ) 246 | def capture_drag_start_output(drag_start): 247 | return ''' 248 | Drag start event produced: 249 | ``` 250 | {} 251 | ```'''.format(pprint.pformat(drag_start, indent=4, width=200, compact=False, sort_dicts=True)) 252 | 253 | 254 | @app.callback( 255 | Output('dragging_output', 'children'), 256 | Input('network', 'dragging') 257 | ) 258 | def capture_dragging_output(dragging): 259 | return ''' 260 | Dragging event produced: 261 | ``` 262 | {} 263 | ```'''.format(pprint.pformat(dragging, indent=4, width=200, compact=False, sort_dicts=True)) 264 | 265 | 266 | @app.callback( 267 | Output('drag_end_output', 'children'), 268 | Input('network', 'dragEnd') 269 | ) 270 | def capture_drag_end_output(drag_end): 271 | return ''' 272 | Drag end event produced: 273 | ``` 274 | {} 275 | ```'''.format(pprint.pformat(drag_end, indent=4, width=200, compact=False, sort_dicts=True)) 276 | 277 | 278 | @app.callback( 279 | Output('control_node_dragging_output', 'children'), 280 | Input('network', 'controlNodeDragging') 281 | ) 282 | def capture_control_node_dragging_output(control_node_dragging): 283 | return ''' 284 | Control node dragging event produced: 285 | ``` 286 | {} 287 | ```'''.format(pprint.pformat(control_node_dragging, indent=4, width=200, compact=False, sort_dicts=True)) 288 | 289 | 290 | @app.callback( 291 | Output('control_node_drag_end_output', 'children'), 292 | Input('network', 'controlNodeDragEnd') 293 | ) 294 | def capture_control_node_drag_end_output(drag_end): 295 | return ''' 296 | Control node drag end event produced: 297 | ``` 298 | {} 299 | ```'''.format(pprint.pformat(drag_end, indent=4, width=200, compact=False, sort_dicts=True)) 300 | 301 | 302 | @app.callback( 303 | Output('hover_node_output', 'children'), 304 | Input('network', 'hoverNode') 305 | ) 306 | def capture_hover_node_output(hover_node): 307 | return ''' 308 | Hover node event produced: 309 | ``` 310 | {} 311 | ```'''.format(pprint.pformat(hover_node, indent=4, width=200, compact=False, sort_dicts=True)) 312 | 313 | 314 | @app.callback( 315 | Output('blur_node_output', 'children'), 316 | Input('network', 'blurNode') 317 | ) 318 | def capture_blur_node_output(blur_node): 319 | if blur_node is not None: 320 | return ''' 321 | Blur node event produced: 322 | ``` 323 | {} 324 | ```'''.format(pprint.pformat(blur_node, indent=4, width=200, compact=False, sort_dicts=True)) 325 | 326 | 327 | @app.callback( 328 | Output('hover_edge_output', 'children'), 329 | Input('network', 'hoverEdge') 330 | ) 331 | def capture_hover_edge_output(hover_edge): 332 | return ''' 333 | Hover edge event produced: 334 | ``` 335 | {} 336 | ```'''.format(pprint.pformat(hover_edge, indent=4, width=200, compact=False, sort_dicts=True)) 337 | 338 | 339 | @app.callback( 340 | Output('blur_edge_output', 'children'), 341 | Input('network', 'blurEdge') 342 | ) 343 | def capture_deselect_output(blur_edge): 344 | if blur_edge is not None: 345 | return ''' 346 | Blur edge event produced: 347 | ``` 348 | {} 349 | ```'''.format(pprint.pformat(blur_edge, indent=4, width=200, compact=False, sort_dicts=True)) 350 | 351 | 352 | @app.callback( 353 | Output('zoom_output', 'children'), 354 | Input('network', 'zoom') 355 | ) 356 | def capture_deselect_output(zoom): 357 | if zoom is not None: 358 | return ''' 359 | Zoom event produced: 360 | ``` 361 | {} 362 | ```'''.format(pprint.pformat(zoom, indent=4, width=200, compact=False, sort_dicts=True)) 363 | 364 | 365 | @app.callback( 366 | Output('show_popup_output', 'children'), 367 | Input('network', 'showPopup') 368 | ) 369 | def capture_show_popup_output(show_popup): 370 | if show_popup is not None: 371 | return ''' 372 | Show popup event produced: 373 | ``` 374 | {} 375 | ```'''.format(pprint.pformat(show_popup, indent=4, width=200, compact=False, sort_dicts=True)) 376 | 377 | 378 | @app.callback( 379 | Output('hide_popup_output', 'children'), 380 | Input('network', 'hidePopup') 381 | ) 382 | def capture_hide_popup_output(hide_popup): 383 | return ''' 384 | Hide popup event produced: 385 | ``` 386 | {} 387 | ```'''.format(pprint.pformat(hide_popup, indent=4, width=200, compact=False, sort_dicts=True)) 388 | 389 | 390 | if __name__ == '__main__': 391 | app.run_server(debug=True) 392 | -------------------------------------------------------------------------------- /usage_examples/04_physics_events.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import dash 4 | import dashvis.stylesheets 5 | from dash import dcc 6 | from dash import html 7 | from dash.dependencies import Input 8 | from dash.dependencies import Output 9 | from dashvis import DashNetwork 10 | 11 | from usage_examples._common import default_options_ 12 | 13 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 14 | suppress_callback_exceptions=True) 15 | 16 | network = DashNetwork( 17 | id='network', 18 | style={'height': '400px'}, 19 | options=default_options_, 20 | enableHciEvents=False, 21 | enablePhysicsEvents=True, 22 | enableOtherEvents=False 23 | ) 24 | 25 | app.layout = html.Div([ 26 | html.Header("This demo shows how one can process various physics events from the network component."), 27 | network, 28 | html.Br(), 29 | html.H4("Select Physics type callback event type below: "), 30 | html.Div([ 31 | dcc.Tabs(id="physics-event-listeners-tabs", value='physics-event-listener-tabs', vertical=True, children=[ 32 | dcc.Tab(label='Start Stabilizing', value='start-stabilizing-tab'), 33 | dcc.Tab(label='Stabilization Progress', value='stabilization-progress-tab'), 34 | dcc.Tab(label='Stabilization Iterations Done', value='stabilization-iterations-done-tab'), 35 | dcc.Tab(label='Stabilized', value='stabilized-tab'), 36 | ]), 37 | html.Div(id='physics-event-listeners-tabs-content') 38 | ], style={'width': '100%', 'display': 'flex'}), 39 | ]) 40 | 41 | @app.callback(Output('physics-event-listeners-tabs-content', 'children'), 42 | Input('physics-event-listeners-tabs', 'value')) 43 | def render_physics_content(tab): 44 | if tab == 'start-stabilizing-tab': 45 | return dcc.Markdown(id='start_stabilizing_output') 46 | elif tab == 'stabilization-progress-tab': 47 | return dcc.Markdown(id='stabilization_progress_output') 48 | elif tab == 'stabilization-iterations-done-tab': 49 | return dcc.Markdown(id='stabilization_iterations_done_output') 50 | elif tab == 'stabilized-tab': 51 | return dcc.Markdown(id='stabilized_output'), 52 | 53 | @app.callback( 54 | Output('start_stabilizing_output', 'children'), 55 | Input('network', 'startStabilizing') 56 | ) 57 | def capture_start_stabilizing_output(startStabilizing): 58 | return ''' 59 | Start Stabilizing event produced: 60 | ``` 61 | {} 62 | ```'''.format(pprint.pformat(startStabilizing, indent=4, width=200, compact=False, sort_dicts=True)) 63 | 64 | 65 | @app.callback( 66 | Output('stabilization_progress_output', 'children'), 67 | Input('network', 'stabilizationProgress') 68 | ) 69 | def capture_stabilization_progress_output(stabilizationProgress): 70 | return ''' 71 | Stabilization Progress event produced: 72 | ``` 73 | {} 74 | ```'''.format(pprint.pformat(stabilizationProgress, indent=4, width=200, compact=False, sort_dicts=True)) 75 | 76 | 77 | @app.callback( 78 | Output('stabilization_iterations_done_output', 'children'), 79 | Input('network', 'stabilizationIterationsDone') 80 | ) 81 | def capture_stabilization_iterations_done_output(stabilizationIterationsDone): 82 | return ''' 83 | Stabilization iterations done event produced: 84 | ``` 85 | {} 86 | ```'''.format(pprint.pformat(stabilizationIterationsDone, indent=4, width=200, compact=False, sort_dicts=True)) 87 | 88 | 89 | @app.callback( 90 | Output('stabilized_output', 'children'), 91 | Input('network', 'stabilized') 92 | ) 93 | def capture_stabilized_output(stabilized): 94 | return ''' 95 | Stabilized event produced: 96 | ``` 97 | {} 98 | ```'''.format(pprint.pformat(stabilized, indent=4, width=200, compact=False, sort_dicts=True)) 99 | 100 | if __name__ == '__main__': 101 | app.run_server(debug=True) 102 | -------------------------------------------------------------------------------- /usage_examples/05_other_events.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import dash 4 | import dashvis.stylesheets 5 | from dash import dcc 6 | from dash import html 7 | from dash.dependencies import Input 8 | from dash.dependencies import Output 9 | from dashvis import DashNetwork 10 | 11 | from usage_examples._common import default_options_ 12 | 13 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 14 | suppress_callback_exceptions=True) 15 | 16 | network = DashNetwork( 17 | id='network', 18 | style={'height': '400px'}, 19 | options=default_options_, 20 | enableHciEvents=False, 21 | enablePhysicsEvents=False, 22 | enableOtherEvents=True 23 | ) 24 | 25 | app.layout = html.Div([ 26 | html.Header("This demo shows how one can process various other events from the network component."), 27 | network, 28 | html.Br(), 29 | html.Div([ 30 | dcc.Tabs(id="other-event-listeners-tabs", value='other-event-listener-tabs', vertical=True, children=[ 31 | dcc.Tab(label='Resize', value='resize-tab'), 32 | dcc.Tab(label='Init Redraw', value='init-redraw-tab'), 33 | dcc.Tab(label='Before Drawing', value='before-drawing-tab'), 34 | dcc.Tab(label='Animation Finished', value='animation-finished-tab'), 35 | dcc.Tab(label='Config Change', value='config-change-tab'), 36 | ]), 37 | html.Div(id='other-event-listeners-tabs-content') 38 | ], style={'width': '100%', 'display': 'flex'}), 39 | ]) 40 | 41 | @app.callback(Output('other-event-listeners-tabs-content', 'children'), 42 | Input('other-event-listeners-tabs', 'value')) 43 | def render_other_content(tab): 44 | if tab == 'resize-tab': 45 | return dcc.Markdown(id='resize_output') 46 | elif tab == 'init-redraw-tab': 47 | return dcc.Markdown(id='init_redraw_output') 48 | elif tab == 'before-drawing-tab': 49 | return dcc.Markdown(id='before_drawing_output') 50 | elif tab == 'animation-finished-tab': 51 | return dcc.Markdown(id='animation_finished_output') 52 | elif tab == 'config-change-tab': 53 | return dcc.Markdown(id='config_change_output'), 54 | 55 | @app.callback( 56 | Output('resize_output', 'children'), 57 | Input('network', 'resize') 58 | ) 59 | def capture_resize_output(resize): 60 | return ''' 61 | Resize event produced: 62 | ``` 63 | {} 64 | ```'''.format(pprint.pformat(resize, indent=4, width=200, compact=False, sort_dicts=True)) 65 | 66 | 67 | @app.callback( 68 | Output('init_redraw_output', 'children'), 69 | Input('network', 'initRedraw') 70 | ) 71 | def capture_init_redraw_output(init_redraw): 72 | return ''' 73 | Init redraw event produced: 74 | ``` 75 | {} 76 | ```'''.format(pprint.pformat(init_redraw, indent=4, width=200, compact=False, sort_dicts=True)) 77 | 78 | 79 | @app.callback( 80 | Output('before_drawing_output', 'children'), 81 | Input('network', 'beforeDrawing') 82 | ) 83 | def capture_before_drawing_output(before_drawing): 84 | return ''' 85 | Before drawing event produced: 86 | ``` 87 | {} 88 | ```'''.format(pprint.pformat(before_drawing, indent=4, width=200, compact=False, sort_dicts=True)) 89 | 90 | 91 | @app.callback( 92 | Output('after_drawing_output', 'children'), 93 | Input('network', 'afterDrawing') 94 | ) 95 | def capture_after_drawing_output(after_drawing): 96 | return ''' 97 | After drawing event produced: 98 | ``` 99 | {} 100 | ```'''.format(pprint.pformat(after_drawing, indent=4, width=200, compact=False, sort_dicts=True)) 101 | 102 | 103 | @app.callback( 104 | Output('animation_finished_output', 'children'), 105 | Input('network', 'animationFinished') 106 | ) 107 | def capture_animation_finished_output(animation_finished): 108 | return ''' 109 | Animation finished event produced: 110 | ``` 111 | {} 112 | ```'''.format(pprint.pformat(animation_finished, indent=4, width=200, compact=False, sort_dicts=True)) 113 | 114 | 115 | @app.callback( 116 | Output('config_change_output', 'children'), 117 | Input('network', 'configChange') 118 | ) 119 | def capture_config_change_output(config_change): 120 | return ''' 121 | Config change event produced: 122 | ``` 123 | {} 124 | ```'''.format(pprint.pformat(config_change, indent=4, width=200, compact=False, sort_dicts=True)) 125 | 126 | 127 | server = app.server 128 | 129 | if __name__ == '__main__': 130 | app.run_server(debug=True) 131 | -------------------------------------------------------------------------------- /usage_examples/06_custom_events.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_ace 3 | import dashvis.stylesheets 4 | from dash import State 5 | from dash import dcc 6 | from dash import html 7 | from dash.dependencies import Input 8 | from dash.dependencies import Output 9 | from dashvis import DashNetwork 10 | 11 | from usage_examples._common import default_options_ 12 | 13 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 14 | suppress_callback_exceptions=True) 15 | 16 | network_events = [ 17 | 'click', 18 | 'doubleClick', 19 | 'oncontext', 20 | 'hold', 21 | 'release', 22 | 'select', 23 | 'selectNode', 24 | 'selectEdge', 25 | 'deselectNode', 26 | 'deselectEdge', 27 | 'dragStart', 28 | 'dragging', 29 | 'dragEnd', 30 | 'controlNodeDragging', 31 | 'controlNodeDragEnd', 32 | 'hoverNode', 33 | 'blurNode', 34 | 'hoverEdge', 35 | 'blurEdge', 36 | 'zoom', 37 | 'showPopup', 38 | 'hidePopup', 39 | 'startStabilizing', 40 | 'stabilizationProgress', 41 | 'stabilizationIterationsDone', 42 | 'stabilized', 43 | 'resize', 44 | 'initRedraw', 45 | 'beforeDrawing', 46 | 'afterDrawing', 47 | 'animationFinished', 48 | 'configChange' 49 | ] 50 | 51 | network = DashNetwork( 52 | id='network', 53 | style={'height': '400px'}, 54 | options=default_options_, 55 | enableHciEvents=False, 56 | enablePhysicsEvents=False, 57 | enableOtherEvents=False 58 | ) 59 | 60 | app.layout = html.Div([ 61 | html.Header( 62 | "This demo shows how one can define custom front end function handlers for all events happening with the " 63 | "network component."), 64 | network, 65 | html.Br(), 66 | html.Div([ 67 | html.H4("Select event name:"), 68 | dcc.Dropdown(id='event-name-selection', options=network_events), 69 | html.H4("Write your custom JavaScript callback:"), 70 | dash_ace.DashAceEditor( 71 | id='callback-input', 72 | value='''function log() { 73 | // Write your code below 74 | }''', 75 | theme='github', 76 | mode='javascript', 77 | tabSize=4, 78 | width='20em', 79 | height='15em', 80 | enableBasicAutocompletion=False, 81 | enableLiveAutocompletion=False, 82 | placeholder='Javascript code ...' 83 | ), 84 | html.Button("Register custom callback", id="register-on-event"), 85 | html.Button("De-register custom callback", id="register-off-event"), 86 | html.Button("Execute callback once", id="register-once-event"), 87 | ], style={'maxWidth': '20em'}) 88 | ]) 89 | 90 | 91 | # noinspection PyUnusedLocal 92 | @app.callback(Output('network', 'on'), Input('register-on-event', 'n_clicks'), 93 | State('event-name-selection', 'value'), State('callback-input', 'value'), prevent_initial_callbacks=False) 94 | def register_single_on_function(n_clicks, selected_event, code_input): 95 | if not selected_event: 96 | return None 97 | else: 98 | return { 99 | 'event_name': selected_event, 100 | 'callback': code_input 101 | } 102 | 103 | 104 | # noinspection PyUnusedLocal 105 | @app.callback(Output('network', 'off'), Input('register-off-event', 'n_clicks'), 106 | State('event-name-selection', 'value'), State('callback-input', 'value'), prevent_initial_callbacks=False) 107 | def register_single_off_function(n_clicks, selected_event, code_input): 108 | if not selected_event: 109 | return None 110 | else: 111 | return { 112 | 'event_name': selected_event, 113 | 'callback': code_input 114 | } 115 | 116 | 117 | # noinspection PyUnusedLocal 118 | @app.callback(Output('network', 'once'), Input('register-once-event', 'n_clicks'), 119 | State('event-name-selection', 'value'), State('callback-input', 'value'), prevent_initial_callbacks=False) 120 | def register_single_once_function(n_clicks, selected_event, code_input): 121 | if not selected_event: 122 | return None 123 | else: 124 | return { 125 | 'event_name': selected_event, 126 | 'callback': code_input 127 | } 128 | 129 | 130 | server = app.server 131 | 132 | if __name__ == '__main__': 133 | app.run_server(debug=True) 134 | -------------------------------------------------------------------------------- /usage_examples/07_coordinate_conversion.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dashvis.stylesheets 3 | from dash import State 4 | from dash import dcc 5 | from dash import html 6 | from dash.dependencies import Input 7 | from dash.dependencies import Output 8 | from dashvis import DashNetwork 9 | 10 | from usage_examples._common import default_options_ 11 | 12 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 13 | suppress_callback_exceptions=True) 14 | 15 | network = DashNetwork( 16 | id='network', 17 | style={'height': '400px'}, 18 | options=default_options_, 19 | enableHciEvents=False, 20 | enablePhysicsEvents=False, 21 | enableOtherEvents=False 22 | ) 23 | 24 | app.layout = html.Div([ 25 | html.Header( 26 | "This demo shows how one can resize the network and " 27 | "convert coordinates between its internal coordinate system and the DOM."), 28 | network, 29 | html.Br(), 30 | html.Div([ 31 | html.H4("Resize network:"), 32 | html.Div([ 33 | html.A("Width: "), 34 | dcc.Input(id='width-input', value=500, type='number', placeholder="Width"), 35 | ]), 36 | html.Div([ 37 | html.A("Height: "), 38 | dcc.Input(id='height-input', value=500, type='number', placeholder="Height"), 39 | ]), 40 | html.Button("Resize", id="resize-button"), 41 | html.H4("Coordinate conversion:"), 42 | html.H5("Inputs:"), 43 | html.Div([ 44 | html.A("X: "), 45 | dcc.Input(id='x_input', value=100, type='number', placeholder="X"), 46 | ]), 47 | html.Div([ 48 | html.A("Y: "), 49 | dcc.Input(id='y_input', value=100, type='number', placeholder="Y"), 50 | ]), 51 | html.Button("Canvas to DOM", id="canvasToDom_button"), 52 | html.Button("DOM to Canvas", id="domToCanvas_button"), 53 | html.H5("Latest results:"), 54 | html.Div([ 55 | html.A("X: "), 56 | html.A(id='x_output') 57 | ]), 58 | html.Div([ 59 | html.A("Y: "), 60 | html.A(id='y_output') 61 | ]), 62 | ]), 63 | ]) 64 | 65 | 66 | # noinspection PyUnusedLocal 67 | @app.callback( 68 | [Output('network', 'canvasToDOM'), 69 | Output('network', 'DOMtoCanvas')], 70 | [Input('canvasToDom_button', 'n_clicks'), 71 | Input('domToCanvas_button', 'n_clicks')], 72 | [State('x_input', 'value'), 73 | State('y_input', 'value')], 74 | prevent_initial_callback=True 75 | ) 76 | def initiate_coordinate_conversion(ctd_n_clicks, dtc_n_clicks, x_input, y_input): 77 | ctx = dash.callback_context 78 | button_id = None 79 | if not ctx.triggered_id: 80 | raise dash.exceptions.PreventUpdate 81 | else: 82 | button_id = ctx.triggered_id 83 | 84 | canvasToDom = {} 85 | domToCanvas = {} 86 | 87 | if "canvasToDom" in button_id: 88 | canvasToDom = {'x': x_input, 'y': y_input} 89 | 90 | else: 91 | domToCanvas = {'x': x_input, 'y': y_input} 92 | 93 | return canvasToDom, domToCanvas 94 | 95 | 96 | @app.callback(Output('x_output', 'children'), 97 | Output('y_output', 'children'), 98 | Input('network', 'canvasToDOM'), 99 | Input('network', 'DOMtoCanvas'), 100 | prevent_initial_callbacks=True) 101 | def process_coordinate_conversion(canvasToDOM, DOMtoCanvas): 102 | if canvasToDOM is None and DOMtoCanvas is None: 103 | raise dash.exceptions.PreventUpdate 104 | 105 | result_obj = None 106 | if canvasToDOM['x'] and canvasToDOM['y']: 107 | result_obj = canvasToDOM 108 | elif DOMtoCanvas['x'] and DOMtoCanvas['y']: 109 | result_obj = DOMtoCanvas 110 | 111 | if not result_obj: 112 | raise dash.exceptions.PreventUpdate 113 | else: 114 | return result_obj['x'], result_obj['y'] 115 | 116 | 117 | @app.callback(Output('network', 'setSize'), [ 118 | Input('resize-button', 'n_clicks'), 119 | State('width-input', 'value'), 120 | State('height-input', 'value') 121 | ]) 122 | def resize_graph(n_clicks, cur_width, cur_height): 123 | if n_clicks is None: 124 | # PreventUpdate prevents ALL outputs updating 125 | raise dash.exceptions.PreventUpdate 126 | else: 127 | return {'width': str(cur_width), 'height': str(cur_height)} 128 | 129 | 130 | server = app.server 131 | 132 | if __name__ == '__main__': 133 | app.run_server(debug=True) 134 | -------------------------------------------------------------------------------- /usage_examples/08_clustering.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_ace 3 | import dashvis.stylesheets 4 | from dash import State 5 | from dash import dcc 6 | from dash import html 7 | from dash.dependencies import Input 8 | from dash.dependencies import Output 9 | from dashvis import DashNetwork 10 | from _common import str_to_dict, dict_to_str 11 | 12 | from usage_examples._common import default_options_ 13 | 14 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 15 | suppress_callback_exceptions=True) 16 | 17 | network = DashNetwork( 18 | id='network', 19 | style={'height': '400px'}, 20 | options=default_options_, 21 | enableHciEvents=False, 22 | enablePhysicsEvents=False, 23 | enableOtherEvents=False 24 | ) 25 | 26 | app.layout = html.Div([ 27 | html.Header( 28 | "This demo shows how one can control network clustering using Dash callbacks."), 29 | network, 30 | html.Br(), 31 | html.Div([ 32 | html.H4("Clustering"), 33 | html.H4("Write your custom joinCondition(...) callback:"), 34 | dash_ace.DashAceEditor( 35 | id='join-condition-input', 36 | value='''function(nodeOptions) { 37 | return nodeOptions.cid === 1; 38 | } 39 | // Note if using clusterByConnection then you need to provide a function which accepts two parameters (see vis.js docs) 40 | ''', 41 | theme='github', 42 | mode='javascript', 43 | tabSize=4, 44 | # width='20em', 45 | # height='15em', 46 | enableBasicAutocompletion=False, 47 | enableLiveAutocompletion=False, 48 | placeholder='Javascript code ...' 49 | ), 50 | html.H4("Write your custom processProperties(...) callback:"), 51 | dash_ace.DashAceEditor( 52 | id='process-properties-input', 53 | value='''function (clusterOptions, childNodes, childEdges) { 54 | return clusterOptions; 55 | } 56 | ''', 57 | theme='github', 58 | mode='javascript', 59 | tabSize=4, 60 | # width='20em', 61 | # height='15em', 62 | enableBasicAutocompletion=False, 63 | enableLiveAutocompletion=False, 64 | placeholder='Javascript code ...' 65 | ), 66 | html.H4("Write your custom clusterNodeProperties(...) dictionary:"), 67 | dash_ace.DashAceEditor( 68 | id='cluster-node-properties-input', 69 | value='''{ 70 | # Python dictionary with node properties 71 | } 72 | ''', 73 | theme='github', 74 | mode='python', 75 | tabSize=4, 76 | # width='20em', 77 | # height='15em', 78 | enableBasicAutocompletion=False, 79 | enableLiveAutocompletion=False, 80 | placeholder='Python code ...' 81 | ), 82 | html.H4("Write your custom clusterEdgeProperties(...) dictionary:"), 83 | dash_ace.DashAceEditor( 84 | id='cluster-edge-properties-input', 85 | value='''{ 86 | # Python dictionary with edge properties 87 | } 88 | ''', 89 | theme='github', 90 | mode='python', 91 | tabSize=4, 92 | # width='20em', 93 | # height='15em', 94 | enableBasicAutocompletion=False, 95 | enableLiveAutocompletion=False, 96 | placeholder='Python code ...' 97 | ), 98 | html.Div([ 99 | html.A("Node ID for cluster by connection: "), 100 | dcc.Input(id='cluster_node_id', value=1, type='number', placeholder="Node ID"), 101 | ]), 102 | html.Div([ 103 | html.A("Hubsize for cluster by hubsize: "), 104 | dcc.Input(id='hubsize', value=1, type='number', placeholder="Hubsize"), 105 | ]), 106 | html.Div( 107 | [ 108 | html.Button("Cluster", id="cluster_button"), 109 | html.Button("Cluster by connection", id="clusterByConnection_button"), 110 | html.Button("Cluster by hubsize", id="clusterByHubsize_button"), 111 | html.Button("Cluster outliers", id="clusterOutliers_button"), 112 | ] 113 | ), 114 | dash_ace.DashAceEditor( 115 | id='search_results', 116 | value="", 117 | theme='github', 118 | mode='python', 119 | tabSize=4, 120 | # width='20em', 121 | # height='15em', 122 | enableBasicAutocompletion=False, 123 | enableLiveAutocompletion=False, 124 | placeholder='Python code ...' 125 | ), 126 | html.Button("Find node", id="findNode_button"), 127 | ]) 128 | ]) 129 | 130 | @app.callback( 131 | Output('network', 'cluster'), 132 | Input('cluster_button', 'n_clicks'), 133 | State('join-condition-input', 'value'), 134 | State('process-properties-input', 'value'), 135 | State('cluster-node-properties-input', 'value'), 136 | State('cluster-edge-properties-input', 'value'), 137 | prevent_initial_callbacks=False 138 | ) 139 | def cluster(n_clicks, joinCondition, processProperties, clusterNodeProperties, clusterEdgeProperties): 140 | clusterNodeProperties = str_to_dict(clusterNodeProperties) 141 | clusterEdgeProperties = str_to_dict(clusterEdgeProperties) 142 | 143 | if not n_clicks: 144 | raise dash.exceptions.PreventUpdate 145 | else: 146 | return { 147 | 'options': { 148 | 'joinCondition': joinCondition, 149 | 'processProperties': processProperties, 150 | 'clusterNodeProperties': clusterNodeProperties, 151 | 'clusterEdgeProperties': clusterEdgeProperties 152 | } 153 | } 154 | 155 | 156 | @app.callback( 157 | Output('network', 'clusterByConnection'), 158 | Input('clusterByConnection_button', 'n_clicks'), 159 | State('cluster_node_id', 'value'), 160 | State('join-condition-input', 'value'), 161 | State('process-properties-input', 'value'), 162 | State('cluster-node-properties-input', 'value'), 163 | State('cluster-edge-properties-input', 'value'), 164 | prevent_initial_callbacks=False 165 | ) 166 | def clusterByConnection(n_clicks, cluster_node_id, joinCondition, processProperties, clusterNodeProperties, 167 | clusterEdgeProperties): 168 | clusterNodeProperties = str_to_dict(clusterNodeProperties) 169 | clusterEdgeProperties = str_to_dict(clusterEdgeProperties) 170 | 171 | if not n_clicks: 172 | raise dash.exceptions.PreventUpdate 173 | else: 174 | return { 175 | 'nodeId': str(cluster_node_id), 176 | 'options': { 177 | 'joinCondition': joinCondition, 178 | 'processProperties': processProperties, 179 | 'clusterNodeProperties': clusterNodeProperties, 180 | 'clusterEdgeProperties': clusterEdgeProperties 181 | } 182 | } 183 | 184 | 185 | @app.callback( 186 | Output('network', 'clusterByHubsize'), 187 | Input('clusterByHubsize_button', 'n_clicks'), 188 | State('hubsize', 'value'), 189 | State('join-condition-input', 'value'), 190 | State('process-properties-input', 'value'), 191 | State('cluster-node-properties-input', 'value'), 192 | State('cluster-edge-properties-input', 'value'), 193 | prevent_initial_callbacks=False 194 | ) 195 | def clusterByHubsize(n_clicks, hubsize, joinCondition, processProperties, clusterNodeProperties, 196 | clusterEdgeProperties): 197 | clusterNodeProperties = str_to_dict(clusterNodeProperties) 198 | clusterEdgeProperties = str_to_dict(clusterEdgeProperties) 199 | 200 | if not n_clicks: 201 | raise dash.exceptions.PreventUpdate 202 | else: 203 | return { 204 | 'hubsize': int(hubsize), 205 | 'options': { 206 | 'joinCondition': joinCondition, 207 | 'processProperties': processProperties, 208 | 'clusterNodeProperties': clusterNodeProperties, 209 | 'clusterEdgeProperties': clusterEdgeProperties 210 | } 211 | } 212 | 213 | 214 | @app.callback( 215 | Output('network', 'clusterOutliers'), 216 | Input('clusterOutliers_button', 'n_clicks'), 217 | State('join-condition-input', 'value'), 218 | State('process-properties-input', 'value'), 219 | State('cluster-node-properties-input', 'value'), 220 | State('cluster-edge-properties-input', 'value'), 221 | prevent_initial_callbacks=False 222 | ) 223 | def clusterOutliers(n_clicks, joinCondition, processProperties, clusterNodeProperties, 224 | clusterEdgeProperties): 225 | clusterNodeProperties = str_to_dict(clusterNodeProperties) 226 | clusterEdgeProperties = str_to_dict(clusterEdgeProperties) 227 | 228 | if not n_clicks: 229 | raise dash.exceptions.PreventUpdate 230 | else: 231 | return { 232 | 'options': { 233 | 'joinCondition': joinCondition, 234 | 'processProperties': processProperties, 235 | 'clusterNodeProperties': clusterNodeProperties, 236 | 'clusterEdgeProperties': clusterEdgeProperties 237 | } 238 | } 239 | 240 | 241 | @app.callback( 242 | Output('network', 'findNode'), 243 | Input('findNode_button', 'n_clicks'), 244 | State('cluster_node_id', 'value'), 245 | prevent_initial_callbacks=True 246 | ) 247 | def findNode(n_clicks, nodeId): 248 | if not n_clicks: 249 | raise dash.exceptions.PreventUpdate 250 | 251 | return { 252 | 'nodeId': int(nodeId), 253 | 'result': [''], 254 | } 255 | 256 | 257 | @app.callback( 258 | Output('search_results', 'value'), 259 | Input('network', 'findNode'), 260 | prevent_initial_callbacks=True 261 | ) 262 | def handleFindNodeOutput(results): 263 | return str(results['result']) 264 | 265 | server = app.server 266 | 267 | if __name__ == '__main__': 268 | app.run_server(debug=True) 269 | -------------------------------------------------------------------------------- /usage_examples/09_manipulation.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_ace 3 | import dashvis.stylesheets 4 | from dash import State 5 | from dash import dcc 6 | from dash import html 7 | from dash.dependencies import Input 8 | from dash.dependencies import Output 9 | from dashvis import DashNetwork 10 | from _common import str_to_dict, dict_to_str 11 | 12 | from usage_examples._common import default_options_ 13 | 14 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 15 | suppress_callback_exceptions=True) 16 | 17 | network = DashNetwork( 18 | id='network', 19 | style={'height': '400px'}, 20 | options=default_options_, 21 | enableHciEvents=False, 22 | enablePhysicsEvents=False, 23 | enableOtherEvents=False 24 | ) 25 | 26 | app.layout = html.Div([ 27 | html.Header( 28 | "This demo shows how one can control network manipulation using Dash callbacks."), 29 | network, 30 | html.Br(), 31 | html.Div([ 32 | html.H4("Manipulation methods:"), 33 | html.Button("Enabled Edit Mode", id="enableEditMode_button"), 34 | html.Button("Disable Edit Mode", id="disableEditMode_button"), 35 | html.Button("Add Mode Mode", id="addNodeMode_button"), 36 | html.Button("Edit Node", id="editNode_button"), 37 | html.Button("Add Edge Mode", id="addEdgeMode_button"), 38 | html.Button("Edit Edge Mode", id="editEdgeMode_button"), 39 | html.Button("Delete Selected", id="deleteSelected_button") 40 | ]) 41 | ]) 42 | 43 | callback_ouputs = { 44 | 'enableEditMode': Output('network', 'enableEditMode'), 45 | 'disableEditMode': Output('network', 'disableEditMode'), 46 | 'addNodeMode': Output('network', 'addNodeMode'), 47 | 'editNode': Output('network', 'editNode'), 48 | 'addEdgeMode': Output('network', 'addEdgeMode'), 49 | 'editEdgeMode': Output('network', 'editEdgeMode'), 50 | 'deleteSelected': Output('network', 'deleteSelected'), 51 | } 52 | 53 | # noinspection PyUnusedLocal 54 | @app.callback( 55 | output=callback_ouputs, 56 | inputs=dict( 57 | inputs={ 58 | 'enableEditMode_button': Input('enableEditMode_button', 'n_clicks'), 59 | 'disableEditMode_button': Input('disableEditMode_button', 'n_clicks'), 60 | 'addNodeMode_button': Input('addNodeMode_button', 'n_clicks'), 61 | 'editNode_button': Input('editNode_button', 'n_clicks'), 62 | 'addEdgeMode_button': Input('addEdgeMode_button', 'n_clicks'), 63 | 'editEdgeMode_button': Input('editEdgeMode_button', 'n_clicks'), 64 | 'deleteSelected_button': Input('deleteSelected_button', 'n_clicks'), 65 | } 66 | ), 67 | prevent_initial_callbacks=True 68 | ) 69 | def handle_manipulation_methods(inputs): 70 | # Get the button id 71 | ctx = dash.callback_context 72 | button_id = None 73 | if not ctx.triggered_id: 74 | raise dash.exceptions.PreventUpdate 75 | else: 76 | button_id = ctx.triggered_id 77 | 78 | outputs = {key: False for key in callback_ouputs.keys()} 79 | outputs[button_id.replace('_button', '')] = True 80 | 81 | return outputs 82 | 83 | server = app.server 84 | 85 | if __name__ == '__main__': 86 | app.run_server(debug=True) 87 | -------------------------------------------------------------------------------- /usage_examples/10_nodes_edges_info.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import dash 4 | import dash_ace 5 | import dashvis.stylesheets 6 | from dash import State 7 | from dash import dcc 8 | from dash import html 9 | from dash.dependencies import Input 10 | from dash.dependencies import Output 11 | from dashvis import DashNetwork 12 | from _common import str_to_dict, dict_to_str 13 | 14 | from usage_examples._common import default_options_ 15 | 16 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 17 | suppress_callback_exceptions=True) 18 | 19 | network = DashNetwork( 20 | id='network', 21 | style={'height': '400px'}, 22 | options=default_options_, 23 | enableHciEvents=False, 24 | enablePhysicsEvents=False, 25 | enableOtherEvents=False 26 | ) 27 | 28 | app.layout = html.Div([ 29 | html.Header( 30 | "This demo shows how one can use the API to get information on nodes and edges."), 31 | network, 32 | html.Br(), 33 | html.Div([ 34 | html.Div([ 35 | html.A("Comma-separated list of Node IDs: "), 36 | dcc.Input(id='node_ids_input', value='', type='text'), 37 | ]), 38 | html.Div([ 39 | html.A("Direction (for get connected nodes call): "), 40 | dcc.Dropdown(id='direction_dropdown', options=['from', 'to'], value='from'), 41 | ]), 42 | html.Button("Get Positions", id="getPositions_button"), 43 | html.Button("Get Position", id="getPosition_button"), 44 | html.Button("Get Bounding Box", id="getBoundingBox_button"), 45 | html.Button("Get Connected Nodes", id="getConnectedNodes_button"), 46 | html.Button("Get Connected Edges", id="getConnectedEdges_button"), 47 | dash_ace.DashAceEditor( 48 | id='results', 49 | value="", 50 | theme='github', 51 | mode='python', 52 | tabSize=4, 53 | # width='20em', 54 | height='15em', 55 | enableBasicAutocompletion=False, 56 | enableLiveAutocompletion=False, 57 | placeholder='Query results will be displayed here ...' 58 | ), 59 | html.H4("Move node:"), 60 | html.Div([ 61 | html.A("Node ID to move:"), 62 | dcc.Input(id='move_node_ids_input', value='', type='text'), 63 | ]), 64 | html.H5("X and Y coordinates must be in canvas space"), 65 | html.Div([ 66 | html.A("X: "), 67 | dcc.Input(id='x_input', value=100, type='number', placeholder="X"), 68 | ]), 69 | html.Div([ 70 | html.A("Y: "), 71 | dcc.Input(id='y_input', value=100, type='number', placeholder="Y"), 72 | ]), 73 | html.Button("Move node", id="moveNode_button"), 74 | ]) 75 | ]) 76 | 77 | callback_ouputs = { 78 | 'getPositions': Output('network', 'getPositions'), 79 | 'getPosition': Output('network', 'getPosition'), 80 | 'getBoundingBox': Output('network', 'getBoundingBox'), 81 | 'getConnectedNodes': Output('network', 'getConnectedNodes'), 82 | 'getConnectedEdges': Output('network', 'getConnectedEdges'), 83 | 'moveNode': Output('network', 'moveNode'), 84 | } 85 | 86 | 87 | # noinspection PyUnusedLocal 88 | @app.callback( 89 | output=callback_ouputs, 90 | inputs=dict( 91 | inputs={ 92 | 'getPositions_button': Input('getPositions_button', 'n_clicks'), 93 | 'getPosition_button': Input('getPosition_button', 'n_clicks'), 94 | 'getBoundingBox_button': Input('getBoundingBox_button', 'n_clicks'), 95 | 'getConnectedNodes_button': Input('getConnectedNodes_button', 'n_clicks'), 96 | 'getConnectedEdges_button': Input('getConnectedEdges_button', 'n_clicks'), 97 | 'moveNode_button': Input('moveNode_button', 'n_clicks'), 98 | }, 99 | state={ 100 | 'node_ids_input': State('node_ids_input', 'value'), 101 | 'direction_dropdown': State('direction_dropdown', 'value'), 102 | 'x_input': State('x_input', 'value'), 103 | 'y_input': State('y_input', 'value'), 104 | } 105 | ), 106 | prevent_initial_callbacks=True 107 | ) 108 | def handle_button_press(inputs, state): 109 | # Get the button id 110 | ctx = dash.callback_context 111 | button_id = None 112 | if not ctx.triggered_id: 113 | raise dash.exceptions.PreventUpdate 114 | else: 115 | button_id = ctx.triggered_id 116 | 117 | outputs = {key: None for key in callback_ouputs.keys()} 118 | 119 | node_ids = state['node_ids_input'].replace(' ', '').split(',') 120 | 121 | node_ids = node_ids if button_id == 'getPositions_button' else node_ids[0] 122 | 123 | if button_id == 'getPositions_button': 124 | outputs['getPositions'] = { 125 | 'nodeIds': node_ids, 126 | 'result': None 127 | } 128 | elif button_id == 'getConnectedNodes_button': 129 | outputs['getConnectedNodes'] = { 130 | 'nodeId': node_ids, 131 | 'direction': state['direction_dropdown'], 132 | 'result': [''] 133 | } 134 | elif button_id == 'moveNode_button': 135 | outputs['moveNode'] = { 136 | 'nodeId': node_ids, 137 | 'x': state['x_input'], 138 | 'y': state['y_input'] 139 | } 140 | else: 141 | outputs[button_id.replace('_button', '')] = { 142 | 'nodeId': node_ids, 143 | 'result': None 144 | } 145 | 146 | return outputs 147 | 148 | 149 | @app.callback( 150 | output=Output('results', 'value'), 151 | inputs=dict( 152 | inputs={ 153 | 'getPositions': Input('network', 'getPositions'), 154 | 'getPosition': Input('network', 'getPosition'), 155 | 'getBoundingBox': Input('network', 'getBoundingBox'), 156 | 'getConnectedNodes': Input('network', 'getConnectedNodes'), 157 | 'getConnectedEdges': Input('network', 'getConnectedEdges'), 158 | } 159 | ), 160 | prevent_initial_callbacks=True 161 | ) 162 | def handle_result_generation(inputs): 163 | # Get the result 164 | ctx = dash.callback_context 165 | 166 | output_data = "" 167 | for prop in ctx.triggered: 168 | if prop['value'] and any(key in prop['value'] for key in ['nodeId', 'nodeIds'] ): 169 | output_data += json.dumps(prop['value'], sort_keys=False, indent=2) 170 | 171 | return output_data 172 | 173 | 174 | server = app.server 175 | 176 | if __name__ == '__main__': 177 | app.run_server(debug=True) 178 | -------------------------------------------------------------------------------- /usage_examples/11_physics_control.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import dash 4 | import dashvis.stylesheets 5 | from dash import Input 6 | from dash import Output 7 | from dash import State 8 | from dash import dcc 9 | from dash import html 10 | from dashvis import DashNetwork 11 | 12 | from usage_examples._common import default_options_ 13 | 14 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 15 | suppress_callback_exceptions=True) 16 | 17 | # Generate a large random network 18 | def generate_network(): 19 | nodes = [] 20 | edges = [] 21 | # Generate random number of nodes 22 | num_nodes = random.randint(100, 1000) 23 | for i in range(0, num_nodes): 24 | nodes.append({ 25 | 'id': i, 26 | 'label': 'Node ' + str(i), 27 | 'title': 'This is Node ' + str(i), 28 | }) 29 | num_edges = random.sample(range(0, num_nodes), random.randint(5, num_nodes)) 30 | for i in num_edges: 31 | edges.append({ 32 | 'id': i, 33 | 'from': i, 34 | 'to': random.randint(0, num_nodes - 1), 35 | 'title': 'This is Edge ' + str(i), 36 | }) 37 | 38 | return { 39 | 'nodes': nodes, 40 | 'edges': edges 41 | } 42 | 43 | network_data = generate_network() 44 | default_options_['layout']['improvedLayout'] = False 45 | 46 | network = DashNetwork( 47 | id='network', 48 | data=network_data, 49 | style={'height': '400px'}, 50 | options=default_options_, 51 | enableHciEvents=False, 52 | enablePhysicsEvents=False, 53 | enableOtherEvents=False 54 | ) 55 | 56 | app.layout = html.Div([ 57 | html.Header( 58 | "This demo shows how one can use the API to control network physics."), 59 | network, 60 | html.Br(), 61 | html.Button("Start Simulation", id="startSimulation_button"), 62 | html.Button("Stop Simulation", id="stopSimulation_button"), 63 | html.Div([ 64 | html.A("Number of iterations: "), 65 | dcc.Input(id='num_iterations', value='600', type='number'), 66 | html.Button("Stabilise", id="stabilize_button"), 67 | ]), 68 | ]) 69 | 70 | callback_ouputs = { 71 | 'startSimulation': Output('network', 'startSimulation'), 72 | 'stopSimulation': Output('network', 'stopSimulation'), 73 | 'stabilize': Output('network', 'stabilize'), 74 | } 75 | 76 | 77 | # noinspection PyUnusedLocal 78 | @app.callback( 79 | output=callback_ouputs, 80 | inputs=dict( 81 | inputs={ 82 | 'startSimulation_button': Input('startSimulation_button', 'n_clicks'), 83 | 'stopSimulation_button': Input('stopSimulation_button', 'n_clicks'), 84 | 'stabilize_button': Input('stabilize_button', 'n_clicks'), 85 | }, 86 | state={ 87 | 'num_iterations': State('num_iterations', 'value'), 88 | } 89 | ), 90 | prevent_initial_callbacks=True 91 | ) 92 | def handle_button_press(inputs, state): 93 | # Get the button id 94 | ctx = dash.callback_context 95 | button_id = None 96 | if not ctx.triggered_id: 97 | raise dash.exceptions.PreventUpdate 98 | else: 99 | button_id = ctx.triggered_id 100 | 101 | outputs = {key: None for key in callback_ouputs.keys()} 102 | if button_id == 'stabilize_button': 103 | outputs[button_id.replace('_button', '')] = int(state['num_iterations']) 104 | else: 105 | outputs[button_id.replace('_button', '')] = True 106 | 107 | return outputs 108 | 109 | 110 | server = app.server 111 | 112 | if __name__ == '__main__': 113 | app.run_server(debug=True) 114 | -------------------------------------------------------------------------------- /usage_examples/12_selection_demo.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | import dash 5 | import dash_ace 6 | import dashvis.stylesheets 7 | from dash import Input 8 | from dash import Output 9 | from dash import State 10 | from dash import dcc 11 | from dash import html 12 | from dashvis import DashNetwork 13 | 14 | from usage_examples._common import default_options_ 15 | 16 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 17 | suppress_callback_exceptions=True) 18 | 19 | network = DashNetwork( 20 | id='network', 21 | style={'height': '400px'}, 22 | enableHciEvents=['select'], 23 | options=default_options_, 24 | enablePhysicsEvents=False, 25 | enableOtherEvents=False 26 | ) 27 | 28 | app.layout = html.Div([ 29 | html.Header( 30 | "This demo shows how one can use the API to control node/edge selection."), 31 | network, 32 | html.Br(), 33 | html.Div([ 34 | html.Button("Get Selection", id="getSelection_button"), 35 | html.Button("Get Selected Nodes", id="getSelectedNodes_button"), 36 | html.Button("Get Selected Edges", id="getSelectedEdges_button"), 37 | dash_ace.DashAceEditor( 38 | id='data-output', 39 | value='''{ 40 | # Here you'll see the output of the operations triggered by the buttons above. 41 | } 42 | ''', 43 | theme='github', 44 | mode='python', 45 | tabSize=4, 46 | # width='20em', 47 | height='25em', 48 | enableBasicAutocompletion=False, 49 | enableLiveAutocompletion=False, 50 | placeholder='Python code ...' 51 | ), 52 | ]), 53 | html.Div([ 54 | html.H5("X and Y coordinates must be in canvas space"), 55 | html.Div([ 56 | html.A("X: "), 57 | dcc.Input(id='x_input', value=100, type='number', placeholder="X"), 58 | ]), 59 | html.Div([ 60 | html.A("Y: "), 61 | dcc.Input(id='y_input', value=100, type='number', placeholder="Y"), 62 | ]), 63 | html.Button("Get Node At", id="getNodeAt_button"), 64 | html.Button("Get Edge At", id="getEdgeAt_button"), 65 | dash_ace.DashAceEditor( 66 | id='get-output', 67 | value='''{ 68 | # Here you'll see the output of the operations triggered by the buttons above. 69 | } 70 | ''', 71 | theme='github', 72 | mode='python', 73 | tabSize=4, 74 | # width='20em', 75 | height='25em', 76 | enableBasicAutocompletion=False, 77 | enableLiveAutocompletion=False, 78 | placeholder='Python code ...' 79 | ), 80 | ]), 81 | html.Div([ 82 | html.Div([ 83 | html.A("Node IDs:"), 84 | dcc.Input(id='node_ids_input', value='', type='text'), 85 | ]), 86 | html.Div([ 87 | html.A("Edge IDs:"), 88 | dcc.Input(id='edge_ids_input', value='', type='text'), 89 | ]), 90 | dcc.Checklist( 91 | options=[ 92 | {'label': 'Unselect All', 'value': 'unselectAll'}, 93 | {'label': 'Highlight Edges', 'value': 'highlightEdges'}, 94 | ], 95 | value=['highlightEdges'], 96 | id='extra_options' 97 | ), 98 | html.Button("Select Nodes", id="selectNodes_button"), 99 | html.Button("Select Edges", id="selectEdges_button"), 100 | html.Button("Set Selection", id="setSelection_button"), 101 | html.Button("Unselect All", id="unselectAll_button"), 102 | ]), 103 | ]) 104 | 105 | callback_ouputs = { 106 | 'getSelection': Output('network', 'getSelection'), 107 | 'getSelectedNodes': Output('network', 'getSelectedNodes'), 108 | 'getSelectedEdges': Output('network', 'getSelectedEdges'), 109 | } 110 | 111 | 112 | # # noinspection PyUnusedLocal 113 | @app.callback( 114 | output=callback_ouputs, 115 | inputs=dict( 116 | inputs={ 117 | 'getSelection_button': Input('getSelection_button', 'n_clicks'), 118 | 'getSelectedNodes_button': Input('getSelectedNodes_button', 'n_clicks'), 119 | 'getSelectedEdges_button': Input('getSelectedEdges_button', 'n_clicks'), 120 | } 121 | ), 122 | prevent_initial_callbacks=True 123 | ) 124 | def handle_button_press(inputs): 125 | # Get the button id 126 | ctx = dash.callback_context 127 | button_id = None 128 | if not ctx.triggered_id: 129 | raise dash.exceptions.PreventUpdate 130 | else: 131 | button_id = ctx.triggered_id 132 | 133 | outputs = {key: None for key in callback_ouputs.keys()} 134 | if button_id == 'getSelection_button': 135 | outputs[button_id.replace('_button', '')] = {} 136 | else: 137 | outputs[button_id.replace('_button', '')] = [] 138 | 139 | return outputs 140 | 141 | 142 | @app.callback( 143 | output=Output('data-output', 'value'), 144 | inputs=dict( 145 | inputs={ 146 | 'getSelection': Input('network', 'getSelection'), 147 | 'getSelectedNodes': Input('network', 'getSelectedNodes'), 148 | 'getSelectedEdges': Input('network', 'getSelectedEdges'), 149 | } 150 | ), 151 | prevent_initial_callbacks=True 152 | ) 153 | def handle_result_generation(inputs): 154 | # Get the result 155 | ctx = dash.callback_context 156 | 157 | output_data = "" 158 | for prop in ctx.triggered: 159 | output_data += json.dumps(prop, sort_keys=False, indent=2) 160 | 161 | return output_data 162 | 163 | 164 | # # noinspection PyUnusedLocal 165 | get_callback_ouputs = { 166 | 'getNodeAt': Output('network', 'getNodeAt'), 167 | 'getEdgeAt': Output('network', 'getEdgeAt'), 168 | } 169 | @app.callback( 170 | output=get_callback_ouputs, 171 | inputs=dict( 172 | inputs={ 173 | 'getNodeAt_button': Input('getNodeAt_button', 'n_clicks'), 174 | 'getEdgeAt_button': Input('getEdgeAt_button', 'n_clicks'), 175 | }, 176 | state={ 177 | 'x_input': State('x_input', 'value'), 178 | 'y_input': State('y_input', 'value'), 179 | } 180 | ), 181 | prevent_initial_callbacks=True 182 | ) 183 | def handle_get_button_press(inputs, state): 184 | # Get the button id 185 | ctx = dash.callback_context 186 | button_id = None 187 | if not ctx.triggered_id: 188 | raise dash.exceptions.PreventUpdate 189 | else: 190 | button_id = ctx.triggered_id 191 | 192 | outputs = {key: None for key in get_callback_ouputs.keys()} 193 | 194 | outputs[button_id.replace('_button', '')] = { 195 | 'position': { 196 | 'x': state['x_input'], 197 | 'y': state['y_input'], 198 | }, 199 | 'result': [] 200 | } 201 | 202 | return outputs 203 | 204 | 205 | @app.callback( 206 | output=Output('get-output', 'value'), 207 | inputs=dict( 208 | inputs={ 209 | 'getNodeAt': Input('network', 'getNodeAt'), 210 | 'getEdgeAt': Input('network', 'getEdgeAt'), 211 | } 212 | ), 213 | prevent_initial_callbacks=True 214 | ) 215 | def handle_get_results(inputs): 216 | # Get the result 217 | ctx = dash.callback_context 218 | 219 | output_data = "" 220 | for prop in ctx.triggered: 221 | output_data += json.dumps(prop, sort_keys=False, indent=2) 222 | 223 | return output_data 224 | 225 | select_ouputs = { 226 | 'selectNodes': Output('network', 'selectNodes'), 227 | 'selectEdges': Output('network', 'selectEdges'), 228 | 'setSelection': Output('network', 'setSelection'), 229 | 'unselectAll': Output('network', 'unselectAll'), 230 | } 231 | 232 | # noinspection PyUnusedLocal 233 | @app.callback( 234 | output=select_ouputs, 235 | inputs=dict( 236 | inputs={ 237 | 'selectNodes_button': Input('selectNodes_button', 'n_clicks'), 238 | 'selectEdges_button': Input('selectEdges_button', 'n_clicks'), 239 | 'setSelection_button': Input('setSelection_button', 'n_clicks'), 240 | 'unselectAll_button': Input('unselectAll_button', 'n_clicks'), 241 | }, 242 | state={ 243 | 'node_ids_input': State('node_ids_input', 'value'), 244 | 'edge_ids_input': State('edge_ids_input', 'value'), 245 | 'extra_options': State('extra_options', 'value'), 246 | } 247 | ), 248 | prevent_initial_callbacks=True 249 | ) 250 | def handle_button_press(inputs, state): 251 | # Get the button id 252 | ctx = dash.callback_context 253 | button_id = None 254 | if not ctx.triggered_id: 255 | raise dash.exceptions.PreventUpdate 256 | else: 257 | button_id = ctx.triggered_id 258 | 259 | outputs = {key: None for key in select_ouputs.keys()} 260 | outputs['unselectAll'] = False 261 | 262 | node_ids = state['node_ids_input'].replace(' ', '').split(',') if len(state['node_ids_input']) > 0 else [] 263 | edge_ids = state['edge_ids_input'].replace(' ', '').split(',') if len(state['edge_ids_input']) > 0 else [] 264 | unselectAll = 'unselectAll' in state['extra_options'] 265 | highlightEdges = 'highlightEdges' in state['extra_options'] 266 | 267 | if button_id == 'selectNodes_button': 268 | outputs['selectNodes'] = { 269 | 'nodeIds': node_ids, 270 | 'highlightEdges': highlightEdges 271 | } 272 | elif button_id == 'selectEdges_button': 273 | outputs['selectEdges'] = { 274 | 'edgeIds': edge_ids, 275 | } 276 | elif button_id == 'setSelection_button': 277 | outputs['setSelection'] = { 278 | 'selection': { 279 | 'nodes': node_ids if len(node_ids) > 0 else None, 280 | 'edges': edge_ids if len(edge_ids) > 0 else None, 281 | }, 282 | 'options': { 283 | 'unselectAll': unselectAll, 284 | 'highlightEdges': highlightEdges, 285 | } 286 | } 287 | elif button_id == 'unselectAll_button': 288 | outputs['unselectAll'] = True 289 | 290 | return outputs 291 | 292 | server = app.server 293 | 294 | if __name__ == '__main__': 295 | app.run_server(debug=True) 296 | -------------------------------------------------------------------------------- /usage_examples/13_viewport_control.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | import dash 5 | import dash_ace 6 | import dashvis.stylesheets 7 | from dash import Input 8 | from dash import Output 9 | from dash import State 10 | from dash import dcc 11 | from dash import html 12 | from dashvis import DashNetwork 13 | 14 | from usage_examples._common import default_options_ 15 | 16 | app = dash.Dash(__name__, external_stylesheets=[dashvis.stylesheets.VIS_NETWORK_STYLESHEET], 17 | suppress_callback_exceptions=True) 18 | 19 | network = DashNetwork( 20 | id='network', 21 | style={'height': '400px'}, 22 | options=default_options_, 23 | enableHciEvents=False, 24 | enablePhysicsEvents=False, 25 | enableOtherEvents=False 26 | ) 27 | 28 | app.layout = html.Div([ 29 | html.Header( 30 | "This demo shows how one can use the API to control network viewport."), 31 | network, 32 | html.Br(), 33 | html.Div([ 34 | html.Button("Get Scale", id="getScale_button"), 35 | html.Button("Get View Position", id="getViewPosition_button"), 36 | dash_ace.DashAceEditor( 37 | id='data-output', 38 | value='''{ 39 | # Here you'll see the output of the operations triggered by the buttons above. 40 | } 41 | ''', 42 | theme='github', 43 | mode='python', 44 | tabSize=4, 45 | # width='20em', 46 | height='25em', 47 | enableBasicAutocompletion=False, 48 | enableLiveAutocompletion=False, 49 | placeholder='Python code ...' 50 | ), 51 | ]), 52 | html.Div([ 53 | html.H5("Focus Node ID: "), 54 | dcc.Input(id='focus_node_id_input', value="", type='text', placeholder="Focus Node ID"), 55 | html.H5("Focus options:"), 56 | html.Div([ 57 | html.A("Scale:"), 58 | dcc.Input(id='focus_scale', value=5, type='number'), 59 | ]), 60 | html.Div([ 61 | html.A("Offset:"), 62 | html.A("X:"), 63 | dcc.Input(id='x_offset', value=0, type='number'), 64 | html.A("Y:"), 65 | dcc.Input(id='y_offset', value=0, type='number'), 66 | ]), 67 | html.Div([ 68 | dcc.Checklist( 69 | options=[{'label': 'Locked', 'value': 'locked'}], 70 | value=[], 71 | id='focus_locked' 72 | ), 73 | ]), 74 | html.Div([ 75 | dcc.Checklist( 76 | options=[ 77 | {'label': 'Animation enabled', 'value': 'enabled'}, 78 | ], 79 | value=[], 80 | id='animation_enabled' 81 | ), 82 | html.A("Duration:"), 83 | dcc.Input(id='animation_duration', value=0, type='number'), 84 | html.A("Easing function:"), 85 | dcc.Dropdown(id='easing_function', options=['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 86 | 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 87 | 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 'easeOutQuint', 88 | 'easeInOutQuint'], value='linear'), 89 | ]), 90 | html.Button("Focus", id="focus_button"), 91 | 92 | ]), 93 | html.Div([ 94 | html.H5("Move To Options: "), 95 | html.Div([ 96 | html.A("Scale:"), 97 | dcc.Input(id='move_to_scale', value=5, type='number'), 98 | ]), 99 | html.Div([ 100 | html.A("Position:"), 101 | html.A("X:"), 102 | dcc.Input(id='x_position', value=0, type='number'), 103 | html.A("Y:"), 104 | dcc.Input(id='y_position', value=0, type='number'), 105 | ]), 106 | html.Div([ 107 | html.A("Offset:"), 108 | html.A("X:"), 109 | dcc.Input(id='move_to_x_offset', value=0, type='number'), 110 | html.A("Y:"), 111 | dcc.Input(id='move_to_y_offset', value=0, type='number'), 112 | ]), 113 | html.Div([ 114 | dcc.Checklist( 115 | options=[{'label': 'Locked', 'value': 'locked'}], 116 | value=[], 117 | id='move_to_locked' 118 | ), 119 | ]), 120 | html.Div([ 121 | dcc.Checklist( 122 | options=[ 123 | {'label': 'Animation enabled', 'value': 'enabled'}, 124 | ], 125 | value=[], 126 | id='move_to_animation_enabled' 127 | ), 128 | html.A("Duration:"), 129 | dcc.Input(id='move_to_animation_duration', value=0, type='number'), 130 | html.A("Easing function:"), 131 | dcc.Dropdown(id='move_to_easing_function', options=['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 132 | 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 133 | 'easeInQuart', 134 | 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 135 | 'easeOutQuint', 136 | 'easeInOutQuint'], value='linear'), 137 | ]), 138 | html.Button("Move To", id="moveTo_button"), 139 | 140 | ]), 141 | html.Div([ 142 | html.H5("Fit Options: "), 143 | html.Div([ 144 | html.A("Min Zoom Level:"), 145 | dcc.Input(id='min_zoom_level', value=0.1, type='number', min=0, max=1, step=0.01), 146 | ]), 147 | html.Div([ 148 | html.A("Max Zoom Level:"), 149 | dcc.Input(id='max_zoom_level', value=0.8, type='number', min=0, max=1, step=0.01), 150 | ]), 151 | html.Div([ 152 | dcc.Checklist( 153 | options=[ 154 | {'label': 'Animation enabled', 'value': 'enabled'}, 155 | ], 156 | value=[], 157 | id='fit_animation_enabled' 158 | ), 159 | html.A("Duration:"), 160 | dcc.Input(id='fit_animation_duration', value=0, type='number'), 161 | html.A("Easing function:"), 162 | dcc.Dropdown(id='fit_easing_function', options=['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 163 | 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 164 | 'easeInQuart', 165 | 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 166 | 'easeOutQuint', 167 | 'easeInOutQuint'], value='linear'), 168 | ]), 169 | html.Button("Fit", id="fit_button"), 170 | ]) 171 | ]) 172 | 173 | callback_ouputs = { 174 | 'getScale': Output('network', 'getScale'), 175 | 'getViewPosition': Output('network', 'getViewPosition'), 176 | } 177 | 178 | 179 | # # noinspection PyUnusedLocal 180 | @app.callback( 181 | output=callback_ouputs, 182 | inputs=dict( 183 | inputs={ 184 | 'getScale_button': Input('getScale_button', 'n_clicks'), 185 | 'getViewPosition_button': Input('getViewPosition_button', 'n_clicks'), 186 | } 187 | ), 188 | prevent_initial_callbacks=True 189 | ) 190 | def handle_button_press(inputs): 191 | # Get the button id 192 | ctx = dash.callback_context 193 | button_id = None 194 | if not ctx.triggered_id: 195 | raise dash.exceptions.PreventUpdate 196 | else: 197 | button_id = ctx.triggered_id 198 | 199 | outputs = {key: None for key in callback_ouputs.keys()} 200 | if button_id == 'getScale_button': 201 | outputs[button_id.replace('_button', '')] = 0 202 | else: 203 | outputs[button_id.replace('_button', '')] = {} 204 | 205 | return outputs 206 | 207 | 208 | @app.callback( 209 | output=Output('data-output', 'value'), 210 | inputs=dict( 211 | inputs={ 212 | 'getScale': Input('network', 'getScale'), 213 | 'getViewPosition': Input('network', 'getViewPosition'), 214 | } 215 | ), 216 | prevent_initial_callbacks=True 217 | ) 218 | def handle_result_generation(inputs): 219 | # Get the result 220 | ctx = dash.callback_context 221 | 222 | output_data = "" 223 | for prop in ctx.triggered: 224 | output_data += json.dumps(prop, sort_keys=False, indent=2) 225 | 226 | return output_data 227 | 228 | 229 | @app.callback( 230 | Output('network', 'focus'), 231 | Input('focus_button', 'n_clicks'), 232 | State('focus_node_id_input', 'value'), 233 | State('focus_scale', 'value'), 234 | State('x_offset', 'value'), 235 | State('y_offset', 'value'), 236 | State('focus_locked', 'value'), 237 | State('animation_enabled', 'value'), 238 | State('animation_duration', 'value'), 239 | State('easing_function', 'value'), 240 | prevent_initial_callbacks=True 241 | ) 242 | def handle_focus(n_clicks, node_id, scale, x_offset, y_offset, locked, animation_enabled, 243 | animation_duration, easing_function): 244 | if not n_clicks: 245 | raise dash.exceptions.PreventUpdate 246 | 247 | return { 248 | 'nodeId': node_id, 249 | 'options': { 250 | 'scale': scale, 251 | 'offset': { 252 | 'x': x_offset, 253 | 'y': y_offset 254 | }, 255 | 'locked': False if not locked else True, 256 | 'animation': False if not animation_enabled else { 257 | 'duration': animation_duration, 258 | 'easingFunction': easing_function 259 | } 260 | } 261 | } 262 | 263 | 264 | @app.callback( 265 | Output('network', 'moveTo'), 266 | Input('moveTo_button', 'n_clicks'), 267 | State('move_to_scale', 'value'), 268 | State('x_position', 'value'), 269 | State('y_position', 'value'), 270 | State('move_to_x_offset', 'value'), 271 | State('move_to_y_offset', 'value'), 272 | State('move_to_locked', 'value'), 273 | State('move_to_animation_enabled', 'value'), 274 | State('move_to_animation_duration', 'value'), 275 | State('move_to_easing_function', 'value'), 276 | prevent_initial_callbacks=True 277 | ) 278 | def handle_move_to(n_clicks, scale, x_position, y_position, x_offset, y_offset, locked, animation_enabled, 279 | animation_duration, easing_function): 280 | if not n_clicks: 281 | raise dash.exceptions.PreventUpdate 282 | 283 | return { 284 | 'options': { 285 | 'position': { 286 | 'x': x_position, 287 | 'y': y_position 288 | }, 289 | 'scale': scale, 290 | 'offset': { 291 | 'x': x_offset, 292 | 'y': y_offset 293 | }, 294 | 'locked': False if not locked else True, 295 | 'animation': False if not animation_enabled else { 296 | 'duration': animation_duration, 297 | 'easingFunction': easing_function 298 | } 299 | } 300 | } 301 | 302 | 303 | @app.callback( 304 | Output('network', 'fit'), 305 | Input('fit_button', 'n_clicks'), 306 | State('focus_node_id_input', 'value'), 307 | State('min_zoom_level', 'value'), 308 | State('max_zoom_level', 'value'), 309 | State('fit_animation_enabled', 'value'), 310 | State('fit_animation_duration', 'value'), 311 | State('fit_easing_function', 'value'), 312 | prevent_initial_callbacks=True 313 | ) 314 | def handle_fit(n_clicks, node_id, min_zoom_level, max_zoom_level, animation_enabled, animation_duration, easing_function): 315 | if not n_clicks: 316 | raise dash.exceptions.PreventUpdate 317 | 318 | return { 319 | 'options': { 320 | 'nodes': [node_id] if node_id else [], 321 | 'minZoomLevel': min_zoom_level, 322 | 'maxZoomLevel': max_zoom_level, 323 | 'animation': False if not animation_enabled else { 324 | 'duration': animation_duration, 325 | 'easingFunction': easing_function 326 | } 327 | } 328 | } 329 | 330 | 331 | server = app.server 332 | 333 | if __name__ == '__main__': 334 | app.run_server(debug=True) 335 | -------------------------------------------------------------------------------- /usage_examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewkoo/dashvis/836a6b2f4b2f28f5259558485026863f1ab31dfa/usage_examples/__init__.py -------------------------------------------------------------------------------- /usage_examples/_common.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | height_ = '100%' 4 | width_ = '100%' 5 | 6 | nodes_ = { 7 | 'shape': 'box', 8 | 'margin': 10, 9 | 'size': 25, 10 | 'borderWidth': 2, 11 | 'borderWidthSelected': 2, 12 | 'font': { 13 | 'multi': 'markdown', 14 | 'align': 'center', 15 | }, 16 | 'labelHighlightBold': True, 17 | 'widthConstraint': { 18 | 'minimum': 30, 19 | 'maximum': 100, 20 | } 21 | } 22 | 23 | edges_ = { 24 | 'color': { 25 | 'inherit': 'both', 26 | }, 27 | 'arrows': { 28 | 'to': { 29 | 'enabled': True, 30 | 'scaleFactor': 0.5 31 | } 32 | }, 33 | 'chosen': False, 34 | "arrowStrikethrough": False, 35 | 'smooth': { 36 | 'type': "dynamic", 37 | 'roundness': 0.5, 38 | } 39 | } 40 | 41 | physics_ = { 42 | 'enabled': True, 43 | } 44 | 45 | manipulation_ = { 46 | 'enabled': True, 47 | 'initiallyActive': True, 48 | 'addNode': """function(nodeData,callback) { 49 | nodeData.label = 'hello world'; 50 | callback(nodeData); 51 | }""", 52 | 'addEdge': True, 53 | 'editNode': None, 54 | 'editEdge': True, 55 | 'deleteNode': True, 56 | 'deleteEdge': True, 57 | } 58 | 59 | interaction_ = { 60 | 'hover': True, 61 | 'hoverConnectedEdges': True, 62 | 'multiselect': True, 63 | 'keyboard': { 64 | 'enabled': True, 65 | 'bindToWindow': False, 66 | 'autoFocus': True, 67 | }, 68 | 'navigationButtons': True, 69 | } 70 | 71 | layout_ = { 72 | 'improvedLayout': True, 73 | } 74 | 75 | configure_ = { 76 | 'enabled': False, 77 | 'showButton': False, 78 | } 79 | 80 | default_options_ = dict(autoResize=True, height=height_, width=width_, configure=configure_, nodes=nodes_, edges=edges_, 81 | layout=layout_, interaction=interaction_, manipulation=manipulation_, physics=physics_, ) 82 | 83 | 84 | def str_to_dict(str_data: str) -> dict: 85 | try: 86 | return ast.literal_eval(str_data) 87 | except Exception as e: 88 | print(e) 89 | return {} 90 | 91 | def dict_to_str(dict_data: dict) -> str: 92 | try: 93 | return ast.literal_eval(dict_data) 94 | except Exception as e: 95 | print(e) 96 | return {} 97 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import'); 5 | const packagejson = require('./package.json'); 6 | 7 | const dashLibraryName = packagejson.name.replace(/-/g, '_'); 8 | 9 | module.exports = (env, argv) => { 10 | 11 | let mode; 12 | 13 | const overrides = module.exports || {}; 14 | 15 | // if user specified mode flag take that value 16 | if (argv && argv.mode) { 17 | mode = argv.mode; 18 | } 19 | 20 | // else if configuration object is already set (module.exports) use that value 21 | else if (overrides.mode) { 22 | mode = overrides.mode; 23 | } 24 | 25 | // else take webpack default (production) 26 | else { 27 | mode = 'production'; 28 | } 29 | 30 | let filename = (overrides.output || {}).filename; 31 | if(!filename) { 32 | const modeSuffix = mode === 'development' ? 'dev' : 'min'; 33 | filename = `${dashLibraryName}.${modeSuffix}.js`; 34 | } 35 | 36 | const entry = overrides.entry || {main: './src/lib/index.js'}; 37 | 38 | const devtool = overrides.devtool || 'source-map'; 39 | 40 | const externals = ('externals' in overrides) ? overrides.externals : ({ 41 | react: 'React', 42 | 'react-dom': 'ReactDOM', 43 | 'plotly.js': 'Plotly', 44 | 'prop-types': 'PropTypes', 45 | }); 46 | 47 | return { 48 | mode, 49 | entry, 50 | output: { 51 | path: path.resolve(__dirname, dashLibraryName), 52 | chunkFilename: '[name].js', 53 | filename, 54 | library: dashLibraryName, 55 | libraryTarget: 'window', 56 | }, 57 | devtool, 58 | externals, 59 | module: { 60 | rules: [ 61 | { 62 | test: /\.jsx?$/, 63 | exclude: /node_modules/, 64 | use: { 65 | loader: 'babel-loader', 66 | }, 67 | }, 68 | { 69 | test: /\.css$/, 70 | use: [ 71 | { 72 | loader: 'style-loader', 73 | options: { 74 | insertAt: 'top' 75 | } 76 | }, 77 | { 78 | loader: 'css-loader', 79 | }, 80 | ], 81 | }, 82 | ], 83 | }, 84 | optimization: { 85 | minimizer: [ 86 | new TerserPlugin({ 87 | sourceMap: true, 88 | parallel: true, 89 | cache: './.build_cache/terser', 90 | terserOptions: { 91 | warnings: false, 92 | ie8: false 93 | } 94 | }) 95 | ], 96 | splitChunks: { 97 | name: "dashvis-shared", 98 | cacheGroups: { 99 | async: { 100 | chunks: 'async', 101 | minSize: 0, 102 | name(module, chunks, cacheGroupKey) { 103 | return `${cacheGroupKey}-${chunks[0].name}`; 104 | } 105 | }, 106 | shared: { 107 | chunks: 'all', 108 | minSize: 0, 109 | minChunks: 2, 110 | name: 'dashvis-shared' 111 | } 112 | } 113 | } 114 | }, 115 | plugins: [ 116 | new WebpackDashDynamicImport(), 117 | new webpack.SourceMapDevToolPlugin({ 118 | filename: '[file].map', 119 | exclude: ['async-plotlyjs'] 120 | }) 121 | ] 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /webpack.serve.config.js: -------------------------------------------------------------------------------- 1 | const config = require('./webpack.config.js'); 2 | const path = require('path'); 3 | 4 | config.entry = {main: './src/demo/index.js'}; 5 | config.output = { 6 | filename: './output.js', 7 | path: path.resolve(__dirname), 8 | }; 9 | config.mode = 'development'; 10 | config.externals = undefined; // eslint-disable-line 11 | config.devtool = 'inline-source-map'; 12 | module.exports = config; 13 | --------------------------------------------------------------------------------