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 |
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 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------