├── .github ├── ISSUE_TEMPLATE │ ├── .config │ ├── feature-request.md │ ├── loacal-bug.md │ └── website-bug.md └── stale.yml ├── .gitignore ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── backend ├── .pylintrc ├── Dockerfile ├── default │ ├── groups.json │ ├── layer_types_current.json │ ├── legend_preferences.json │ ├── model_current.py │ └── preferences.json ├── models │ ├── 3d │ │ ├── groups.json │ │ ├── layer_types_current.json │ │ ├── legend_preferences.json │ │ ├── model_current.py │ │ └── preferences.json │ ├── monochrome │ │ ├── groups.json │ │ ├── layer_types_current.json │ │ ├── legend_preferences.json │ │ ├── model_current.py │ │ └── preferences.json │ ├── resnet │ │ ├── groups.json │ │ ├── layer_types_current.json │ │ ├── legend_preferences.json │ │ ├── model_current.py │ │ ├── preferences.json │ │ ├── visualizations.zip │ │ └── visualizations │ │ │ ├── graph.pdf │ │ │ ├── graph.svg │ │ │ ├── legend.pdf │ │ │ └── legend.svg │ └── unet │ │ ├── groups.json │ │ ├── layer_types_current.json │ │ ├── legend_preferences.json │ │ ├── model_current.py │ │ ├── preferences.json │ │ ├── visualizations.zip │ │ └── visualizations │ │ ├── graph.pdf │ │ ├── graph.svg │ │ ├── legend.pdf │ │ └── legend.svg ├── requirements.txt ├── server.py ├── translate │ ├── __init__.py │ ├── graph.py │ ├── keras_loader.txt │ ├── layer.py │ ├── translate_keras.py │ └── translate_onnx.py └── uwsgi.ini ├── net2vis ├── .gitignore ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.js │ ├── App.test.js │ ├── AppRouter.js │ ├── actions │ ├── index.js │ └── types.js │ ├── api │ ├── CodeApi.js │ ├── DownloadApi.js │ ├── GroupsApi.js │ ├── LayerTypesApi.js │ ├── LegendPreferencesApi.js │ ├── ModelApi.js │ ├── NetworkApi.js │ └── PreferencesApi.js │ ├── colors │ └── index.js │ ├── components │ ├── AlertSnack.js │ ├── MainComponent.js │ ├── code │ │ └── CodeComponent.js │ ├── controls │ │ ├── ControlsComponent.js │ │ └── ToggleButton.js │ ├── input │ │ └── InputField.js │ ├── legend │ │ ├── ComplexLegendItem.js │ │ ├── LegendComponent.js │ │ └── LegendItem.js │ ├── network │ │ ├── DimensionsLabelComponent.js │ │ ├── EdgeComponent.js │ │ ├── EdgesComponent.js │ │ ├── FeaturesLabelComponent.js │ │ ├── LayerComponent.js │ │ ├── NameLabelComponent.js │ │ ├── NetworkComponent.js │ │ ├── SampleComponent.js │ │ └── TooltipComponent.js │ ├── patterns │ │ └── PatternComponent.js │ └── preferences │ │ ├── FeaturesComponent.js │ │ ├── GroupPreferencesComponent.js │ │ ├── LayerPreferencesComponent.js │ │ ├── LegendPreferencesComponent.js │ │ ├── NetworkPreferencesComponent.js │ │ └── PreferencesComponent.js │ ├── graphs │ └── index.js │ ├── groups │ ├── Activation.js │ ├── Addition.js │ ├── Automation.js │ ├── Common.js │ ├── Concatenation.js │ ├── Duplicates.js │ ├── Grouping.js │ ├── Occurences.js │ ├── Removal.js │ └── Sort.js │ ├── index.js │ ├── layers │ ├── Common.js │ ├── Hiding.js │ └── Splitting.js │ ├── legend │ └── index.js │ ├── media │ ├── download_icon.png │ └── group_icon.png │ ├── paths │ └── index.js │ ├── reducers │ ├── AlertSnackReducer.js │ ├── CodeReducer.js │ ├── ColorModeReducer.js │ ├── CompressionReducer.js │ ├── DisplayReducer.js │ ├── ErrorReducer.js │ ├── GroupsReducer.js │ ├── IDReducer.js │ ├── LayerExtremeDimensionsReducer.js │ ├── LayerTypesSettingsReducer.js │ ├── LegendBboxReducer.js │ ├── LegendPreferencesReducer.js │ ├── LegendTransformReducer.js │ ├── NetworkBboxReducer.js │ ├── NetworkReducer.js │ ├── PreferencesModeReducer.js │ ├── PreferencesReducer.js │ ├── SelectedLegendItemReducer.js │ ├── SelectionReducer.js │ ├── TransformReducer.js │ ├── index.js │ └── initialState.js │ ├── registerServiceWorker.js │ ├── selection │ └── index.js │ ├── setupProxy.js │ └── styles │ └── index.scss ├── net2vis_teaser.png └── net2vis_teaser_legend.png /.github/ISSUE_TEMPLATE/.config: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question or Help 4 | url: mailto:alex.baeuerle@uni-ulm.de 5 | about: For questions or problems with Net2Vis, email me. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an improvement for Net2Vis. 4 | title: '' 5 | labels: 'feature-request' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the request** 10 | Clearly describe the goal of the feature request. Why is it needed? 11 | 12 | **How can it be tackled?** 13 | Describe an envisioned solution. What do you want the new behavior to be? 14 | 15 | **How will it look like?** 16 | If applicable, share mockup images or links to examples illustrating the desired output. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/loacal-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Loacal Bug 3 | about: Do you have a bug on your local installation of Net2Vis? 4 | title: '' 5 | labels: Local Instance 6 | assignees: Sparkier 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. macOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/website-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Website Bug 3 | about: Did you find a Bug on our Website? 4 | title: '' 5 | labels: Website 6 | assignees: Sparkier 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Network ID** 14 | The network id is what you find at the end of the URL, after `http://viscom.net2vis.uni-ulm.de/`. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. macOS] 24 | - Browser [e.g. chrome, safari] 25 | - Version [e.g. 22] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | critical 8 | # Label to use when marking an issue as stale 9 | staleLabel: stale 10 | # Comment to post when marking an issue as stale. Set to `false` to disable 11 | markComment: > 12 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. 13 | # Comment to post when closing a stale issue. Set to `false` to disable 14 | closeComment: > 15 | This issue has been automatically closed for inactivity. Feel free to reopen the issue if there is still need to tackle this. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Do not push the models! 2 | backend/models 3 | 4 | # Created by https://www.gitignore.io/api/code,python 5 | 6 | ### Code ### 7 | # Visual Studio Code - https://code.visualstudio.com/ 8 | .settings/ 9 | .vscode/ 10 | jsconfig.json 11 | 12 | ### Python ### 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | .pytest_cache/ 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | .hypothesis/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule.* 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | 112 | 113 | # End of https://www.gitignore.io/api/code,python 114 | 115 | 116 | # Created by https://www.gitignore.io/api/node,sass,linux,macos,windows 117 | 118 | ### Linux ### 119 | *~ 120 | 121 | # temporary files which can be created if a process still has a handle open of a deleted file 122 | .fuse_hidden* 123 | 124 | # KDE directory preferences 125 | .directory 126 | 127 | # Linux trash folder which might appear on any partition or disk 128 | .Trash-* 129 | 130 | # .nfs files are created when an open file is removed but is still being accessed 131 | .nfs* 132 | 133 | ### macOS ### 134 | *.DS_Store 135 | .AppleDouble 136 | .LSOverride 137 | 138 | # Icon must end with two \r 139 | Icon 140 | 141 | # Thumbnails 142 | ._* 143 | 144 | # Files that might appear in the root of a volume 145 | .DocumentRevisions-V100 146 | .fseventsd 147 | .Spotlight-V100 148 | .TemporaryItems 149 | .Trashes 150 | .VolumeIcon.icns 151 | .com.apple.timemachine.donotpresent 152 | 153 | # Directories potentially created on remote AFP share 154 | .AppleDB 155 | .AppleDesktop 156 | Network Trash Folder 157 | Temporary Items 158 | .apdisk 159 | 160 | ### Node ### 161 | # Logs 162 | logs 163 | *.log 164 | npm-debug.log* 165 | yarn-debug.log* 166 | yarn-error.log* 167 | 168 | # Runtime data 169 | pids 170 | *.pid 171 | *.seed 172 | *.pid.lock 173 | 174 | # Directory for instrumented libs generated by jscoverage/JSCover 175 | lib-cov 176 | 177 | # Coverage directory used by tools like istanbul 178 | coverage 179 | 180 | # nyc test coverage 181 | .nyc_output 182 | 183 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 184 | .grunt 185 | 186 | # Bower dependency directory (https://bower.io/) 187 | bower_components 188 | 189 | # node-waf configuration 190 | .lock-wscript 191 | 192 | # Compiled binary addons (http://nodejs.org/api/addons.html) 193 | build/Release 194 | 195 | # Dependency directories 196 | node_modules/ 197 | jspm_packages/ 198 | 199 | # Typescript v1 declaration files 200 | typings/ 201 | 202 | # Optional npm cache directory 203 | .npm 204 | 205 | # Optional eslint cache 206 | .eslintcache 207 | 208 | # Optional REPL history 209 | .node_repl_history 210 | 211 | # Output of 'npm pack' 212 | *.tgz 213 | 214 | # Yarn Integrity file 215 | .yarn-integrity 216 | 217 | # dotenv environment variables file 218 | .env 219 | 220 | ### Sass ### 221 | .sass-cache/ 222 | *.css.map 223 | 224 | ### Windows ### 225 | # Windows thumbnail cache files 226 | Thumbs.db 227 | ehthumbs.db 228 | ehthumbs_vista.db 229 | 230 | # Folder config file 231 | Desktop.ini 232 | 233 | # Recycle Bin used on file shares 234 | $RECYCLE.BIN/ 235 | 236 | # Windows Installer files 237 | *.cab 238 | *.msi 239 | *.msm 240 | *.msp 241 | 242 | # Windows shortcuts 243 | *.lnk 244 | 245 | 246 | # End of https://www.gitignore.io/api/node,sass,linux,macos,windows 247 | 248 | # Created by https://www.gitignore.io/api/virtualenv 249 | # Edit at https://www.gitignore.io/?templates=virtualenv 250 | 251 | ### VirtualEnv ### 252 | # Virtualenv 253 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 254 | .Python 255 | [Bb]in 256 | [Ii]nclude 257 | [Ll]ib 258 | [Ll]ib64 259 | [Ll]ocal 260 | [Ss]cripts 261 | pyvenv.cfg 262 | .env 263 | .venv 264 | env/ 265 | venv/ 266 | ENV/ 267 | env.bak/ 268 | venv.bak/ 269 | pip-selfcheck.json 270 | 271 | # End of https://www.gitignore.io/api/virtualenv 272 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # YAML 1.2 2 | --- 3 | abstract: "To convey neural network architectures in publications, appropriate visualizations are of great importance. While most current deep learning papers contain such visualizations, these are usually handcrafted just before publication, which results in a lack of a common visual grammar, significant time investment, errors, and ambiguities. Current automatic network visualization tools focus on debugging the network itself and are not ideal for generating publication visualizations. Therefore, we present an approach to automate this process by translating network architectures specified in Keras into visualizations that can directly be embedded into any publication. To do so, we propose a visual grammar for convolutional neural networks (CNNs), which has been derived from an analysis of such figures extracted from all ICCV and CVPR papers published between 2013 and 2019. The proposed grammar incorporates visual encoding, network layout, layer aggregation, and legend generation. We have further realized our approach in an online system available to the community, which we have evaluated through expert feedback, and a quantitative study. It not only reduces the time needed to generate network visualizations for publications, but also enables a unified and unambiguous visualization design." 4 | authors: 5 | - 6 | affiliation: "Ulm University" 7 | family-names: "Bäuerle" 8 | given-names: Alex 9 | orcid: "https://orcid.org/0000-0003-3886-8799" 10 | - 11 | affiliation: "Ulm University" 12 | family-names: Onzenoodt 13 | given-names: Christian 14 | name-particle: van 15 | orcid: "https://orcid.org/0000-0002-5951-6795" 16 | - 17 | affiliation: "Ulm University" 18 | family-names: Ropinski 19 | given-names: Timo 20 | orcid: "https://orcid.org/0000-0002-7857-5512" 21 | cff-version: "1.1.0" 22 | date-released: 2021-02-08 23 | doi: "10.1109/TVCG.2021.3057483" 24 | message: "If you use this software, please cite it using these metadata." 25 | title: "Net2Vis – A Visual Grammar for Automatically Generating Publication-Tailored CNN Architecture Visualizations" 26 | ... 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at alex.baeuerle@uni-ulm.de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Visual Computing Group (Ulm University) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Net2Vis Teaser](net2vis_teaser.png) 2 | ![Net2Vis Teaser_Legend](net2vis_teaser_legend.png) 3 | 4 | # Net2Vis 5 | 6 | :white_check_mark: Automatic Network Visualization 7 | 8 | :white_check_mark: Levels of Abstraction 9 | 10 | :white_check_mark: Unified Design 11 | 12 | Created by Alex Bäuerle, Christian van Onzenoodt and Timo Ropinski. 13 | 14 | Accessible online. 15 | 16 | ## What is this? 17 | 18 | Net2Vis automatically generates abstract visualizations for convolutional neural networks from Keras code. 19 | 20 | ## How does this help me? 21 | 22 | When looking at publications that use neural networks for their techniques, it is still apparent how they differ. 23 | Most of them are handcrafted and thus lack a unified visual grammar. 24 | Handcrafting such visualizations also creates ambiguities and misinterpretations. 25 | 26 | With Net2Vis, these problems are gone. 27 | It is designed to provide an abstract network visualization while still providing general information about individual layers. 28 | We reflect the number of features as well as the spatial resolution of the tensor in our glyph design. 29 | Layer-Types can be identified through colors. 30 | Since these networks can get fairly complex, we added the possibility to group layers and thus compact the network through replacing common layer sequences. 31 | 32 | The best of it: Once the application runs, you just have to paste your Keras code into your browser and the visualization is automatically generated based on that. 33 | You still can tweak your visualizations and create abstractions before downloading them as SVG and PDF. 34 | 35 | ## How can I use this? 36 | 37 | Either, go to our Website, or install Net2Vis locally. 38 | Our website includes no setup, but might be slower and limited in network size depending on what you are working on. 39 | Installing this locally allows you to modify the functionality and might be better performing than the online version. 40 | 41 | ## Installation 42 | 43 | Starting with Net2Vis is pretty easy (assuming python3, tested to run on python 3.6-3.8, and npm). 44 | 45 | 1. Clone this Repo 46 | 2. For the Backend to work, we need Cairo and Docker installed on your machine. This is used for PDF conversion and running models pasted into the browser (more) secure. 47 | 48 | For docker, the docker daemon needs to run. 49 | This way, we can run the pasted code within separate containers. 50 | 51 | For starting up the backend, the following steps are needed: 52 | 53 | 1. Go into the backend folder: `cd backend` 54 | 2. Install backend dependencies by running `pip3 install -r requirements.txt` 55 | 3. Install the docker container by running `docker build --force-rm -t tf_plus_keras .` 56 | 4. To start the server, issue: `python3 server.py` 57 | 58 | The frontend is a react application that can be started as follows: 59 | 60 | 1. Go into the frontend folder: `cd net2vis` 61 | 2. Install the javascript dependencies using: `npm install` 62 | 3. Start the frontend application with: `npm start` 63 | 64 | ## Model Presets 65 | 66 | For local installations only: If you want to replicate any of the network figures in our paper, or just want to see examples for visualizations, we have included all network figures from our paper for you to experiment with. To access those simply use the following urls: 67 | 68 | - Figure 1: localhost:3000/unet 69 | - Figure 4: localhost:3000/resnet 70 | - Figure 5: localhost:3000/monochrome 71 | - Figure 6: localhost:3000/3d 72 | 73 | For most of these URL endings, you will probably also find networks in the official version, however, there is no guarantee that they wont have been changed. 74 | 75 | ## Citation 76 | 77 | If you find this code useful please consider citing us: 78 | 79 | @article{bauerle2021net2vis, 80 | title={Net2vis--a visual grammar for automatically generating publication-tailored cnn architecture visualizations}, 81 | author={B{\"a}uerle, Alex and Van Onzenoodt, Christian and Ropinski, Timo}, 82 | journal={IEEE transactions on visualization and computer graphics}, 83 | volume={27}, 84 | number={6}, 85 | pages={2980--2991}, 86 | year={2021}, 87 | publisher={IEEE} 88 | } 89 | 90 | ## Acknowlegements 91 | 92 | This work was funded by the Carl-Zeiss-Scholarship for Ph.D. students. 93 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | from tensorflow/tensorflow:latest 2 | 3 | RUN pip install keras 4 | -------------------------------------------------------------------------------- /backend/default/groups.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /backend/default/layer_types_current.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /backend/default/legend_preferences.json: -------------------------------------------------------------------------------- 1 | {"element_spacing":{"value":120,"type":"number","description":"Spacing between Elements"},"layer_height":{"value":30,"type":"number","description":"Layer Height"},"layer_width":{"value":10,"type":"number","description":"Layer Width"},"layers_spacing_horizontal":{"value":5,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":10,"type":"number","description":"Vertical spacing between Layers"},"complex_spacing":{"value":15,"type":"number","description":"Spacing before complex Layer"},"stroke_width":{"value":2,"type":"number","description":"Stroke Width"},"reverse_order":{"value":false,"type":"switch","description":"Reverse Legend Order"}} -------------------------------------------------------------------------------- /backend/default/model_current.py: -------------------------------------------------------------------------------- 1 | # You can freely modify this file. 2 | # However, you need to have a function that is named get_model and returns a Keras Model. 3 | import keras as k 4 | from keras import models 5 | from keras import layers 6 | from keras import utils 7 | 8 | def get_model(): 9 | img_height = 256 10 | img_width = 256 11 | img_channels = 1 12 | 13 | input_shape = (img_height, img_width, img_channels) 14 | img_input = k.Input(shape=input_shape) 15 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(img_input) 16 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1) 17 | pool1 = layers.MaxPooling2D(pool_size=(2, 2))(conv1) 18 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1) 19 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2) 20 | pool2 = layers.MaxPooling2D(pool_size=(2, 2))(conv2) 21 | 22 | model = models.Model(img_input, pool2) 23 | 24 | return model 25 | -------------------------------------------------------------------------------- /backend/default/preferences.json: -------------------------------------------------------------------------------- 1 | {"layer_display_min_height":{"value":30,"type":"number","description":"Minimum Layer Height"},"layer_display_max_height":{"value":100,"type":"number","description":"Maximum Layer Height"},"features_mapping":{"value":"width","type":"choice","description":"Visual mapping of the features"},"layer_display_min_width":{"value":20,"type":"number","description":"Minimum width of Layers"},"layer_display_max_width":{"value":80,"type":"number","description":"Maximum width of Layers"},"layers_spacing_horizontal":{"value":20,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":200,"type":"number","description":"Vertical spacing between Layers"},"show_dimensions":{"value":true,"type":"switch","description":"Dimensions Label"},"show_features":{"value":true,"type":"switch","description":"Features Label"},"stroke_width":{"value":4,"type":"number","description":"Stroke Width"},"show_samples":{"value":false,"type":"switch","description":"Input/Output Samples"},"no_colors":{"value":false,"type":"switch","description":"Disable Colors"},"add_splitting":{"value":true,"type":"switch","description":"Replace Split Layers"}} 2 | -------------------------------------------------------------------------------- /backend/models/3d/groups.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /backend/models/3d/layer_types_current.json: -------------------------------------------------------------------------------- 1 | {"Reshape":{"color":"#2196F3","alias":"Reshape","texture":"url(#muster1)","hidden":true,"dense":false},"Conv3D":{"color":"#8BC34A","alias":"Conv3D","texture":"url(#muster2)","hidden":false,"dense":false},"Flatten":{"color":"#F44336","alias":"Flatten","texture":"url(#muster3)","hidden":false,"dense":true},"Dense":{"color":"#795548","alias":"Dense","texture":"url(#muster4)","hidden":false,"dense":true}} -------------------------------------------------------------------------------- /backend/models/3d/legend_preferences.json: -------------------------------------------------------------------------------- 1 | {"element_spacing":{"value":120,"type":"number","description":"Spacing between Elements"},"layer_height":{"value":30,"type":"number","description":"Layer Height"},"layer_width":{"value":10,"type":"number","description":"Layer Width"},"layers_spacing_horizontal":{"value":5,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":10,"type":"number","description":"Vertical spacing between Layers"},"complex_spacing":{"value":15,"type":"number","description":"Spacing before complex Layer"},"stroke_width":{"value":2,"type":"number","description":"Stroke Width"},"reverse_order":{"value":false,"type":"switch","description":"Reverse Legend Order"}} -------------------------------------------------------------------------------- /backend/models/3d/model_current.py: -------------------------------------------------------------------------------- 1 | # You can freely modify this file. 2 | # However, you need to have a function that is named get_model and returns a Keras Model. 3 | import tensorflow as tf 4 | from tensorflow.python.keras import models 5 | from tensorflow.python.keras import layers 6 | from tensorflow.python.keras import utils 7 | 8 | def get_model(): 9 | model = models.Sequential() 10 | model.add(layers.Reshape((30, 30, 30, 1), input_shape=(30, 30, 30, 1))) 11 | model.add(layers.Conv3D(16, 6, strides=2, activation='relu', padding='same')) 12 | model.add(layers.Conv3D(64, 5, strides=2, activation='relu', padding='same')) 13 | model.add(layers.Conv3D(64, 5, strides=2, activation='relu')) 14 | model.add(layers.Flatten()) 15 | model.add(layers.Dense(10, activation='softmax')) 16 | return model -------------------------------------------------------------------------------- /backend/models/3d/preferences.json: -------------------------------------------------------------------------------- 1 | {"layer_display_min_height":{"value":30,"type":"number","description":"Minimum Layer Height"},"layer_display_max_height":{"value":100,"type":"number","description":"Maximum Layer Height"},"features_mapping":{"value":"width","type":"choice","description":"Visual mapping of the features"},"layer_display_min_width":{"value":20,"type":"number","description":"Minimum width of Layers"},"layer_display_max_width":{"value":80,"type":"number","description":"Maximum width of Layers"},"layers_spacing_horizontal":{"value":20,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":200,"type":"number","description":"Vertical spacing between Layers"},"show_dimensions":{"value":true,"type":"switch","description":"Dimensions Label"},"show_features":{"value":true,"type":"switch","description":"Features Label"},"stroke_width":{"value":4,"type":"number","description":"Stroke Width"},"show_samples":{"value":false,"type":"switch","description":"Input/Output Samples"},"no_colors":{"value":false,"type":"switch","description":"Disable Colors"},"add_splitting":{"value":true,"type":"switch","description":"Replace Split Layers"}} 2 | -------------------------------------------------------------------------------- /backend/models/monochrome/groups.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /backend/models/monochrome/layer_types_current.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /backend/models/monochrome/legend_preferences.json: -------------------------------------------------------------------------------- 1 | {"element_spacing":{"value":120,"type":"number","description":"Spacing between Elements"},"layer_height":{"value":30,"type":"number","description":"Layer Height"},"layer_width":{"value":10,"type":"number","description":"Layer Width"},"layers_spacing_horizontal":{"value":5,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":10,"type":"number","description":"Vertical spacing between Layers"},"complex_spacing":{"value":15,"type":"number","description":"Spacing before complex Layer"},"stroke_width":{"value":2,"type":"number","description":"Stroke Width"},"reverse_order":{"value":false,"type":"switch","description":"Reverse Legend Order"}} -------------------------------------------------------------------------------- /backend/models/monochrome/model_current.py: -------------------------------------------------------------------------------- 1 | # You can freely modify this file. 2 | # However, you need to have a function that is named get_model and returns a Keras Model. 3 | import keras as k 4 | from keras import models 5 | from keras import layers 6 | from keras import utils 7 | 8 | def get_model(): 9 | img_height = 256 10 | img_width = 256 11 | img_channels = 1 12 | 13 | input_shape = (img_height, img_width, img_channels) 14 | img_input = k.Input(shape=input_shape) 15 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(img_input) 16 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1) 17 | pool1 = layers.MaxPooling2D(pool_size=(2, 2))(conv1) 18 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1) 19 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2) 20 | pool2 = layers.MaxPooling2D(pool_size=(2, 2))(conv2) 21 | 22 | model = models.Model(img_input, pool2) 23 | 24 | return model 25 | -------------------------------------------------------------------------------- /backend/models/monochrome/preferences.json: -------------------------------------------------------------------------------- 1 | {"layer_display_min_height":{"value":30,"type":"number","description":"Minimum Layer Height"},"layer_display_max_height":{"value":100,"type":"number","description":"Maximum Layer Height"},"features_mapping":{"value":"width","type":"choice","description":"Visual mapping of the features"},"layer_display_min_width":{"value":20,"type":"number","description":"Minimum width of Layers"},"layer_display_max_width":{"value":80,"type":"number","description":"Maximum width of Layers"},"layers_spacing_horizontal":{"value":20,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":200,"type":"number","description":"Vertical spacing between Layers"},"show_dimensions":{"value":true,"type":"switch","description":"Dimensions Label"},"show_features":{"value":true,"type":"switch","description":"Features Label"},"stroke_width":{"value":4,"type":"number","description":"Stroke Width"},"show_samples":{"value":false,"type":"switch","description":"Input/Output Samples"},"no_colors":{"value":true,"type":"switch","description":"Disable Colors"},"add_splitting":{"value":true,"type":"switch","description":"Replace Split Layers"},"show_name":{"value":false,"type":"switch","description":"Name Label"}} -------------------------------------------------------------------------------- /backend/models/resnet/groups.json: -------------------------------------------------------------------------------- 1 | [{"name":"DGE2n2tCsOFAGKTUZ7ZsmXLQjsQWGC7E","active":true,"layers":[{"id":180,"name":"Split","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[],"output":[39,45],"properties":{}}},{"id":39,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[180],"output":[42],"properties":{}}},{"id":42,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[39],"output":[46],"properties":{}}},{"id":46,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[42],"output":[49],"properties":{}}},{"id":49,"name":"Add","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[45,46],"output":[],"properties":{}}},{"id":45,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[180],"output":[49],"properties":{}}}]},{"name":"FfAkH6CEijf85b3i2QMFV8tUpGCZ413W","active":true,"layers":[{"id":187,"name":"Split","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[],"output":[113,121],"properties":{}}},{"id":113,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[187],"output":[116],"properties":{}}},{"id":116,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[113],"output":[119],"properties":{}}},{"id":119,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[116],"output":[121],"properties":{}}},{"id":121,"name":"Add","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[187,119],"output":[],"properties":{}}}]}] -------------------------------------------------------------------------------- /backend/models/resnet/layer_types_current.json: -------------------------------------------------------------------------------- 1 | {"InputLayer":{"color":"#ffeb3b","alias":"Input","texture":"url(#muster1)","hidden":false},"ZeroPadding2D":{"color":"#607d8b","alias":"ZeroPadding2D","texture":"url(#muster2)","hidden":true},"Conv2D":{"color":"#8bc34a","alias":"Conv","texture":"url(#muster3)","hidden":false},"BatchNormalization":{"color":"#795548","alias":"BatchNormalization","texture":"url(#muster4)","hidden":true},"Activation":{"color":"#FFEB3B","alias":"Activation","texture":"url(#muster5)","hidden":true},"MaxPooling2D":{"color":"#f44336","alias":"MaxPool","texture":"url(#muster6)","hidden":false},"Add":{"color":"#795548","alias":"Add","texture":"url(#muster7)","hidden":false},"GlobalAveragePooling2D":{"color":"#009688","alias":"Gl. Avg. Pool","texture":"url(#muster8)","hidden":false},"Dense":{"color":"#FF9800","alias":"Dense","texture":"url(#muster9)","hidden":false},"Split":{"color":"#9e9e9e","alias":"Route","texture":"url(#muster10)","hidden":false},"DGE2n2tCsOFAGKTUZ7ZsmXLQjsQWGC7E":{"color":"#3f51b5","alias":"Residual 1","texture":"url(#muster12)"},"FfAkH6CEijf85b3i2QMFV8tUpGCZ413W":{"color":"#03A9F4","alias":"Residual 2","texture":"url(#muster12)"}} -------------------------------------------------------------------------------- /backend/models/resnet/legend_preferences.json: -------------------------------------------------------------------------------- 1 | {"element_spacing":{"value":100,"type":"number","description":"Spacing between Elements"},"layer_height":{"value":30,"type":"number","description":"Layer Height"},"layer_width":{"value":10,"type":"number","description":"Layer Width"},"layers_spacing_horizontal":{"value":5,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":10,"type":"number","description":"Vertical spacing between Layers"},"complex_spacing":{"value":15,"type":"number","description":"Spacing before complex Layer"},"stroke_width":{"value":2,"type":"number","description":"Stroke Width"},"reverse_order":{"value":false,"type":"switch","description":"Reverse Legend Order"}} -------------------------------------------------------------------------------- /backend/models/resnet/model_current.py: -------------------------------------------------------------------------------- 1 | # You can freely modify this file. 2 | # However, you need to have a function that is named get_model and returns a Keras Model. 3 | import tensorflow as tf 4 | from tensorflow.python.keras import models 5 | from tensorflow.python.keras import layers 6 | from tensorflow.python.keras import utils 7 | 8 | def get_model(): 9 | img_height = 256 10 | img_width = 256 11 | img_channels = 1 12 | 13 | model = tf.keras.applications.ResNet50(weights=None) 14 | 15 | return model -------------------------------------------------------------------------------- /backend/models/resnet/preferences.json: -------------------------------------------------------------------------------- 1 | {"layer_display_min_height":{"value":20,"type":"number","description":"Minimum Layer Height"},"layer_display_max_height":{"value":150,"type":"number","description":"Maximum Layer Height"},"features_mapping":{"value":"width","type":"choice","description":"Visual mapping of the features"},"layer_display_min_width":{"value":20,"type":"number","description":"Minimum width of Layers"},"layer_display_max_width":{"value":80,"type":"number","description":"Maximum width of Layers"},"layers_spacing_horizontal":{"value":20,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":30,"type":"number","description":"Vertical spacing between Layers"},"show_dimensions":{"value":true,"type":"switch","description":"Dimensions Label"},"show_features":{"value":true,"type":"switch","description":"Features Label"},"stroke_width":{"value":4,"type":"number","description":"Stroke Width"},"show_samples":{"value":false,"type":"switch","description":"Input/Output Samples"},"no_colors":{"value":false,"type":"switch","description":"Disable Colors"},"add_splitting":{"value":true,"type":"switch","description":"Replace Split Layers"}} -------------------------------------------------------------------------------- /backend/models/resnet/visualizations.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/resnet/visualizations.zip -------------------------------------------------------------------------------- /backend/models/resnet/visualizations/graph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/resnet/visualizations/graph.pdf -------------------------------------------------------------------------------- /backend/models/resnet/visualizations/legend.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/resnet/visualizations/legend.pdf -------------------------------------------------------------------------------- /backend/models/resnet/visualizations/legend.svg: -------------------------------------------------------------------------------- 1 | InputConvMaxPoolAddGl. Avg. PoolDenseSplit=Residual 1=Residual 2 -------------------------------------------------------------------------------- /backend/models/unet/groups.json: -------------------------------------------------------------------------------- 1 | [{"name":"Yo8sMxTwF8lCR58dEDXKEU2JvfH3YuiF","active":true,"layers":[{"id":10,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[],"output":[11],"properties":{}}},{"id":11,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[10],"output":[],"properties":{}}}]},{"name":"YBaN7uEOn6xCN16QXzg7oOrDIftiAF0n","active":true,"layers":[{"id":44,"name":"Yo8sMxTwF8lCR58dEDXKEU2JvfH3YuiF","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[],"output":[22],"properties":{}}},{"id":22,"name":"UpSampling2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[44],"output":[23],"properties":{}}},{"id":23,"name":"Conv2D","properties":{"dimensions":{"in":[1,1,1],"out":[1,1,1]},"input":[22],"output":[],"properties":{}}}]}] -------------------------------------------------------------------------------- /backend/models/unet/layer_types_current.json: -------------------------------------------------------------------------------- 1 | {"InputLayer":{"color":"#2196F3","alias":"InputLayer","texture":"url(#muster1)","hidden":true},"Conv2D":{"color":"#4caf50","alias":"Conv2D","texture":"url(#muster2)","hidden":false},"MaxPooling2D":{"color":"#2196f3","alias":"MaxPool","texture":"url(#muster3)","hidden":false},"Dropout":{"color":"#795548","alias":"Dropout","texture":"url(#muster4)","hidden":true},"UpSampling2D":{"color":"#ffeb3b","alias":"DeConv","texture":"url(#muster5)","hidden":false},"Concatenate":{"color":"#795548","alias":"Concatenate","texture":"url(#muster6)","hidden":false},"Yo8sMxTwF8lCR58dEDXKEU2JvfH3YuiF":{"color":"#8bc34a","alias":"Basic","texture":"url(#muster7)"},"YBaN7uEOn6xCN16QXzg7oOrDIftiAF0n":{"color":"#f44336","alias":"Up","texture":"url(#muster8)"}} -------------------------------------------------------------------------------- /backend/models/unet/legend_preferences.json: -------------------------------------------------------------------------------- 1 | {"element_spacing":{"value":120,"type":"number","description":"Spacing between Elements"},"layer_height":{"value":30,"type":"number","description":"Layer Height"},"layer_width":{"value":10,"type":"number","description":"Layer Width"},"layers_spacing_horizontal":{"value":5,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":10,"type":"number","description":"Vertical spacing between Layers"},"complex_spacing":{"value":15,"type":"number","description":"Spacing before complex Layer"},"stroke_width":{"value":2,"type":"number","description":"Stroke Width"},"reverse_order":{"value":false,"type":"switch","description":"Reverse Legend Order"}} -------------------------------------------------------------------------------- /backend/models/unet/model_current.py: -------------------------------------------------------------------------------- 1 | # You can freely modify this file. 2 | # However, you need to have a function that is named get_model and returns a Keras Model. 3 | import tensorflow as tf 4 | from tensorflow.python.keras import models 5 | from tensorflow.python.keras import layers 6 | from tensorflow.python.keras import utils 7 | 8 | def get_model(): 9 | img_height = 256 10 | img_width = 256 11 | img_channels = 1 12 | 13 | input_shape = (img_height, img_width, img_channels) 14 | img_input = tf.keras.Input(shape=input_shape) 15 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(img_input) 16 | conv1 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1) 17 | pool1 = layers.MaxPooling2D(pool_size=(2, 2))(conv1) 18 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1) 19 | conv2 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2) 20 | pool2 = layers.MaxPooling2D(pool_size=(2, 2))(conv2) 21 | conv3 = layers.Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2) 22 | conv3 = layers.Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3) 23 | pool3 = layers.MaxPooling2D(pool_size=(2, 2))(conv3) 24 | conv4 = layers.Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3) 25 | conv4 = layers.Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4) 26 | drop4 = layers.Dropout(0.5)(conv4) 27 | pool4 = layers.MaxPooling2D(pool_size=(2, 2))(drop4) 28 | conv5 = layers.Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4) 29 | conv5 = layers.Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5) 30 | drop5 = layers.Dropout(0.5)(conv5) 31 | up6 = layers.Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(layers.UpSampling2D(size = (2,2))(drop5)) 32 | merge6 = layers.concatenate([drop4,up6]) 33 | conv6 = layers.Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6) 34 | conv6 = layers.Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6) 35 | up7 = layers.Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(layers.UpSampling2D(size = (2,2))(conv6)) 36 | merge7 = layers.concatenate([conv3,up7]) 37 | conv7 = layers.Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7) 38 | conv7 = layers.Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7) 39 | up8 = layers.Conv2D(128, 2, activation = 'relu', padding='same', kernel_initializer='he_normal')(layers.UpSampling2D(size = (2,2))(conv7)) 40 | cv1 = layers.Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(up8) 41 | merge8 = layers.concatenate([conv2, up8]) 42 | conv8 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8) 43 | conv8 = layers.Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8) 44 | up9 = layers.Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(layers.UpSampling2D(size = (2,2))(conv8)) 45 | merge9 = layers.concatenate([conv1,up9]) 46 | conv9 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9) 47 | conv9 = layers.Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9) 48 | conv9 = layers.Conv2D(2, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9) 49 | conv10 = layers.Conv2D(1, 1, activation = 'sigmoid')(conv9) 50 | model = models.Model(img_input, conv10) 51 | return model 52 | -------------------------------------------------------------------------------- /backend/models/unet/preferences.json: -------------------------------------------------------------------------------- 1 | {"layer_display_min_height":{"value":30,"type":"number","description":"Minimum Layer Height"},"layer_display_max_height":{"value":150,"type":"number","description":"Maximum Layer Height"},"features_mapping":{"value":"width","type":"choice","description":"Visual mapping of the features"},"layer_display_min_width":{"value":40,"type":"number","description":"Minimum width of Layers"},"layer_display_max_width":{"value":120,"type":"number","description":"Maximum width of Layers"},"layers_spacing_horizontal":{"value":30,"type":"number","description":"Horizontal spacing between Layers"},"layers_spacing_vertical":{"value":280,"type":"number","description":"Vertical spacing between Layers"},"show_dimensions":{"value":true,"type":"switch","description":"Dimensions Label"},"show_features":{"value":true,"type":"switch","description":"Features Label"},"stroke_width":{"value":4,"type":"number","description":"Stroke Width"},"show_samples":{"value":false,"type":"switch","description":"Input/Output Samples"},"no_colors":{"value":false,"type":"switch","description":"Disable Colors"},"add_splitting":{"value":false,"type":"switch","description":"Replace Split Layers"}} -------------------------------------------------------------------------------- /backend/models/unet/visualizations.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/unet/visualizations.zip -------------------------------------------------------------------------------- /backend/models/unet/visualizations/graph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/unet/visualizations/graph.pdf -------------------------------------------------------------------------------- /backend/models/unet/visualizations/legend.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/models/unet/visualizations/legend.pdf -------------------------------------------------------------------------------- /backend/models/unet/visualizations/legend.svg: -------------------------------------------------------------------------------- 1 | Conv2DMaxPoolDeConvConcatenate=Basic=Up -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.12.0 2 | astor==0.8.1 3 | astroid==2.5.1 4 | astunparse==1.6.3 5 | cached-property==1.5.2 6 | cachetools==4.2.1 7 | cairocffi==1.2.0 8 | CairoSVG==2.5.2 9 | certifi==2020.12.5 10 | cffi==1.14.5 11 | chardet==4.0.0 12 | click==7.1.2 13 | cssselect2==0.4.1 14 | defusedxml==0.7.1 15 | docker==4.4.4 16 | epicbox==1.1.0 17 | Flask==1.1.2 18 | flatbuffers==1.12 19 | gast==0.4.0 20 | google-auth==1.28.0 21 | google-auth-oauthlib==0.4.3 22 | google-pasta==0.2.0 23 | importlib-metadata==3.7.3 24 | isort==5.8.0 25 | itsdangerous==1.1.0 26 | Jinja2==2.11.3 27 | Keras-Applications==1.0.8 28 | Keras-Preprocessing==1.1.2 29 | lazy-object-proxy==1.5.2 30 | Markdown==3.3.4 31 | MarkupSafe==1.1.1 32 | mccabe==0.6.1 33 | oauthlib==3.1.0 34 | opt-einsum==3.3.0 35 | Pillow>=8.2.0 36 | protobuf==3.18.3 37 | pyasn1==0.4.8 38 | pyasn1-modules==0.2.8 39 | pycparser==2.20 40 | pylint==2.7.2 41 | python-dateutil==2.8.1 42 | requests==2.25.1 43 | requests-oauthlib==1.3.0 44 | rsa==4.7.2 45 | scipy==1.6.1 46 | six==1.15.0 47 | structlog==21.1.0 48 | tensorflow==2.5.1 49 | termcolor==1.1.0 50 | tinycss2==1.1.0 51 | toml==0.10.2 52 | typed-ast==1.4.2 53 | typing-extensions==3.7.4.3 54 | urllib3==1.26.5 55 | uWSGI==2.0.19.1 56 | webencodings==0.5.1 57 | websocket-client==0.58.0 58 | Werkzeug==1.0.1 59 | wrapt==1.12.1 60 | zipp==3.4.1 61 | onnx==1.10.1 62 | -------------------------------------------------------------------------------- /backend/translate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/backend/translate/__init__.py -------------------------------------------------------------------------------- /backend/translate/graph.py: -------------------------------------------------------------------------------- 1 | """Used to handle neural network data graphs.""" 2 | class Node: 3 | """Representing one node in the Graph.""" 4 | def __init__(self): 5 | self.data = None 6 | self.children = [] 7 | self.parent = None 8 | 9 | # Representation of the Graph that gets extracted from Code. 10 | class Graph: 11 | """Representing a Network Graph.""" 12 | def __init__(self): 13 | self.layers = [] 14 | 15 | 16 | def add_layer(self, layer): 17 | """Add a new Layer to the Graph. 18 | 19 | Arguments: 20 | layer {object} -- the layer to be added 21 | """ 22 | self.layers.append(layer) 23 | 24 | 25 | # Return the Graph representation. 26 | def __repr__(self): 27 | string = '' 28 | for i in range(len(self.layers)): 29 | string = string + type(self.layers[i]).__name__ 30 | if i != (len(self.layers) - 1): 31 | string = string + ' -> ' 32 | return string 33 | 34 | 35 | def resolve_input_names(self): 36 | """Generate references between the Layers based on the Input names.""" 37 | for i in range(len(self.layers)): 38 | for name in self.layers[i].input_names: 39 | for j in range(len(self.layers)): 40 | if self.layers[j].name == name: 41 | self.layers[i].input.append(self.layers[j]) 42 | self.layers[j].output.append(self.layers[i]) 43 | 44 | 45 | def dimensions_str(self): 46 | """Return the Layer Dimensions as Pretty String. 47 | 48 | Returns: 49 | String -- the dimensions of all layers as a string 50 | """ 51 | dim = '' 52 | for i in range(len(self.layers)): 53 | dim = dim + str(self.layers[i].dimensions) 54 | if i != (len(self.layers)-1): 55 | dim = dim + ' -> ' 56 | return dim 57 | 58 | 59 | def dimensions(self): 60 | """Return the Layer Dimensions as Array. 61 | 62 | Returns: 63 | list -- the dimenstions of the network layers 64 | """ 65 | dim = [] 66 | for i in range(len(self.layers)): 67 | dim.append(self.layers[i].dimensions) 68 | return dim 69 | -------------------------------------------------------------------------------- /backend/translate/keras_loader.txt: -------------------------------------------------------------------------------- 1 | import model 2 | 3 | keras_model = model.get_model() 4 | print(keras_model.to_json()) 5 | -------------------------------------------------------------------------------- /backend/translate/layer.py: -------------------------------------------------------------------------------- 1 | """Used to handle layers as objects.""" 2 | 3 | 4 | class Layer: 5 | """Representation of Layers.""" 6 | # Initialize the Properties. 7 | 8 | def __init__(self, class_name, name, dimensions): 9 | self.properties = {} 10 | self.input = [] 11 | self.input_names = [] 12 | self.output = [] 13 | self.name = name 14 | self.type = class_name 15 | self.dimensions = dimensions 16 | 17 | @classmethod 18 | def from_keras(cls, class_name, name, layer): 19 | dimensions = { 20 | 'in': 0, 21 | 'out': layer.get_output_at(0).get_shape().as_list()[1:] 22 | } 23 | if isinstance(layer.get_input_at(0), list): 24 | dimensions['in'] = layer.get_input_at( 25 | 0)[0].get_shape().as_list()[1:] 26 | else: 27 | dimensions['in'] = layer.get_input_at(0).get_shape().as_list()[1:] 28 | if isinstance(layer.get_output_at(0), list): 29 | dimensions['out'] = layer.get_output_at( 30 | 0)[0].get_shape().as_list()[1:] 31 | else: 32 | dimensions['out'] = layer.get_output_at( 33 | 0).get_shape().as_list()[1:] 34 | return cls(class_name, name, dimensions) 35 | 36 | @classmethod 37 | def from_onnx(cls, layer, onnx_graph): 38 | dimensions = { 39 | 'in': 0, 40 | 'out': 0 41 | } 42 | if len(layer.input) > 0: 43 | for value_info in onnx_graph.value_info: 44 | if (layer.input[0] == value_info.name): 45 | dimensions['in'] = list( 46 | map(lambda x: x.dim_value, value_info.type.tensor_type.shape.dim[1:])) 47 | if (layer.output[0] == value_info.name): 48 | dimensions['out'] = list( 49 | map(lambda x: x.dim_value, value_info.type.tensor_type.shape.dim[1:])) 50 | for value_info in onnx_graph.input: 51 | if (layer.input[0] == value_info.name): 52 | dimensions['in'] = list( 53 | map(lambda x: x.dim_value, value_info.type.tensor_type.shape.dim[1:])) 54 | for value_info in onnx_graph.output: 55 | if (layer.output[0] == value_info.name): 56 | dimensions['out'] = list( 57 | map(lambda x: x.dim_value, value_info.type.tensor_type.shape.dim[1:])) 58 | return cls(layer.op_type, layer.name, dimensions) 59 | 60 | def add_specs(self, specs): 61 | """Add Specifications. 62 | Arguments: 63 | specs {list} -- the specifications to be added to the layer 64 | """ 65 | # Update Properties based on Specs. Try to Parse the Specs. 66 | for prop in specs: 67 | if isinstance(specs[prop], dict): 68 | self.properties[prop] = specs[prop]['class_name'] 69 | else: 70 | self.properties[prop] = specs[prop] 71 | 72 | def add_input_names_from_node(self, nodes): 73 | """Adds all the Names of the Input Nodes to the Layer. 74 | Arguments: 75 | nodes {list} -- all nodes that are inputs to the layer 76 | """ 77 | if (len(nodes) > 0): 78 | for node in nodes[0]: 79 | self.input_names.append(node[0]) 80 | 81 | def __repr__(self): 82 | return "%s(properties: %r)" % (self.__class__, self.properties) 83 | -------------------------------------------------------------------------------- /backend/translate/translate_keras.py: -------------------------------------------------------------------------------- 1 | """Translates Keras code so it can be used within the application.""" 2 | import json 3 | import epicbox 4 | from tensorflow import keras 5 | from translate.graph import Graph 6 | import translate.layer as layer 7 | 8 | keras_ext = '.h5' 9 | 10 | 11 | def translate_keras(filename): 12 | """Translate a keras model defined in a file into the neural network graph. 13 | 14 | Arguments: 15 | filename {String} -- name of the file to be translated 16 | 17 | Returns: 18 | object -- the result of this translation, can be an error 19 | """ 20 | if keras_ext in filename: 21 | try: 22 | return graph_from_model_file(filename) 23 | except Exception as err: 24 | return {'error_class': '', 'line_number': 1, 25 | 'detail': "Model could not be loaded correctly. Error: " + str(err)} 26 | else: 27 | epicbox.configure(profiles=[ 28 | epicbox.Profile('python', 'tf_plus_keras:latest')]) 29 | general_reader = open('translate/keras_loader.txt', 'rb') 30 | general_code = general_reader.read() 31 | with open(filename, 'rb') as myfile: 32 | keras_code = myfile.read() 33 | try: 34 | return graph_from_external_file(keras_code, general_code) 35 | except Exception as err: 36 | return {'error_class': '', 'line_number': 1, 37 | 'detail': str(err)} 38 | 39 | 40 | def graph_from_external_file(keras_code, general_code): 41 | """Get a graph from an external file defining the neural network. 42 | 43 | Arguments: 44 | keras_code {String} -- the keras code defining the network 45 | general_code {String} -- the keras code used to load the network 46 | 47 | Returns: 48 | object -- the result of this translation, can be an error 49 | """ 50 | files = [ 51 | {'name': 'model.py', 'content': keras_code}, 52 | {'name': 'main.py', 'content': general_code} 53 | ] 54 | limits = {'cputime': 100, 'memory': 2000} 55 | result = epicbox.run('python', 'python3 main.py', 56 | files=files, limits=limits) 57 | if b'Traceback' in result["stderr"]: 58 | raise Exception(result["stderr"].decode('utf-8')) 59 | model_json = json.loads(result["stdout"]) 60 | model_keras = keras.models.model_from_json(result["stdout"]) 61 | layers_extracted = model_json['config']['layers'] 62 | graph = Graph() 63 | previous_node = '' 64 | for index, json_layer in enumerate(layers_extracted): 65 | if len(layers_extracted) > len(model_keras.layers): 66 | index = index - 1 67 | if index >= 0: 68 | previous_node = add_layer_type(json_layer, model_keras.layers[index], graph, 69 | previous_node) 70 | graph.resolve_input_names() 71 | return graph 72 | 73 | 74 | def graph_from_model_file(keras_model_file): 75 | model_keras = keras.models.load_model(keras_model_file) 76 | model_json = json.loads(model_keras.to_json()) 77 | layers_extracted = model_json['config']['layers'] 78 | graph = Graph() 79 | previous_node = '' 80 | for index, json_layer in enumerate(layers_extracted): 81 | if len(layers_extracted) > len(model_keras.layers): 82 | index = index - 1 83 | if index >= 0: 84 | previous_node = add_layer_type(json_layer, model_keras.layers[index], graph, 85 | previous_node) 86 | graph.resolve_input_names() 87 | return graph 88 | 89 | 90 | def add_layer_type(layer_json, model_layer, graph, previous_node): 91 | """Add a Layer. Layers are identified by name and equipped using the spec. 92 | 93 | Arguments: 94 | layer_json {dict} -- json representation of the layer 95 | model_layer {object} -- tensorflow model representation of the layer 96 | graph {object} -- neural network graph 97 | previous_node {String} -- name of the node before this one 98 | 99 | Returns: 100 | String -- name of the new layer 101 | """ 102 | new_layer = layer.Layer.from_keras(layer_json['class_name'], 103 | layer_json['config']['name'], model_layer) 104 | new_layer.add_specs(layer_json['config']) 105 | return add_to_graph(new_layer, layer_json, graph, previous_node) 106 | 107 | 108 | def add_to_graph(new_layer, model_layer, graph, previous_node): 109 | """Takes new layer, adds the Properties and then adds the Layer to the Graph. 110 | 111 | Arguments: 112 | new_layer {object} -- the layer to be added to the graph 113 | model_layer {dict} -- json dict of the layer props 114 | graph {object} -- the neural network graph 115 | previous_node {String} -- name of the previous layer 116 | 117 | Returns: 118 | String -- name of the new layer 119 | """ 120 | try: 121 | new_layer.add_input_names_from_node(model_layer['inbound_nodes']) 122 | except Exception: 123 | if previous_node != '': 124 | new_layer.add_input_names_from_node([[[previous_node, 0, 0, {}]]]) 125 | graph.add_layer(new_layer) 126 | return new_layer.name 127 | -------------------------------------------------------------------------------- /backend/translate/translate_onnx.py: -------------------------------------------------------------------------------- 1 | import onnx 2 | from onnx import shape_inference 3 | from translate.graph import Graph 4 | import translate.layer as layer 5 | 6 | 7 | def translate_onnx(file): 8 | """Get a graph from an external file defining the neural network. 9 | Arguments: 10 | file {Path} -- the path to the onnx file to be translated 11 | Returns: 12 | object -- the result of this translation, can be an error 13 | """ 14 | onnx_model = onnx.load(file) 15 | onnx_model = shape_inference.infer_shapes(onnx_model) 16 | graph = Graph() 17 | previous_node = '' 18 | for index, node in enumerate(onnx_model.graph.node): 19 | if index >= 0: 20 | add_layer_type(node, graph, previous_node, onnx_model.graph) 21 | graph.resolve_input_names() 22 | return graph 23 | 24 | 25 | def add_layer_type(node, graph, previous_node, onnx_graph): 26 | """Add a Layer. Layers are identified by name and equipped using the spec. 27 | Arguments: 28 | node {onnx.Node} -- the node that represents the layer 29 | graph {object} -- neural network graph 30 | previous_node {String} -- name of the node before this one 31 | onnx_graph {onnx.Graph} -- the graph that represents this network 32 | Returns: 33 | String -- name of the new layer 34 | """ 35 | new_layer = layer.Layer.from_onnx(node, onnx_graph) 36 | add_specs_onnx(new_layer, node.attribute) 37 | return add_to_graph(new_layer, node, onnx_graph, graph, previous_node) 38 | 39 | 40 | def add_to_graph(new_layer, node, onnx_graph, graph, previous_node): 41 | """Takes new layer, adds the Properties and then adds the Layer to the Graph. 42 | Arguments: 43 | new_layer {object} -- the layer to be added to the graph 44 | model_layer {dict} -- json dict of the layer props 45 | graph {object} -- the neural network graph 46 | previous_node {String} -- name of the previous layer 47 | Returns: 48 | String -- name of the new layer 49 | """ 50 | for input in node.input: 51 | for graph_node in onnx_graph.node: 52 | for output in graph_node.output: 53 | if (input == output): 54 | new_layer.input_names.append(graph_node.name) 55 | graph.add_layer(new_layer) 56 | return new_layer.name 57 | 58 | 59 | def add_specs_onnx(new_layer, attributes): 60 | for attribute in attributes: 61 | if (attribute.type == onnx.AttributeProto.AttributeType.FLOAT): 62 | new_layer.properties[attribute.name] = attribute.f 63 | elif (attribute.type == onnx.AttributeProto.AttributeType.FLOATS): 64 | new_layer.properties[attribute.name] = str(attribute.floats) 65 | elif (attribute.type == onnx.AttributeProto.AttributeType.INT): 66 | new_layer.properties[attribute.name] = attribute.i 67 | elif (attribute.type == onnx.AttributeProto.AttributeType.INTS): 68 | new_layer.properties[attribute.name] = str(attribute.ints) 69 | else: 70 | print(attribute.type, 'not supported yet.') 71 | -------------------------------------------------------------------------------- /backend/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | chdir = /home/alex/Net2Vis/backend 3 | socket = uwsgi.sock 4 | chmod-socket = 775 5 | plugin = python 6 | module = server:app 7 | virtualenv = venv 8 | enable-threads = true 9 | processes = 4 10 | threads = 2 11 | master = true 12 | uid = http 13 | gid = http 14 | lazy-apps = true 15 | http-websockets = true 16 | die-on-term = true 17 | logto = uwsgi.log 18 | vacuum = true 19 | -------------------------------------------------------------------------------- /net2vis/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # CSS files since were using scss 24 | src/styles/*.css -------------------------------------------------------------------------------- /net2vis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "net2vis", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.4.1", 7 | "@emotion/styled": "^11.3.0", 8 | "@mui/icons-material": "^5.0.1", 9 | "@mui/material": "^5.0.2", 10 | "ace-builds": "^1.4.12", 11 | "dagre": "^0.8.5", 12 | "file-saver": "^2.0.5", 13 | "http-proxy-middleware": "^2.0.1", 14 | "jquery": "^3.6.0", 15 | "jszip": "^3.7.1", 16 | "node-sass-chokidar": "^1.5.0", 17 | "npm": "^7.21.0", 18 | "npm-run-all": "^4.1.5", 19 | "prop-types": "^15.7.2", 20 | "randomstring": "^1.2.1", 21 | "react": "^17.0.2", 22 | "react-ace": "^9.4.3", 23 | "react-color": "^2.19.3", 24 | "react-dom": "^17.0.2", 25 | "react-dropzone": "^11.3.4", 26 | "react-redux": "^7.2.4", 27 | "react-router-dom": "^5.2.0", 28 | "react-scripts": "^4.0.3", 29 | "react-svg-tooltip": "0.0.11", 30 | "redux": "^4.1.1", 31 | "redux-thunk": "^2.3.0", 32 | "tinycolor2": "^1.4.2" 33 | }, 34 | "scripts": { 35 | "build-css": "node-sass-chokidar src/ -o src/", 36 | "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", 37 | "start-js": "react-scripts start", 38 | "start": "npm-run-all -p watch-css start-js", 39 | "build-js": "react-scripts build", 40 | "build": "npm-run-all build-css build-js", 41 | "test": "react-scripts test --env=jsdom", 42 | "eject": "react-scripts eject" 43 | }, 44 | "browserslist": [ 45 | ">0.2%", 46 | "not dead", 47 | "not ie <= 11", 48 | "not op_mini all" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /net2vis/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/net2vis/public/favicon.ico -------------------------------------------------------------------------------- /net2vis/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 24 | Net2Vis 25 | 26 | 27 | 30 |
31 | 41 |
42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /net2vis/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /net2vis/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createStore, applyMiddleware } from "redux"; 3 | import { Provider } from "react-redux"; 4 | import thunk from "redux-thunk"; 5 | 6 | import AppRouter from "./AppRouter"; 7 | import Controls from "./components/controls/ControlsComponent"; 8 | import combinedReducers from "./reducers"; 9 | 10 | import { CssBaseline, createTheme, ThemeProvider } from "@mui/material"; 11 | import { blue, red } from "@mui/material/colors"; 12 | 13 | // Create the Store using all the Reducers and applying the Middleware 14 | const store = createStore(combinedReducers, applyMiddleware(thunk)); 15 | 16 | const theme = createTheme({ 17 | palette: { 18 | primary: blue, 19 | secondary: red, 20 | }, 21 | typography: { 22 | useNextVariants: true, 23 | }, 24 | }); 25 | 26 | // Render the App 27 | // The App provides the Store to the following components. 28 | // Controls as well as Routed Content are rendered. 29 | const App = () => ( 30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 | ); 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /net2vis/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | it("renders without crashing", () => { 6 | const div = document.createElement("div"); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /net2vis/src/AppRouter.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BrowserRouter as Router, Route } from "react-router-dom"; 3 | 4 | import Main from "./components/MainComponent"; 5 | 6 | // AppRouter Calling other Components dependant on Route 7 | class AppRouter extends React.Component { 8 | render() { 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 |
16 |
17 |
18 | ); 19 | } 20 | } 21 | 22 | export default AppRouter; 23 | -------------------------------------------------------------------------------- /net2vis/src/actions/types.js: -------------------------------------------------------------------------------- 1 | // Constants for Action calls 2 | export const SET_ID = "SET_ID"; 3 | export const LOAD_NETWORK_SUCCESS = "LOAD_NETWORK_SUCCESS"; 4 | export const SET_LAYERS_EXTREMES = "SET_LAYERS_EXTREMES"; 5 | export const LOAD_CODE_SUCCESS = "LOAD_CODE_SUCCESS"; 6 | export const UPDATE_CODE_SUCESS = "UPDATE_CODE_SUCCESS"; 7 | export const MOVE_GROUP = "MOVE_GROUP"; 8 | export const MOVE_LEGEND = "MOVE_LEGEND"; 9 | export const ZOOM_GROUP = "ZOOM_GROUP"; 10 | export const ZOOM_LEGEND = "ZOOM_LEGEND"; 11 | export const TOGGLE_CODE = "TOGGLE_CODE"; 12 | export const TOGGLE_PREFERENCES = "TOGGLE_PREFERENCES"; 13 | export const TOGGLE_LEGEND = "TOGGLE_LEGEND"; 14 | export const TOGGLE_ALERT = "TOGGLE_ALERT"; 15 | export const TOGGLE_HELP = "TOGGLE_HELP"; 16 | export const TOGGLE_UPLOAD = "TOGGLE_UPLOAD"; 17 | export const LOAD_LAYER_TYPES_SUCCESS = "LOAD_LAYER_TYPES_SUCCESS"; 18 | export const LOAD_PREFERENCES_SUCCESS = "LOAD_PREFERENCES_SUCCESS"; 19 | export const UPDATE_PREFERENCES_SUCCESS = "UPDATE_PREFERENCES_SUCCESS"; 20 | export const ADD_ERROR = "ADD_ERROR"; 21 | export const REMOVE_ERROR = "REMOVE_ERROR"; 22 | export const SELECT_LAYER = "SELECT_LAYER"; 23 | export const SELECT_LAYERS = "SELECT_LAYERS"; 24 | export const DESELECT_LAYER = "DESELECT_LAYER"; 25 | export const DESELECT_LAYERS = "DESELECT_LAYERS"; 26 | export const SET_PREFERENCE_MODE = "SET_PREFERENCE_MODE"; 27 | export const SET_SELECTED_LEGEND_ITEM = "SET_SELECTED_LEGEND_ITEM"; 28 | export const LOAD_GROUPS_SUCCESS = "LOAD_GROUPS_SUCCESS"; 29 | export const UPDATE_GROUPS = "UPDATE_GROUPS"; 30 | export const INITIALIZE_COMPRESSED_NETWORK = "INITIALIZE_COMPRESSED_NETWORK"; 31 | export const LOAD_LEGEND_PREFERENCES_SUCCESS = 32 | "LOAD_LEGEND_PREFERENCES_SUCCESS"; 33 | export const UPDATE_LEGEND_PREFERENCES_SUCCESS = 34 | "UPDATE_LEGEND_PREFERENCES_SUCCESS"; 35 | export const SET_SELECTION_COLOR_MODE = "SET_SELECTION_COLOR_MODE"; 36 | export const SET_GENERATION_COLOR_MODE = "SET_GENERATION_COLOR_MODE"; 37 | export const SET_NETWORK_BBOX = "SET_NETWORK_BBOX"; 38 | export const SET_LEGEND_BBOX = "SET_LEGEND_BBOX"; 39 | export const UPDATE_ALERT_SNACK_SUCCESS = "UPDATE_ALERT_SNACK_SUCCESS"; 40 | -------------------------------------------------------------------------------- /net2vis/src/api/CodeApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to Code 2 | class CodeApi { 3 | // Get the code from the Backend 4 | static getCode(id) { 5 | const request = new Request("/api/get_code/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.text(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | 20 | // Update the Code on the Backend 21 | static updateCode(code, id) { 22 | const request = new Request("/api/update_code/" + id, { 23 | // Prepare the Request 24 | method: "POST", 25 | body: code, 26 | }); 27 | 28 | return fetch(request) 29 | .then((response) => { 30 | // Return the Result of the Request 31 | return response.text(); 32 | }) 33 | .catch((error) => { 34 | return error; 35 | }); 36 | } 37 | } 38 | 39 | export default CodeApi; 40 | -------------------------------------------------------------------------------- /net2vis/src/api/DownloadApi.js: -------------------------------------------------------------------------------- 1 | import { saveAs } from "file-saver"; 2 | 3 | // Class for calling Backend functions related to Code 4 | class DownloadApi { 5 | // Update the Code on the Backend 6 | static sendVisualization(id, graph, legend) { 7 | var jsonVis = { 8 | graph: graph, 9 | legend: legend, 10 | }; 11 | const request = new Request("/api/process_vis/" + id, { 12 | // Prepare the Request 13 | method: "POST", 14 | body: JSON.stringify(jsonVis), 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | }); 19 | 20 | return fetch(request) 21 | .then((response) => { 22 | // Return the Result of the Request 23 | response.blob().then((res) => { 24 | saveAs(res, "net2vis.zip"); // Download it 25 | }); 26 | }) 27 | .catch((error) => { 28 | return error; 29 | }); 30 | } 31 | } 32 | 33 | export default DownloadApi; 34 | -------------------------------------------------------------------------------- /net2vis/src/api/GroupsApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to Code 2 | class GroupsApi { 3 | // Get the code from the Backend 4 | static getGroups(id) { 5 | const request = new Request("/api/get_groups/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.text(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | 20 | // Update the Code on the Backend 21 | static updateGroups(groups, id) { 22 | const request = new Request("/api/update_groups/" + id, { 23 | // Prepare the Request 24 | method: "POST", 25 | body: JSON.stringify(groups), 26 | }); 27 | 28 | return fetch(request) 29 | .then((response) => { 30 | // Return the Result of the Request 31 | return response.text(); 32 | }) 33 | .catch((error) => { 34 | return error; 35 | }); 36 | } 37 | } 38 | 39 | export default GroupsApi; 40 | -------------------------------------------------------------------------------- /net2vis/src/api/LayerTypesApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to the LayerTypes 2 | class LayerTypesApi { 3 | // Get the code from the Backend 4 | static getLayerTypes(id) { 5 | const request = new Request("/api/get_layer_types/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.text(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | 20 | // Update the Code on the Backend 21 | static updateLayerTypes(layerTypes, id) { 22 | const request = new Request("/api/update_layer_types/" + id, { 23 | // Prepare the Request 24 | method: "POST", 25 | body: JSON.stringify(layerTypes), 26 | }); 27 | 28 | return fetch(request) 29 | .then((response) => { 30 | // Return the Result of the Request 31 | return response.text(); 32 | }) 33 | .catch((error) => { 34 | return error; 35 | }); 36 | } 37 | } 38 | 39 | export default LayerTypesApi; 40 | -------------------------------------------------------------------------------- /net2vis/src/api/LegendPreferencesApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to the Legends Preferences 2 | class LegendPreferencesApi { 3 | // Get the code from the Backend 4 | static getLegendPreferences(id) { 5 | const request = new Request("/api/get_legend_preferences/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.text(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | 20 | // Update the Preferences on the Backend 21 | static updateLegendPreferences(preferences, id) { 22 | const request = new Request("/api/update_legend_preferences/" + id, { 23 | // Prepare the Request 24 | method: "POST", 25 | body: JSON.stringify(preferences), 26 | }); 27 | 28 | return fetch(request) 29 | .then((response) => { 30 | // Return the Result of the Request 31 | return response.text(); 32 | }) 33 | .catch((error) => { 34 | return error; 35 | }); 36 | } 37 | } 38 | 39 | export default LegendPreferencesApi; 40 | -------------------------------------------------------------------------------- /net2vis/src/api/ModelApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to Code 2 | class ModelApi { 3 | // Update the Model file on the Backend 4 | static updateModel(file, id) { 5 | let formData = new FormData(); 6 | formData.append("model", file); 7 | 8 | const request = new Request("/api/upload_model/" + id, { 9 | // Prepare the Request 10 | method: "POST", 11 | body: formData, 12 | }); 13 | 14 | return fetch(request) 15 | .then((response) => { 16 | // Return the Result of the Request 17 | return response.text(); 18 | }) 19 | .catch((error) => { 20 | return error; 21 | }); 22 | } 23 | 24 | // Delete the model from the Backend 25 | static deleteModel(id) { 26 | const request = new Request("/api/delete_model/" + id, { 27 | // Prepare the Request 28 | method: "GET", 29 | }); 30 | 31 | return fetch(request) 32 | .then((response) => { 33 | // Return the result of the Request 34 | return response.text(); 35 | }) 36 | .catch((error) => { 37 | return error; 38 | }); 39 | } 40 | } 41 | 42 | export default ModelApi; 43 | -------------------------------------------------------------------------------- /net2vis/src/api/NetworkApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to the Network 2 | class NetworkApi { 3 | // Get the Network from the Backend 4 | static getNetwork(id) { 5 | const request = new Request("/api/get_network/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.json(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | } 20 | 21 | export default NetworkApi; 22 | -------------------------------------------------------------------------------- /net2vis/src/api/PreferencesApi.js: -------------------------------------------------------------------------------- 1 | // Class for calling Backend functions related to the Preferences 2 | class PreferencesApi { 3 | // Get the code from the Backend 4 | static getPreferences(id) { 5 | const request = new Request("/api/get_preferences/" + id, { 6 | // Prepare the Request 7 | method: "GET", 8 | }); 9 | 10 | return fetch(request) 11 | .then((response) => { 12 | // Return the result of the Request 13 | return response.text(); 14 | }) 15 | .catch((error) => { 16 | return error; 17 | }); 18 | } 19 | 20 | // Update the Preferences on the Backend 21 | static updatePreferences(preferences, id) { 22 | const request = new Request("/api/update_preferences/" + id, { 23 | // Prepare the Request 24 | method: "POST", 25 | body: JSON.stringify(preferences), 26 | }); 27 | 28 | return fetch(request) 29 | .then((response) => { 30 | // Return the Result of the Request 31 | return response.text(); 32 | }) 33 | .catch((error) => { 34 | return error; 35 | }); 36 | } 37 | } 38 | 39 | export default PreferencesApi; 40 | -------------------------------------------------------------------------------- /net2vis/src/colors/index.js: -------------------------------------------------------------------------------- 1 | import * as tinycolor from "tinycolor2"; 2 | 3 | // Get the used color Palette 4 | export function getColorPalette() { 5 | var palette = [ 6 | "#2196F3", 7 | "#8BC34A", 8 | "#F44336", 9 | "#795548", 10 | "#FFEB3B", 11 | "#9C27B0", 12 | "#9E9E9E", 13 | "#009688", 14 | "#FF9800", 15 | "#3F51B5", 16 | "#4CAF50", 17 | "#03A9F4", 18 | "#E91E63", 19 | "#00BCD4", 20 | "#FFC107", 21 | "#673AB7", 22 | "#607D8B", 23 | ]; 24 | return palette; 25 | } 26 | 27 | // Get the used color Palette 28 | export function getBlindColorPalette() { 29 | var palette = [ 30 | "#000000", 31 | "#E69F00", 32 | "#56B4E9", 33 | "#009E73", 34 | "#F0E442", 35 | "#0072B2", 36 | "#D55E00", 37 | "#CC79A7", 38 | ]; 39 | return palette; 40 | } 41 | 42 | // Get the used color Palette 43 | export function getTextures() { 44 | var textures = [ 45 | "url(#muster1)", 46 | "url(#muster2)", 47 | "url(#muster3)", 48 | "url(#muster4)", 49 | "url(#muster5)", 50 | "url(#muster6)", 51 | "url(#muster7)", 52 | "url(#muster8)", 53 | "url(#muster9)", 54 | "url(#muster10)", 55 | "url(#muster11)", 56 | "url(#muster12)", 57 | ]; 58 | return textures; 59 | } 60 | 61 | // Generate a new Texture 62 | export function generateNewTexture(layerTypes) { 63 | var textures = getTextures(); 64 | var index = Object.keys(layerTypes).length % textures.length; 65 | return textures[index]; 66 | } 67 | 68 | // Generate a new Color based on the currently set mode 69 | export function generateNewColor(layerTypes, mode) { 70 | if (mode === "Palette") { 71 | return generatePaletteColor(layerTypes); 72 | } else if (mode === "Interpolation") { 73 | return generateInterpolatedColor(layerTypes); 74 | } else if (mode === "Color Blind") { 75 | return generateBlindPaletteColor(layerTypes); 76 | } 77 | } 78 | 79 | // Generate a new color using the color palette 80 | function generatePaletteColor(layerTypes) { 81 | var palette = getColorPalette(); 82 | var index = Object.keys(layerTypes).length % palette.length; 83 | return palette[index]; 84 | } 85 | 86 | // Generate a new color using the color palette 87 | function generateBlindPaletteColor(layerTypes) { 88 | var palette = getBlindColorPalette(); 89 | var index = Object.keys(layerTypes).length % palette.length; 90 | return palette[index]; 91 | } 92 | 93 | // Generate a new color that is different to all existing ones 94 | function generateInterpolatedColor(layerTypes) { 95 | var saturation = 0.8; // Fixed Saturation Value 96 | var value = 0.7; // Fixed color Value 97 | var colors = []; // Placeholder for colors that are already present 98 | for (var key in layerTypes) { 99 | // For all LayerTypes 100 | colors.push(tinycolor(layerTypes[key].color)); // Add their color to the present colors 101 | } 102 | if (colors.length === 0) { 103 | // If no colors are there 104 | var color = tinycolor({ h: 210, s: saturation, v: value }); // Set the initial color 105 | return color.toHexString(); // Return it as hex 106 | } 107 | colors = colors.map((x) => x.toHsv()); // Map all the colors to hsv 108 | colors = colors.map((x) => x.h); // Extract the h value 109 | colors.sort(function (a, b) { 110 | // Sort them 111 | return a - b; // Ascending 112 | }); 113 | var hue = findOptimalHue(colors); // Find a hue value that is different to all others 114 | var newColor = tinycolor({ h: hue, s: saturation, v: value }); // Make a color with this hue 115 | return newColor.toHexString(); // Return it as hex 116 | } 117 | 118 | // Returns a hue value that has maximum absolute distance to all other hue values 119 | function findOptimalHue(colors) { 120 | var hue = 0; // Initialize the new hue 121 | var maxDistance = 0; // Initialize the maximum distance 122 | var distance = 0; // Initialize the current distance 123 | for (var i in colors) { 124 | // Check all colors 125 | if (parseInt(i) === colors.length - 1) { 126 | // If this is the last color 127 | distance = colors[0] + 360 - colors[i]; // Calculate the circular distance to the first 128 | if (distance > maxDistance) { 129 | // If this distance is maximal 130 | maxDistance = distance; // Set the new maxDistance 131 | hue = (colors[i] + distance / 2.0) % 360; // Set the hue to be between the last and the first color 132 | } 133 | } else { 134 | // Not the last color 135 | distance = colors[parseInt(i) + 1] - colors[i]; // Calculate the distance to the next color 136 | if (distance > maxDistance) { 137 | // If this is the max distance 138 | maxDistance = distance; // Set the new maxDistance 139 | hue = colors[i] + distance / 2.0; // Set the hue value to be between these two colors 140 | } 141 | } 142 | } 143 | return hue; 144 | } 145 | 146 | // Darken a given color 147 | export function darkenColor(color) { 148 | var darkenedColor = tinycolor(color); // Get the color to be darkened 149 | darkenedColor.darken(); // Darken the color 150 | return darkenedColor.toHexString(); // Return it as Hex 151 | } 152 | 153 | export function getFillColor(set, noColors, isGroup) { 154 | if (noColors) { 155 | return set.texture; 156 | } else if (isGroup) { 157 | return darkenColor(set.color); 158 | } else { 159 | return set.color; 160 | } 161 | } 162 | 163 | export function getStrokeColor(set, noColors, isGroup, selected, isDense) { 164 | if (selected) { 165 | return "red"; 166 | } else if (noColors) { 167 | return "grey"; 168 | } else if (isGroup || isDense) { 169 | return set.color; 170 | } else { 171 | return darkenColor(set.color); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /net2vis/src/components/AlertSnack.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { bindActionCreators } from "redux"; 5 | 6 | import * as actions from "../actions"; 7 | 8 | import { Close } from "@mui/icons-material"; 9 | import { IconButton, Snackbar } from "@mui/material"; 10 | 11 | class AlertSnack extends React.Component { 12 | handleClose = (event, reason) => { 13 | this.props.actions.updateAlertSnack({ open: false, message: "" }); 14 | }; 15 | 16 | render() { 17 | return ( 18 | {this.props.alertSnack.message}} 30 | action={[ 31 | 37 | 38 | , 39 | ]} 40 | /> 41 | ); 42 | } 43 | } 44 | 45 | // Prop Types holding all the Preferences 46 | AlertSnack.propTypes = { 47 | alertSnack: PropTypes.object.isRequired, 48 | }; 49 | 50 | // Map the State to the Properties of this Component 51 | function mapStateToProps(state, ownProps) { 52 | return { 53 | alertSnack: state.alert_snack, 54 | }; 55 | } 56 | 57 | // Map the actions of the State to the Props of this Class 58 | function mapDispatchToProps(dispatch) { 59 | return { actions: bindActionCreators(actions, dispatch) }; 60 | } 61 | 62 | export default connect(mapStateToProps, mapDispatchToProps)(AlertSnack); 63 | -------------------------------------------------------------------------------- /net2vis/src/components/MainComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import { connect } from "react-redux"; 5 | import { bindActionCreators } from "redux"; 6 | 7 | import { Grid, Paper } from "@mui/material"; 8 | 9 | import * as actions from "../actions"; 10 | import Network from "./network/NetworkComponent"; 11 | import Legend from "./legend/LegendComponent"; 12 | import Preferences from "./preferences/PreferencesComponent"; 13 | import Code from "./code/CodeComponent"; 14 | import Patterns from "./patterns/PatternComponent"; 15 | import AlertSnack from "./AlertSnack"; 16 | 17 | // Main component of the Application that displays all content dependant on the Controls State 18 | class Main extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | var { id } = props.match.params; 22 | if (id !== undefined) { 23 | this.props.actions.setID(id); 24 | this.props.actions.reloadAllState(id, this.props.color_mode.generation); 25 | } 26 | } 27 | 28 | makeId(length) { 29 | var result = ""; 30 | var characters = 31 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 32 | var charactersLength = characters.length; 33 | for (var i = 0; i < length; i++) { 34 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 35 | } 36 | return result; 37 | } 38 | 39 | componentDidUpdate(prevProps) { 40 | // Only reload if the state changes somewhere else than in these 41 | if ( 42 | this.props.color_mode.generation === prevProps.color_mode.generation && 43 | this.props.code_toggle === prevProps.code_toggle && 44 | this.props.preferences_toggle === prevProps.preferences_toggle 45 | ) { 46 | const { id } = this.props.match.params; 47 | this.props.actions.setID(id); 48 | this.props.actions.reloadAllState(id, this.props.color_mode.generation); 49 | } 50 | } 51 | 52 | // MouseDown Listener for SVG, recording the Position and registering MouseMove Listener 53 | handleMouseDown = (e) => { 54 | this.coords = { 55 | x: e.pageX, 56 | y: e.pageY, 57 | }; 58 | document.addEventListener("mousemove", this.handleMouseMove); 59 | this.props.actions.setPreferenceMode("network"); 60 | this.props.actions.setSelectedLegendItem(""); 61 | if (!e.shiftKey) { 62 | this.props.actions.deselectLayers(); 63 | } 64 | }; 65 | 66 | // MouseUp Listener for SVG, ending the drag option by removing the MouseMove Listener 67 | handleMouseUp = () => { 68 | document.removeEventListener("mousemove", this.handleMouseMove); 69 | this.coords = {}; 70 | }; 71 | 72 | // MouseMove Listener, moving the SVG around 73 | handleMouseMove = (e) => { 74 | const xDiff = this.coords.x - e.pageX; 75 | const yDiff = this.coords.y - e.pageY; 76 | 77 | this.coords.x = e.pageX; 78 | this.coords.y = e.pageY; 79 | 80 | this.props.actions.moveGroup([xDiff, yDiff]); 81 | }; 82 | 83 | // Scroll Listener, handling SVG zoom Actions 84 | handleScroll = (e) => { 85 | const delta = e.deltaY / Math.abs(e.deltaY); 86 | this.props.actions.zoomGroup(-delta); 87 | }; 88 | 89 | // Render the Main Content and call other Elements 90 | render() { 91 | var { id } = this.props.match.params; 92 | if (id === undefined) { 93 | id = this.makeId(50); 94 | return ; 95 | } else { 96 | return ( 97 | 98 | {this.props.code_toggle && ( 99 | 100 | 101 | 102 | 103 | 104 | )} 105 | 106 | 107 | 108 | 109 | 117 | 118 | 119 | 120 | 121 | 122 | {this.props.legend_toggle && ( 123 | 124 | 125 | 126 | 127 | 128 | )} 129 | 130 | 131 | {this.props.preferences_toggle && ( 132 | 133 | 134 | 135 | )} 136 | 137 | 138 | ); 139 | } 140 | } 141 | } 142 | 143 | Main.propTypes = { 144 | legend_toggle: PropTypes.bool, 145 | code_toggle: PropTypes.bool.isRequired, 146 | preferences_toggle: PropTypes.bool.isRequired, 147 | color_mode: PropTypes.object.isRequired, 148 | }; 149 | 150 | function mapStateToProps(state, ownProps) { 151 | return { 152 | legend_toggle: state.display.legend_toggle, 153 | code_toggle: state.display.code_toggle, 154 | preferences_toggle: state.display.preferences_toggle, 155 | color_mode: state.color_mode, 156 | }; 157 | } 158 | 159 | // Mapping the Actions called for SVG manipulation to the Props of this Class 160 | function mapDispatchToProps(dispatch) { 161 | return { actions: bindActionCreators(actions, dispatch) }; 162 | } 163 | 164 | export default connect(mapStateToProps, mapDispatchToProps)(Main); 165 | -------------------------------------------------------------------------------- /net2vis/src/components/controls/ToggleButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | // ToggleButton Control Element appearance dependant on State of the Button. Action that is provided gets called on click. 5 | const ToggleButton = ({ name, state, action }) => { 6 | if (state) { 7 | return ( 8 |
action()}> 9 | {name} 10 |
11 | ); 12 | } else { 13 | return ( 14 |
action()}> 15 | {name} 16 |
17 | ); 18 | } 19 | }; 20 | 21 | // Proptypes of ToggleBurttons 22 | ToggleButton.propTypes = { 23 | name: PropTypes.string.isRequired, 24 | state: PropTypes.bool.isRequired, 25 | action: PropTypes.func.isRequired, 26 | }; 27 | 28 | export default ToggleButton; 29 | -------------------------------------------------------------------------------- /net2vis/src/components/input/InputField.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { SketchPicker, TwitterPicker } from "react-color"; 4 | 5 | import * as colors from "../../colors"; 6 | 7 | import { 8 | TextField, 9 | Button, 10 | FormControlLabel, 11 | FormControl, 12 | Switch, 13 | Select, 14 | MenuItem, 15 | InputLabel, 16 | Box, 17 | } from "@mui/material"; 18 | 19 | // ToggleButton Control Element appearance dependant on State of the Button. Action that is provided gets called on click. 20 | const InputField = ({ 21 | value, 22 | type, 23 | description, 24 | action, 25 | options, 26 | active = true, 27 | }) => { 28 | switch (type) { 29 | case "number": 30 | return ( 31 |
32 | action(e)} 38 | margin="normal" 39 | className="inputElement" 40 | inputProps={{ min: "0", max: "1000", step: "10" }} 41 | /> 42 |
43 | ); 44 | case "color": 45 | return ( 46 |
47 | {options === "Palette" ? ( 48 | action(e)} 54 | /> 55 | ) : ( 56 | action(e)} 62 | /> 63 | )} 64 |
65 | ); 66 | case "text": 67 | return ( 68 |
69 | action(e)} 74 | margin="normal" 75 | className="inputElement" 76 | /> 77 |
78 | ); 79 | case "button": 80 | return ( 81 |
82 | 91 |
92 | ); 93 | case "paddedButton": 94 | return ( 95 |
96 | 105 |
106 | ); 107 | case "codeButton": 108 | return ( 109 |
110 | 119 |
120 | ); 121 | case "switch": 122 | return ( 123 |
124 | action(e)} 129 | value="checkedB" 130 | color="primary" 131 | /> 132 | } 133 | label={description} 134 | /> 135 |
136 | ); 137 | case "select": 138 | return ( 139 |
140 | 141 | {description} 142 | 154 | 155 |
156 | ); 157 | case "barselect": 158 | return ( 159 |
160 | 161 | 162 | 169 | 170 | 171 |
172 | ); 173 | default: 174 | return
; 175 | } 176 | }; 177 | 178 | // Proptypes of ToggleBurttons 179 | InputField.propTypes = { 180 | value: PropTypes.any.isRequired, 181 | type: PropTypes.string.isRequired, 182 | description: PropTypes.string.isRequired, 183 | action: PropTypes.func.isRequired, 184 | }; 185 | 186 | export default InputField; 187 | -------------------------------------------------------------------------------- /net2vis/src/components/legend/ComplexLegendItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | 5 | import EdgeComponent from "../network/EdgeComponent"; 6 | 7 | import * as paths from "../../paths"; 8 | import * as colors from "../../colors"; 9 | 10 | class ComplexLegendItem extends React.Component { 11 | render() { 12 | var set = this.props.layer_types_settings[this.props.layer.layer.name]; 13 | const noColors = this.props.preferences.no_colors.value; 14 | let isGroup = false; 15 | const selected = this.props.selected === this.props.layer.layer.name; 16 | for (var group in this.props.groups) { 17 | if (this.props.groups[group].name === this.props.layer.layer.name) { 18 | isGroup = true; 19 | } 20 | } 21 | const style = { 22 | fill: colors.getFillColor(set, noColors, isGroup), 23 | stroke: colors.getStrokeColor(set, noColors, isGroup, selected), 24 | strokeLinejoin: "round", 25 | strokeWidth: isGroup 26 | ? this.props.legend_preferences.stroke_width.value * 3 27 | : this.props.legend_preferences.stroke_width.value, 28 | }; 29 | const extreme_dimensions = { 30 | max_size: this.props.legend_preferences.layer_height.value, 31 | min_size: this.props.legend_preferences.layer_height.value, 32 | }; // Get the Extremes of the Display Size for the Glyphs 33 | const pathableLayer = { 34 | layer: { 35 | id: parseInt(this.props.layer.id, 10), 36 | properties: { 37 | input: this.props.layer.layer.properties.input, 38 | output: this.props.layer.layer.properties.output, 39 | }, 40 | }, 41 | width: this.props.legend_preferences.layer_width.value, 42 | y: this.props.layer.y, 43 | }; 44 | const pathData = paths.calculateGlyphPath( 45 | extreme_dimensions, 46 | [extreme_dimensions.max_size, extreme_dimensions.max_size], 47 | pathableLayer, 48 | this.props.edges 49 | ); // Calculate the Path of the Layer 50 | const current_edges = paths.getOutgoingEdges( 51 | pathableLayer, 52 | this.props.edges 53 | ); // Get relevant Edges going out from the current Layer 54 | 55 | return ( 56 | 63 | {current_edges.map((edge, index) => ( 64 | 73 | ))} 74 | 80 | this.props.action(this.props.layer.layer.name)} 84 | /> 85 | 86 | 87 | ); 88 | } 89 | } 90 | 91 | // PropTypes of this Class 92 | ComplexLegendItem.propTypes = { 93 | groups: PropTypes.array.isRequired, 94 | layer_types_settings: PropTypes.object.isRequired, 95 | legend_preferences: PropTypes.object.isRequired, 96 | preferences: PropTypes.object.isRequired, 97 | selected: PropTypes.string.isRequired, 98 | }; 99 | 100 | // Map the State of the Application to the Props of this Class 101 | function mapStateToProps(state, ownProps) { 102 | return { 103 | groups: state.groups, 104 | layer_types_settings: state.layer_types_settings, 105 | legend_preferences: state.legend_preferences, 106 | preferences: state.preferences, 107 | }; 108 | } 109 | 110 | export default connect(mapStateToProps, undefined)(ComplexLegendItem); 111 | -------------------------------------------------------------------------------- /net2vis/src/components/legend/LegendItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | 5 | import * as legend from "../../legend"; 6 | import * as colors from "../../colors"; 7 | 8 | import ComplexLegendItem from "./ComplexLegendItem"; 9 | 10 | class LegendItem extends React.Component { 11 | render() { 12 | // Set the properties of the item to be drawn 13 | const set = this.props.representation.layer.representer.setting; 14 | const noColors = this.props.preferences.no_colors.value; 15 | const isGroup = !this.props.representation.layer.trivial; 16 | const selected = 17 | this.props.selected === this.props.representation.layer.representer.name; 18 | const strokeColor = this.props.representation.layer.dense 19 | ? colors.getFillColor(set, noColors, isGroup) 20 | : colors.getStrokeColor(set, noColors, isGroup, selected); 21 | const style = { 22 | fill: colors.getFillColor(set, noColors, isGroup), 23 | stroke: this.props.representation.layer.active 24 | ? strokeColor 25 | : "lightgrey", 26 | strokeLinejoin: "round", 27 | strokeWidth: this.props.legend_preferences.stroke_width.value, 28 | }; 29 | const textStyle = { 30 | fill: this.props.representation.layer.active ? "black" : "lightgrey", 31 | }; 32 | if (this.props.representation.layer.trivial) { 33 | return ( 34 | 35 | 44 | {this.props.representation.layer.representer.setting.alias} 45 | 46 | 53 | this.props.action( 54 | this.props.representation.layer.representer.name 55 | ) 56 | } 57 | /> 58 | 59 | ); 60 | } else { 61 | const graph = this.props.representation.layer.graph; 62 | var nodes = []; 63 | graph.nodes().forEach(function (e) { 64 | nodes.push(graph.node(e)); 65 | }); 66 | var edges = []; 67 | graph.edges().forEach(function (e) { 68 | edges.push({ v: e.v, w: e.w, points: graph.edge(e) }); 69 | }); 70 | style.strokeWidth = this.props.legend_preferences.stroke_width.value * 3; 71 | var displacement = 72 | nodes[legend.getInputNode(nodes)].y - 73 | this.props.legend_preferences.layer_height.value / 2.0; // Calculate the displacement if an inputnode to a legenditem is not standardly placed in the legend 74 | return ( 75 | 76 | 83 | this.props.action( 84 | this.props.representation.layer.representer.name 85 | ) 86 | } 87 | /> 88 | 98 | = 99 | 100 | 106 | 120 | {this.props.representation.layer.representer.setting.alias} 121 | 122 | {nodes.map((layer, i) => ( 123 | 133 | ))} 134 | 135 | 136 | ); 137 | } 138 | } 139 | } 140 | 141 | // PropTypes of this Class 142 | LegendItem.propTypes = { 143 | legend_preferences: PropTypes.object.isRequired, 144 | preferences: PropTypes.object.isRequired, 145 | selected: PropTypes.string.isRequired, 146 | }; 147 | 148 | // Map the State of the Application to the Props of this Class 149 | function mapStateToProps(state, ownProps) { 150 | return { 151 | legend_preferences: state.legend_preferences, 152 | preferences: state.preferences, 153 | }; 154 | } 155 | 156 | export default connect(mapStateToProps, undefined)(LegendItem); 157 | -------------------------------------------------------------------------------- /net2vis/src/components/network/DimensionsLabelComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const DimensionsLabelComponent = ({ 5 | dimensions, 6 | x, 7 | edge, 8 | layer_max_height, 9 | channels_first, 10 | }) => { 11 | var y_pos = edge.points[0].y; // Initialize the y_pos point Placeholder 12 | for (var j = 1; j < edge.points.length; j++) { 13 | // For all other Points 14 | if (y_pos === edge.points[j].y) { 15 | // The y_pos point had the same y value 16 | j = edge.points.length; // Break the Loop 17 | } else { 18 | // Not the same y value 19 | y_pos = edge.points[j].y; // Update the y_pos point Placeholder 20 | } 21 | } 22 | const initialIndex = channels_first ? 1 : 0; 23 | const lastIndex = channels_first ? dimensions.length : dimensions.length - 1; 24 | var dimTxt = dimensions[initialIndex]; // Initialize the dimensions Texts 25 | for (var i = initialIndex + 1; i < lastIndex; i++) { 26 | // For all dimensions except the last 27 | dimTxt = dimTxt + "x" + dimensions[i]; // Add it to the texts 28 | } 29 | const transform = `translate(${x}, ${5 + y_pos + layer_max_height / 2.0})`; // Manipulate the position of the graph 30 | return ( 31 | 32 | 33 | {dimTxt} 34 | 35 | 36 | ); 37 | }; 38 | 39 | // Proptypes of ToggleBurttons 40 | DimensionsLabelComponent.propTypes = { 41 | dimensions: PropTypes.array.isRequired, 42 | x: PropTypes.number.isRequired, 43 | edge: PropTypes.object.isRequired, 44 | layer_max_height: PropTypes.number.isRequired, 45 | channels_first: PropTypes.bool.isRequired, 46 | }; 47 | 48 | export default DimensionsLabelComponent; 49 | -------------------------------------------------------------------------------- /net2vis/src/components/network/EdgeComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const EdgeComponent = ({ 5 | edge, 6 | layer_max_height, 7 | horizontal_spacing, 8 | color, 9 | }) => { 10 | var points = JSON.parse(JSON.stringify(edge.points)); 11 | var path = ""; 12 | var y_pos = points[0].y; // Initialize the y_pos point Placeholder 13 | for (var j = 1; j < points.length; j++) { 14 | // For all other Points 15 | if (y_pos === points[j].y) { 16 | // The y_pos point had the same y value 17 | j = points.length; // Break the Loop 18 | } else { 19 | // Not the same y value 20 | y_pos = points[j].y; // Update the y_pos point Placeholder 21 | } 22 | } 23 | if (points[0].y !== y_pos) { 24 | // Check if the beggining of the path has a slope 25 | for (var i = 0; i < points.length; i++) { 26 | // Iterate over all points 27 | if (points[i].y === y_pos) { 28 | // The first point of the straight line part has been found 29 | points[i].x = points[i].x - horizontal_spacing.value / 2; // Change the x-value of this point to compensate the spacing 30 | i = points[i].length; // Exit the loop 31 | } 32 | } 33 | } 34 | if (points[points.length - 1].y !== y_pos) { 35 | // Check if the end of the path has a slope 36 | for (i = points.length - 1; i > 0; i--) { 37 | // Iterate over all points in reversed order 38 | if (points[i].y === y_pos) { 39 | // The last point of the straight line part has been found 40 | points[i].x = points[i].x + horizontal_spacing.value / 2; // Change the x-value of this point to compensate the spacing 41 | i = 0; // Exit the loop 42 | } 43 | } 44 | } 45 | for (i in points) { 46 | // Go over all Points 47 | if (points[i].y === y_pos) { 48 | // Check if on same y as y_pos 49 | path = 50 | path + points[i].x + "," + (points[i].y + layer_max_height / 2) + " "; // Add the Point to the Path 51 | } 52 | } 53 | return ; 54 | }; 55 | 56 | // Proptypes of ToggleBurttons 57 | EdgeComponent.propTypes = { 58 | edge: PropTypes.object.isRequired, 59 | layer_max_height: PropTypes.number.isRequired, 60 | }; 61 | 62 | export default EdgeComponent; 63 | -------------------------------------------------------------------------------- /net2vis/src/components/network/EdgesComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { bindActionCreators } from "redux"; 5 | 6 | import * as actions from "../../actions"; 7 | 8 | import EdgeComponent from "./EdgeComponent"; 9 | import DimensionsLabelComponent from "./DimensionsLabelComponent"; 10 | 11 | import * as paths from "../../paths"; 12 | 13 | // Layer Component providing individual Layer Visualizations 14 | class Edges extends React.Component { 15 | // Render the Layer 16 | render() { 17 | // Get the Properties to use them in the Rendering 18 | const dimensions = this.props.layer.layer.properties.dimensions; // Get the Dimensions of the current Layer 19 | const current_edges = paths.getOutgoingEdges( 20 | this.props.layer, 21 | this.props.edges 22 | ); // Get relevant Edges going out from the current Layer 23 | const dimensionsLabelX = 24 | this.props.layer.x + 25 | this.props.layer.width / 2.0 + 26 | this.props.preferences.layers_spacing_horizontal.value / 2.0; 27 | var additionalLabelInput = undefined; // Placeholder for label if this is an input of the Net 28 | if (this.props.layer.layer.properties.input.length === 0) { 29 | // If no inputs 30 | additionalLabelInput = { 31 | dimensions: dimensions.in, // Dimensions for this label are the input dimensions of the layer 32 | x: 33 | 2.0 * this.props.layer.x - 34 | dimensionsLabelX - 35 | this.props.preferences.stroke_width.value, // X position of the label to be left of layer 36 | edge: { 37 | // Edge position is layer y 38 | points: [{ y: this.props.layer.y }], 39 | }, 40 | }; 41 | } 42 | var additionalLabelOutput = undefined; // Placeholder for label if this is an output of the net 43 | if (this.props.layer.layer.properties.output.length === 0) { 44 | // If no outputs 45 | additionalLabelOutput = { 46 | dimensions: dimensions.out, // Dimensions for this label are the output dimensions of the layer 47 | x: dimensionsLabelX, // X position of the label ro be right of layer 48 | edge: { 49 | // Edge position is layer y 50 | points: [{ y: this.props.layer.y }], 51 | }, 52 | }; 53 | } 54 | // Return a Shape with the calculated parameters and add the Property Tooltip 55 | return ( 56 | 57 | {current_edges.map((edge, index) => ( 58 | 59 | 69 | {this.props.preferences.show_dimensions.value && 70 | dimensions.out.length > 1 && ( 71 | 80 | )} 81 | 82 | ))} 83 | {this.props.preferences.show_dimensions.value && 84 | additionalLabelInput !== undefined && 85 | dimensions.out.length > 1 && ( 86 | 95 | )} 96 | {this.props.preferences.show_dimensions.value && 97 | additionalLabelOutput !== undefined && 98 | dimensions.out.length > 1 && ( 99 | 108 | )} 109 | 110 | ); 111 | } 112 | } 113 | 114 | // PropTypes of this Class, containing the Global Layer Settings 115 | Edges.propTypes = { 116 | preferences: PropTypes.object.isRequired, 117 | layer_extreme_dimensions: PropTypes.object.isRequired, 118 | selection: PropTypes.array.isRequired, 119 | layer: PropTypes.object.isRequired, 120 | edges: PropTypes.array.isRequired, 121 | }; 122 | 123 | // Map the State of the Application to the Props of this Class 124 | function mapStateToProps(state, ownProps) { 125 | return { 126 | preferences: state.preferences, 127 | layer_extreme_dimensions: state.layer_extreme_dimensions, 128 | selection: state.selection, 129 | }; 130 | } 131 | 132 | // Map the Actions for the State to the Props of this Class 133 | function mapDispatchToProps(dispatch) { 134 | return { actions: bindActionCreators(actions, dispatch) }; 135 | } 136 | 137 | export default connect(mapStateToProps, mapDispatchToProps)(Edges); 138 | -------------------------------------------------------------------------------- /net2vis/src/components/network/FeaturesLabelComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import * as paths from "../../paths"; 5 | 6 | const FeaturesLabelComponent = ({ 7 | features, 8 | x, 9 | layer_height, 10 | extreme_dimensions, 11 | layer, 12 | edges, 13 | }) => { 14 | const y_diff = [ 15 | (extreme_dimensions.max_size - layer_height[0]) / 2, 16 | (extreme_dimensions.max_size - layer_height[1]) / 2, 17 | ]; // Calculate the vertical difference to center the Glyph 18 | const y_pos = [y_diff[1] + layer_height[1], y_diff[0] + layer_height[0]]; // Vertical Position of bottom-right and bottom-left Points 19 | if (layer.layer.properties.input.length > 1) { 20 | const position_reduced = paths.reducePosition( 21 | paths.getIncomingEdges(layer, edges) 22 | ); // Get the y positions of the straight parts of alloutgoing edges 23 | const y_off = Math.max(...position_reduced) - layer.y; // Calculate the Offset of the current Input Layer to this Layer 24 | y_pos[1] = y_pos[1] + y_off; 25 | } else if (layer.layer.properties.output.length > 1) { 26 | const position_reduced = paths.reducePosition( 27 | paths.getOutgoingEdges(layer, edges) 28 | ); // Get the y positions of the straight parts of alloutgoing edges 29 | const y_off = Math.max(...position_reduced) - layer.y; // Calculate the Offset of the current Input Layer to this Layer 30 | y_pos[0] = y_pos[0] + y_off; 31 | } 32 | var y = Math.max(y_pos[0], y_pos[1]); 33 | return ( 34 | 35 | {features} 36 | 37 | ); 38 | }; 39 | 40 | // Proptypes of ToggleBurttons 41 | FeaturesLabelComponent.propTypes = { 42 | features: PropTypes.number.isRequired, 43 | x: PropTypes.number.isRequired, 44 | layer_height: PropTypes.array.isRequired, 45 | extreme_dimensions: PropTypes.object.isRequired, 46 | layer: PropTypes.object.isRequired, 47 | edges: PropTypes.array.isRequired, 48 | }; 49 | 50 | export default FeaturesLabelComponent; 51 | -------------------------------------------------------------------------------- /net2vis/src/components/network/NameLabelComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import * as paths from "../../paths"; 5 | 6 | const NameLabelComponent = ({ 7 | name, 8 | x, 9 | layer_height, 10 | extreme_dimensions, 11 | layer, 12 | edges, 13 | features_above, 14 | }) => { 15 | const y_diff = [ 16 | (extreme_dimensions.max_size - layer_height[0]) / 2, 17 | (extreme_dimensions.max_size - layer_height[1]) / 2, 18 | ]; // Calculate the vertical difference to center the Glyph 19 | let y_pos = [y_diff[1] + layer_height[1], y_diff[0] + layer_height[0]]; // Vertical Position of bottom-right and bottom-left Points 20 | if (layer.layer.properties.input.length > 1) { 21 | const position_reduced = paths.reducePosition( 22 | paths.getIncomingEdges(layer, edges) 23 | ); // Get the y positions of the straight parts of alloutgoing edges 24 | const y_off = Math.max(...position_reduced) - layer.y; // Calculate the Offset of the current Input Layer to this Layer 25 | y_pos[1] = y_pos[1] + y_off; 26 | } else if (layer.layer.properties.output.length > 1) { 27 | const position_reduced = paths.reducePosition( 28 | paths.getOutgoingEdges(layer, edges) 29 | ); // Get the y positions of the straight parts of alloutgoing edges 30 | const y_off = Math.max(...position_reduced) - layer.y; // Calculate the Offset of the current Input Layer to this Layer 31 | y_pos[0] = y_pos[0] + y_off; 32 | } 33 | let y = Math.max(y_pos[0], y_pos[1]); 34 | y = features_above ? y + 12 + 5 : y; 35 | return ( 36 | 37 | {name} 38 | 39 | ); 40 | }; 41 | 42 | // Proptypes of ToggleBurttons 43 | NameLabelComponent.propTypes = { 44 | name: PropTypes.string.isRequired, 45 | x: PropTypes.number.isRequired, 46 | layer_height: PropTypes.array.isRequired, 47 | extreme_dimensions: PropTypes.object.isRequired, 48 | layer: PropTypes.object.isRequired, 49 | edges: PropTypes.array.isRequired, 50 | features_above: PropTypes.bool.isRequired, 51 | }; 52 | 53 | export default NameLabelComponent; 54 | -------------------------------------------------------------------------------- /net2vis/src/components/network/SampleComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const SampleComponent = ({ 5 | extent, 6 | x, 7 | edge, 8 | layer_max_height, 9 | strokeWidth, 10 | }) => { 11 | var y_pos = edge.points[0].y; // Initialize the y_pos point Placeholder 12 | for (var j = 1; j < edge.points.length; j++) { 13 | // For all other Points 14 | if (y_pos === edge.points[j].y) { 15 | // The y_pos point had the same y value 16 | j = edge.points.length; // Break the Loop 17 | } else { 18 | // Not the same y value 19 | y_pos = edge.points[j].y; // Update the y_pos point Placeholder 20 | } 21 | } 22 | const transform = `translate(${x}, ${ 23 | y_pos + layer_max_height / 2.0 - extent / 2.0 24 | })`; // Manipulate the position of the graph 25 | const style = { 26 | stroke: "lightgrey", 27 | strokeLinejoin: "round", 28 | strokeWidth: strokeWidth, 29 | fillOpacity: 0.0, 30 | strokeDasharray: 4, 31 | }; 32 | return ( 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | // Proptypes of ToggleBurttons 40 | SampleComponent.propTypes = { 41 | extent: PropTypes.number.isRequired, 42 | x: PropTypes.number.isRequired, 43 | edge: PropTypes.object.isRequired, 44 | }; 45 | 46 | export default SampleComponent; 47 | -------------------------------------------------------------------------------- /net2vis/src/components/network/TooltipComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Tooltip } from "react-svg-tooltip"; 4 | 5 | // ToggleButton Control Element appearance dependant on State of the Button. Action that is provided gets called on click. 6 | const TooltipComponent = ({ 7 | properties_object, 8 | dimensions, 9 | tooltipRef, 10 | name, 11 | }) => { 12 | // Build the Properties Object for the Tooltip 13 | const buildProperties = (properties_object, dimensions) => { 14 | const keys = Object.keys(properties_object); // Get the Keys from the Object 15 | var properties = []; // Get all Properties 16 | for (var i in keys) { 17 | if (properties_object[keys[i]]) { 18 | properties.push({ 19 | key: keys[i], 20 | prop: properties_object[keys[i]].toString(), 21 | }); 22 | } 23 | } 24 | // Add Dimensions to Properties 25 | properties.push({ key: "Dimensions in", prop: dimensions.in.toString() }); 26 | properties.push({ key: "Dimensions out", prop: dimensions.out.toString() }); 27 | return properties; 28 | }; 29 | 30 | const properties = buildProperties(properties_object, dimensions); 31 | return ( 32 | 33 | 43 | 44 | Type: {name} 45 | 46 | {properties.map((pro, index) => ( 47 | 48 | {pro.key}: {pro.prop} 49 | 50 | ))} 51 | 52 | ); 53 | }; 54 | 55 | // Proptypes of ToggleBurttons 56 | TooltipComponent.propTypes = { 57 | properties_object: PropTypes.object.isRequired, 58 | dimensions: PropTypes.object.isRequired, 59 | tooltipRef: PropTypes.object.isRequired, 60 | name: PropTypes.string.isRequired, 61 | }; 62 | 63 | export default TooltipComponent; 64 | -------------------------------------------------------------------------------- /net2vis/src/components/patterns/PatternComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // Component for displaying the code of the Neural Network implementation 4 | class Patterns extends React.Component { 5 | // Render the Code into the Code View if Toggled 6 | render() { 7 | return ( 8 | // Editor with Syntax highlighting 9 | 10 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 39 | 40 | 41 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 71 | 76 | 77 | 85 | 90 | 91 | 99 | 104 | 105 | 113 | 114 | 115 | 123 | 124 | 125 | 133 | 134 | 135 | 143 | 144 | 145 | 146 | ); 147 | } 148 | } 149 | 150 | export default Patterns; 151 | -------------------------------------------------------------------------------- /net2vis/src/components/preferences/FeaturesComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { bindActionCreators } from "redux"; 5 | 6 | import InputField from "../input/InputField"; 7 | import * as actions from "../../actions"; 8 | 9 | class Features extends React.Component { 10 | // Feature Mapping Changes 11 | handleSelectChange = (e) => { 12 | var preferences = this.props.preferences; 13 | preferences.features_mapping.value = e.currentTarget.value; 14 | if (e.currentTarget.value !== "width") { 15 | preferences.layer_display_max_width = preferences.layer_display_min_width; 16 | } 17 | this.props.actions.updatePreferences(preferences, this.props.id); 18 | }; 19 | 20 | // Width of a Layer Changes 21 | handleMinWidthChange = (e) => { 22 | var preferences = this.props.preferences; 23 | preferences.layer_display_min_width.value = parseInt( 24 | e.currentTarget.value, 25 | 10 26 | ); 27 | this.props.actions.updatePreferences(preferences, this.props.id); 28 | }; 29 | 30 | // Width of a Layer Changes 31 | handleMaxWidthChange = (e) => { 32 | var preferences = this.props.preferences; 33 | preferences.layer_display_max_width.value = parseInt( 34 | e.currentTarget.value, 35 | 10 36 | ); 37 | this.props.actions.updatePreferences(preferences, this.props.id); 38 | }; 39 | 40 | // Width of a Layer Changes 41 | handleWidthChange = (e) => { 42 | var preferences = this.props.preferences; 43 | preferences.layer_display_max_width.value = parseInt( 44 | e.currentTarget.value, 45 | 10 46 | ); 47 | preferences.layer_display_min_width.value = parseInt( 48 | e.currentTarget.value, 49 | 10 50 | ); 51 | this.props.actions.updatePreferences(preferences, this.props.id); 52 | }; 53 | 54 | // Render the Preferences of the Visualization 55 | render() { 56 | return ( 57 |
58 | 66 | 74 |
75 | ); 76 | } 77 | } 78 | 79 | // Prop Types holding all the Preferences 80 | Features.propTypes = { 81 | id: PropTypes.string.isRequired, 82 | preferences: PropTypes.object.isRequired, 83 | }; 84 | 85 | // Map the State to the Properties of this Component 86 | function mapStateToProps(state, ownProps) { 87 | return { 88 | id: state.id, 89 | preferences: state.preferences, 90 | }; 91 | } 92 | 93 | // Map the actions of the State to the Props of this Class 94 | function mapDispatchToProps(dispatch) { 95 | return { actions: bindActionCreators(actions, dispatch) }; 96 | } 97 | 98 | export default connect(mapStateToProps, mapDispatchToProps)(Features); 99 | -------------------------------------------------------------------------------- /net2vis/src/components/preferences/PreferencesComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | 5 | import NetworkPreferences from "./NetworkPreferencesComponent"; 6 | import GroupPreferences from "./GroupPreferencesComponent"; 7 | import LayerPreferences from "./LayerPreferencesComponent"; 8 | import LegendPreferences from "./LegendPreferencesComponent"; 9 | 10 | // Component for displaying the Preferences of the Visualization 11 | class Preferences extends React.Component { 12 | // Check if the currently selected Layer is a Group 13 | isInGroups = (selectedLayer) => { 14 | for (var i in this.props.groups) { 15 | if (selectedLayer === this.props.groups[i].name) { 16 | return this.props.groups[i]; 17 | } 18 | } 19 | return null; 20 | }; 21 | 22 | // Render the Preferences of the Visualization 23 | render() { 24 | if (this.props.preferences_toggle) { 25 | switch (this.props.preferences_mode) { 26 | case "network": 27 | return ; 28 | case "color": 29 | var group = this.isInGroups(this.props.selected_legend_item); 30 | if (group !== null) { 31 | return ; 32 | } else { 33 | return ; 34 | } 35 | case "legend": 36 | return ; 37 | default: 38 | return null; 39 | } 40 | } else { 41 | return null; 42 | } 43 | } 44 | } 45 | 46 | // Prop Types holding all the Preferences 47 | Preferences.propTypes = { 48 | preferences_mode: PropTypes.string.isRequired, 49 | preferences_toggle: PropTypes.bool.isRequired, 50 | selected_legend_item: PropTypes.string.isRequired, 51 | groups: PropTypes.array.isRequired, 52 | }; 53 | 54 | // Map the State to the Properties of this Component 55 | function mapStateToProps(state, ownProps) { 56 | return { 57 | preferences_mode: state.preferences_mode, 58 | preferences_toggle: state.display.preferences_toggle, 59 | selected_legend_item: state.selected_legend_item, 60 | groups: state.groups, 61 | }; 62 | } 63 | 64 | export default connect(mapStateToProps, undefined)(Preferences); 65 | -------------------------------------------------------------------------------- /net2vis/src/graphs/index.js: -------------------------------------------------------------------------------- 1 | import * as dagre from 'dagre'; 2 | 3 | // Build the network graph upon the Network representation 4 | export function buildGraphFromNetwork( 5 | network, 6 | layer_extreme_dimensions, 7 | preferences 8 | ) { 9 | var graph = new dagre.graphlib.Graph(); // Initialize the dagre Graph 10 | graph.setGraph({ 11 | ranker: 'network-simplex', 12 | rankdir: 'LR', 13 | ranksep: 14 | preferences.layers_spacing_horizontal.value + 15 | preferences.stroke_width.value, 16 | nodesep: preferences.layers_spacing_vertical.value, 17 | }); // Set Graph Properties 18 | graph.setDefaultEdgeLabel(function () { 19 | return {}; 20 | }); // Default Egde Label needs to be set 21 | for (var i in network.layers) { 22 | // Add all Layers to the Graph 23 | const layer = network.layers[i]; // Get the current Layer 24 | if ( 25 | layer.properties.dimensions.in.length > 1 && 26 | layer.properties.dimensions.out.length > 1 27 | ) { 28 | const channel_dim = preferences.channels_first.value 29 | ? 0 30 | : layer.properties.dimensions.out.length - 1; 31 | const spatial_dim = preferences.channels_first.value ? 1 : 0; 32 | const max_layer_dim = Math.max( 33 | layer.properties.dimensions.in[spatial_dim], 34 | layer.properties.dimensions.out[spatial_dim] 35 | ); // Get the maximum dimension of the layer (in vs out) 36 | var lay_diff = 37 | layer_extreme_dimensions.max_size - layer_extreme_dimensions.min_size; // Get the difference between Max and Min for the Extremes of the Layer 38 | lay_diff = lay_diff === 0 ? 1 : lay_diff; // Check if there is any difference in spatial resolution at all 39 | const dim_diff = 40 | preferences.layer_display_max_height.value - 41 | preferences.layer_display_min_height.value; // Get the difference between Max and Min for the Extremes of the Glyph Dimensions 42 | const perc = 43 | (max_layer_dim - layer_extreme_dimensions.min_size) / lay_diff; // Calculate the interpolation factor for boths sides of the Glyph 44 | var height = perc * dim_diff + preferences.layer_display_min_height.value; // Calculate the height for both sides of the Glyph 45 | height = 46 | height > preferences.layer_display_max_height.value 47 | ? preferences.layer_display_max_height.value 48 | : height; // Cap the height when something goes wrong 49 | var feat_diff = 50 | layer_extreme_dimensions.max_features - 51 | layer_extreme_dimensions.min_features; // Get the difference between max and min features of the Layers 52 | feat_diff = feat_diff === 0 ? 1 : feat_diff; // Check if there is any difference in features at all 53 | const fdim_diff = 54 | preferences.layer_display_max_width.value - 55 | preferences.layer_display_min_width.value; // Get the differnece between Max and Min width for the Glyph Dimensions 56 | const f_perc = (layer.properties.dimensions.out[channel_dim] - layer_extreme_dimensions.min_features) / feat_diff; // Calculate the interpolation factor 57 | const width = 58 | f_perc * fdim_diff + preferences.layer_display_min_width.value; // Calculate the width of the glyph 59 | graph.setNode(layer.id, { width: width, height: height, layer: layer }); // Add a Node to the Graph 60 | } else { 61 | graph.setNode(layer.id, { 62 | width: preferences.layer_display_min_width.value, 63 | height: preferences.layer_display_max_height.value, 64 | layer: layer, 65 | }); // Add a Node to the Graph 66 | } 67 | } 68 | for (var j in network.layers) { 69 | // Add all Edges to the Graph 70 | var layer_current = network.layers[j]; // Get the current Layer 71 | for (var k in layer_current.properties.output) { 72 | // Go over all outputs of the current Layer 73 | graph.setEdge(layer_current.id, layer_current.properties.output[k]); // Add the Edge to the Graph 74 | } 75 | } 76 | dagre.layout(graph); // Layout the graph to be displayed in a nice fashion 77 | return graph; 78 | } 79 | -------------------------------------------------------------------------------- /net2vis/src/groups/Activation.js: -------------------------------------------------------------------------------- 1 | // Deactivates a Group and all Groups that depend on it 2 | export function deactivateGroup(selectedItem, groups) { 3 | var groupIndices = findDependingGroups(selectedItem, groups); // Get the indices for all groups to be deactivated 4 | for (var i in groupIndices) { 5 | // For all of these indices 6 | groups[groupIndices[i]].active = false; // Deactivate the group 7 | } 8 | } 9 | 10 | // Find and return all groups that depend on a given group 11 | export function findDependingGroups(selectedItem, groups) { 12 | var indices = findGroupDependenciesByName(selectedItem, groups); // Get all the indices of dependancy 13 | var unique = indices.filter(onlyUnique); // Remove duplicates from the array 14 | return unique; 15 | } 16 | 17 | // Find all Groups that depend on a given Group 18 | function findGroupDependenciesByName(selectedItem, groups) { 19 | var deps = []; // Initialize the dependencies Array 20 | for (var i = 0; i < groups.length; i++) { 21 | // For all Groups 22 | if (groups[i].name === selectedItem) { 23 | // If this is the given Group 24 | deps.push(i); // Add it to the Dependencies 25 | for (var j = 0; j < groups.length; j++) { 26 | // For all Groups 27 | for (var k = 0; k < groups[j].layers.length; k++) { 28 | // For all of ther Layers 29 | if (groups[i].name === groups[j].layers[k].name) { 30 | // If a layer has the same name as the Group that was given 31 | var recursion = findGroupDependenciesByName(groups[j].name, groups); // Recursively resolve their dependencies 32 | for (var l in recursion) { 33 | // For all recursive deps 34 | deps.push(recursion[l]); // Add them to the dependencies 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | return deps; 42 | } 43 | 44 | // Activate a selected Group 45 | export function activateGroup(selectedItem, groups) { 46 | var groupIndices = findGroupDependents(selectedItem, groups); // Get the indices for all groups to be deactivated 47 | for (var i in groupIndices) { 48 | // For all of these indices 49 | groups[groupIndices[i]].active = true; // Deactivate the group 50 | } 51 | } 52 | 53 | // Find all Groups a given Group depends on 54 | function findGroupDependents(selectedItem, groups) { 55 | var indices = findGroupDependentsByName(selectedItem, groups); // Get all the indices of dependancy 56 | var unique = indices.filter(onlyUnique); // Remove duplicates from the array 57 | return unique; 58 | } 59 | 60 | // Find all groups, that the current group depends on 61 | function findGroupDependentsByName(selectedItem, groups) { 62 | var deps = []; // Initialize the dependents array 63 | for (var i = 0; i < groups.length; i++) { 64 | // For all Groups 65 | if (groups[i].name === selectedItem) { 66 | // If this is the current Group 67 | deps.push(i); // Add it to the Dependents 68 | for (var j = 0; j < groups[i].layers.length; j++) { 69 | // For all Layers of this Group 70 | for (var k = 0; k < groups.length; k++) { 71 | // For all Groups 72 | if (groups[k].name === groups[i].layers[j].name) { 73 | // If a Group has the same name as one of the Layers of the current Group 74 | var recursion = findGroupDependentsByName(groups[k].name, groups); // Recursively resolve their dependents 75 | for (var l in recursion) { 76 | // For all recursive deps 77 | deps.push(recursion[l]); // Add them to the dependencies 78 | } 79 | } 80 | } 81 | } 82 | return deps; 83 | } 84 | } 85 | } 86 | 87 | // Filter function to remove duplicates in the Indices List 88 | function onlyUnique(value, index, self) { 89 | return self.indexOf(value) === index; // Checks, if this the given value is at position index in the array 90 | } 91 | -------------------------------------------------------------------------------- /net2vis/src/groups/Addition.js: -------------------------------------------------------------------------------- 1 | import * as occurences from "./Occurences"; 2 | import * as concatenation from "./Concatenation"; 3 | 4 | // A group was added to the Network, so the groups might need to also get grouped 5 | export function addGroup(groups, group) { 6 | for (var i in groups) { 7 | // Check each Group 8 | var groupOccur = occurences.findGroupOccurences(group, groups[i]); // If the new Group occurs in it 9 | for (var j in groupOccur) { 10 | // For all Occurences 11 | groups[i].layers = concatenation.concatenateLayers( 12 | groupOccur[j], 13 | groups[i], 14 | group 15 | ).layers; // Concatenate the Occurence 16 | } 17 | } 18 | groups.push(group); // Add the new Group to the existing ones 19 | } 20 | -------------------------------------------------------------------------------- /net2vis/src/groups/Common.js: -------------------------------------------------------------------------------- 1 | // Find an input Node of a Group 2 | export function findInputNode(group) { 3 | for (var j in group.layers) { 4 | // Iterate over all layers in the Group 5 | if (group.layers[j].properties.input.length === 0) { 6 | // Layer has no inputs contained in the Group 7 | return { inputID: j, inputNode: group.layers[j] }; 8 | } 9 | } 10 | } 11 | 12 | // Find an input Node of a Group 13 | export function findOutputNode(group) { 14 | for (var j in group.layers) { 15 | // Iterate over all layers in the Group 16 | if (group.layers[j].properties.output.length === 0) { 17 | // Layer has no inputs contained in the Group 18 | return { outputID: j, outputNode: group.layers[j] }; 19 | } 20 | } 21 | } 22 | 23 | // Returning the maximum ID in the current network. 24 | export function maxID(network) { 25 | var id = 0; // Initialize the max ID 26 | for (var i in network.layers) { 27 | // Check all layers 28 | id = network.layers[i].id > id ? network.layers[i].id : id; // Set to bigger of current layer id and id 29 | } 30 | return id; 31 | } 32 | -------------------------------------------------------------------------------- /net2vis/src/groups/Concatenation.js: -------------------------------------------------------------------------------- 1 | import * as common from "./Common"; 2 | import * as layerCommon from "../layers/Common"; 3 | 4 | // Replaces multiple Layers by a more abstract one. 5 | export function concatenateLayers(occurence, network, group) { 6 | var compressedNetwork = { layers: [] }; 7 | if (layerIdsExist(occurence, network)) { 8 | // Check if all layers that should be concatenated still exist. 9 | var newID = common.maxID(network) + 1; // Get a new ID for the concatenation Layer 10 | var newLayer = { 11 | // Initialize the new layer 12 | id: newID, 13 | name: group.name, 14 | properties: { 15 | dimensions: { 16 | in: [100, 100, 100], 17 | out: [100, 100, 100], 18 | }, 19 | input: [], 20 | output: [], 21 | properties: {}, 22 | }, 23 | }; 24 | newLayer.properties.dimensions.in = getNewInputDimensions( 25 | occurence, 26 | network 27 | ); // Change the input Dimensions of the new Layer 28 | newLayer.properties.dimensions.out = getNewOutputDimensions( 29 | occurence, 30 | network 31 | ); // Change the output Dimensions of the new Layer 32 | for (var i in network.layers) { 33 | // Go over all Layers in the Network 34 | if (!checkInOccurence(occurence, network.layers[i].id)) { 35 | // Layer not in the compression List 36 | var layer = JSON.parse(JSON.stringify(network.layers[i])); // Copy layer from the original Network 37 | changeInputIfNeccessary(occurence, layer, newID, newLayer); // Change inputs of the layer if they are now missing 38 | changeOutputIfNeccessary(occurence, layer, newID, newLayer); // Change outputs of the layer if they are now missing 39 | compressedNetwork.layers.push(layer); // Add the layer to the compressed Network 40 | } 41 | } 42 | } else { 43 | return network; 44 | } 45 | compressedNetwork.layers.push(newLayer); // Add the new Layer to the compressed Network 46 | return compressedNetwork; 47 | } 48 | 49 | // Check if all layerIDs in the occurence still exist in the Network. 50 | function layerIdsExist(occurence, network) { 51 | for (var i in occurence) { 52 | // Check all items in the occurence 53 | if (!layerIdExists(occurence[i].matchID, network)) { 54 | // Layer does not exist 55 | return false; 56 | } 57 | } 58 | return true; // All layers exist 59 | } 60 | 61 | // Check if a Layer with a given ID exists. 62 | function layerIdExists(id, network) { 63 | for (var i in network.layers) { 64 | // Iterate over all layers in the Network 65 | if (network.layers[i].id === id) { 66 | // Check if it has the searched ID 67 | return true; 68 | } 69 | } 70 | return false; // No layer with the searched ID 71 | } 72 | 73 | // Check if a layer is in the occurence list 74 | function checkInOccurence(occurence, id) { 75 | for (var i in occurence) { 76 | // Iterate over the list 77 | if (occurence[i].matchID === id) { 78 | // Layer IDs match, in the list 79 | return true; 80 | } 81 | } 82 | return false; // Not in the List 83 | } 84 | 85 | // Change the input of a layer if the input to it is about to be removed. 86 | function changeInputIfNeccessary(occurence, layer, newID, newLayer) { 87 | for (var i in layer.properties.input) { 88 | // Iterate over all inputs of the layer 89 | for (var j in occurence) { 90 | // Iterate over all items in the occurence list 91 | if (layer.properties.input[i] === occurence[j].matchID) { 92 | // Input of the layer in the Occurence List 93 | layer.properties.input[i] = newID; // Set the input to the ID of the newly created abstract Layer 94 | newLayer.properties.output.push(layer.id); // Add the current layer to the outputs of the new Layer 95 | } 96 | } 97 | } 98 | } 99 | 100 | // Change the output of a layer if the output to it is about to be removed. 101 | function changeOutputIfNeccessary(occurence, layer, newID, newLayer) { 102 | for (var i in layer.properties.output) { 103 | // Iterate over all outputs of the layer 104 | for (var j in occurence) { 105 | // Iterate over all items in the occurence list 106 | if (layer.properties.output[i] === occurence[j].matchID) { 107 | // Output of the layer in the Occurence List 108 | layer.properties.output[i] = newID; // Set the input to the ID of the newly created abstract Layer 109 | newLayer.properties.input.push(layer.id); // Add the current layer to the inputs of the new Layer 110 | } 111 | } 112 | } 113 | } 114 | 115 | // Get the input dimensions for the new Layer 116 | function getNewInputDimensions(occurence, network) { 117 | for (var i in occurence) { 118 | // Iterate over the Occurence List 119 | if (occurence[i].properties.input.length === 0) { 120 | // No Inputs for a Layer 121 | var id = layerCommon.getLayerByID(occurence[i].matchID, network.layers); 122 | if (id >= 0) { 123 | // Layer has ID that occurence item matches 124 | return network.layers[id].properties.dimensions.in; // Return the input dimensions for this layer from the network 125 | } 126 | } 127 | } 128 | } 129 | 130 | // Get the output dimensions for the new Layer 131 | function getNewOutputDimensions(occurence, network) { 132 | for (var i in occurence) { 133 | // Iterate over the occurence List 134 | if (occurence[i].properties.output.length === 0) { 135 | // Not Outputs for a Layer 136 | var id = layerCommon.getLayerByID(occurence[i].matchID, network.layers); 137 | if (id >= 0) { 138 | // Layer has ID that occurence item matches 139 | return network.layers[id].properties.dimensions.out; // Return the input dimensions for this layer from the network 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /net2vis/src/groups/Duplicates.js: -------------------------------------------------------------------------------- 1 | // Check if a group already exists 2 | export function groupDoesExist(group, groups) { 3 | for (var i in groups) { 4 | // For all Groups 5 | if (groupsEqualLayers(group, groups[i])) { 6 | // Check if the group is equal to the new group 7 | return true; 8 | } 9 | } 10 | return false; 11 | } 12 | 13 | // Chekch if two groups are equal 14 | function groupsEqualLayers(group1, group2) { 15 | if (group1.layers.length === group2.layers.length) { 16 | // Only possible if the groups have the same number of layers 17 | for (var i in group1.layers) { 18 | // For all layers in the Group 19 | if (group1.layers[i].name === group2.layers[i].name) { 20 | // Layers must have the same name 21 | if ( 22 | group1.layers[i].properties.input.length !== 23 | group2.layers[i].properties.input.length 24 | ) { 25 | // Groups must have the same number of inputs 26 | return false; 27 | } 28 | if ( 29 | group1.layers[i].properties.output.length !== 30 | group2.layers[i].properties.output.length 31 | ) { 32 | // Groups must have the same number of outputs 33 | return false; 34 | } 35 | } else { 36 | return false; 37 | } 38 | } 39 | } else { 40 | return false; 41 | } 42 | return true; 43 | } 44 | -------------------------------------------------------------------------------- /net2vis/src/groups/Grouping.js: -------------------------------------------------------------------------------- 1 | import * as randomstring from "randomstring"; 2 | import * as common from "../layers/Common"; 3 | 4 | // Gouping a selection of layers 5 | export function groupLayers(network, selection) { 6 | if (checkGroupable(network, selection)) { 7 | // If the selection is groupable 8 | return generateGroup(network, selection); // Generate the Group 9 | } else { 10 | return undefined; 11 | } 12 | } 13 | 14 | // Check if the current selection is groupable 15 | function checkGroupable(network, selection) { 16 | if (selection.length < 2) { 17 | // Needs to contain more than one element 18 | return false; 19 | } 20 | var inNodes = 0; 21 | var outNodes = 0; 22 | for (var i in selection) { 23 | // Check groupability of each of the selected layers 24 | var layer = 25 | network.layers[common.getLayerByID(selection[i], network.layers)]; // Get the current layer 26 | var inputs = layer.properties.input; // Get the inputs of the current layer 27 | var outputs = layer.properties.output; // Get the outputs of the current layer 28 | var inContained = contained(inputs, selection); // Check how many of the inputs of the current layer are also selected. 29 | var outContained = contained(outputs, selection); // Check how many of the outputs of the current layer are also selected. 30 | if (inputs.length !== inContained && inContained > 0) { 31 | // Some, but not all inputs selected 32 | return false; // Not groupable 33 | } else if (outputs.length !== outContained && outContained > 0) { 34 | // Some, but not all outputs selected 35 | return false; // Not groupable 36 | } else if (outContained === 0 && inContained === 0) { 37 | // No in- or outputs at all selected 38 | return false; // Not Groupable 39 | } 40 | if (inContained === 0) { 41 | // No Inputs to this Layer 42 | inNodes = inNodes + 1; // Is input node, increase count 43 | } 44 | if (outContained === 0) { 45 | outNodes = outNodes + 1; // Is output node, increase count 46 | } 47 | } 48 | if (inNodes > 1 || outNodes > 1) { 49 | // Multiple input or output nodes 50 | return false; // Not groupable 51 | } 52 | return true; // More than one element selected, and all elements connected. Also no parallel path partly selected. Groupable! 53 | } 54 | 55 | // Check, how many of the given layers are contained in the selection 56 | function contained(layers, selection) { 57 | var contained = 0; 58 | for (var j in layers) { 59 | // Iterate over all layers 60 | if (selection.includes(layers[j])) { 61 | // If the selection includes the layer 62 | contained = contained + 1; // Increment the number of contained elements 63 | } 64 | } 65 | return contained; 66 | } 67 | 68 | // Generate the Group in the Graph 69 | function generateGroup(network, selection) { 70 | var group = { 71 | // Initialize the Group as empty object 72 | name: randomstring.generate(), 73 | active: true, 74 | layers: [], 75 | }; 76 | for (var i in selection) { 77 | // Iterate over all selected Layers 78 | group.layers.push({ 79 | id: network.layers[common.getLayerByID(selection[i], network.layers)].id, 80 | name: 81 | network.layers[common.getLayerByID(selection[i], network.layers)].name, 82 | properties: { 83 | dimensions: { 84 | in: [1, 1, 1], 85 | out: [1, 1, 1], 86 | }, 87 | input: addInputsToLayer(selection, network, i), 88 | output: addOutputsToLayer(selection, network, i), 89 | properties: {}, 90 | }, 91 | }); // Add the Layers to the group 92 | } 93 | return group; 94 | } 95 | 96 | function addInputsToLayer(selection, network, i) { 97 | var layers = []; // Initialize input Layers to be added 98 | var inputs = 99 | network.layers[common.getLayerByID(selection[i], network.layers)].properties 100 | .input; // Get all inputs for the current Layer 101 | for (var j in inputs) { 102 | // Iterate over all inputs 103 | var inputLayer = 104 | network.layers[common.getLayerByID(inputs[j], network.layers)]; // Get the Layer of the current Input 105 | for (var k in selection) { 106 | // Iterate over all selected Items 107 | if (selection[k] === inputLayer.id) { 108 | // Current InputLayer is currently inspected selected Item 109 | layers.push(inputLayer.id); // Add the selection ID to the input layers 110 | } 111 | } 112 | } 113 | return layers; 114 | } 115 | 116 | function addOutputsToLayer(selection, network, i) { 117 | var layers = []; // Initialize output Layers to be added 118 | var outputs = 119 | network.layers[common.getLayerByID(selection[i], network.layers)].properties 120 | .output; // Get all outputs for the current Layer 121 | for (var j in outputs) { 122 | // Iterate over all outputs 123 | var outputLayer = 124 | network.layers[common.getLayerByID(outputs[j], network.layers)]; // Get the Layer of the current Output 125 | for (var k in selection) { 126 | // Iterate over all selected Items 127 | if (selection[k] === outputLayer.id) { 128 | // Current OutputLayer id currently inspected selected Item 129 | layers.push(outputLayer.id); // Add the selection ID to the input layers 130 | } 131 | } 132 | } 133 | return layers; 134 | } 135 | -------------------------------------------------------------------------------- /net2vis/src/groups/Removal.js: -------------------------------------------------------------------------------- 1 | import * as common from "./Common"; 2 | import * as layerCommon from "../layers/Common"; 3 | 4 | // Deletes a selected Group 5 | export function deleteGroup(selectedItem, groups) { 6 | var deletionGroup = getGroupByName(selectedItem, groups); // Get the Group that is to be deleted 7 | groups.splice(deletionGroup.id, 1); // Remove the Group 8 | expandSuperGroups(deletionGroup.group, groups); // Expand Groups that depend on that Group 9 | } 10 | 11 | // Expand Groups that depend on a given group 12 | function expandSuperGroups(group, groups) { 13 | for (var i in groups) { 14 | // For all Groups 15 | for (var j = 0; j < groups[i].layers.length; j++) { 16 | // And all their layers 17 | if (groups[i].layers[j].name === group.name) { 18 | // If the layer is the one to be expanded 19 | expandLayer(groups[i], j, group); // Expand the Layer 20 | j = 0; // Need to go back to be sure not to skip Layers 21 | } 22 | } 23 | } 24 | } 25 | 26 | // Expand a Layer that depends on a Group 27 | function expandLayer(expandGroup, position, expansionGroup) { 28 | var input = common.findInputNode(expansionGroup); // Get the inputNode of the group to be inserted into the other 29 | var output = common.findOutputNode(expansionGroup); // Get the outputNode of the group to be inserted into the other 30 | var maxID = common.maxID(expandGroup) + 1; // Get the maximum ID of the group to always be bigger 31 | for (var i in expansionGroup.layers) { 32 | // Iterate over all layers in the group to be inserted 33 | var newLayer = JSON.parse(JSON.stringify(expansionGroup.layers[i])); // Generate a copy of the current layer 34 | newLayer.id = newLayer.id + maxID; // Set a new and unused ID for the current layer 35 | if (i === input.inputID) { 36 | // If it is the input to the group that expands the other 37 | newLayer.properties.input = expandGroup.layers[position].properties.input; // The input to this layer is the same as the input of the expanded layer 38 | for (var j in expandGroup.layers[position].properties.input) { 39 | // For all of these inputs 40 | var currentPreOutput = 41 | expandGroup.layers[ 42 | layerCommon.getLayerByID( 43 | expandGroup.layers[position].properties.input[j], 44 | expandGroup.layers 45 | ) 46 | ].properties.output; // Get the outputs 47 | for (var k in currentPreOutput) { 48 | // Iterate over the outputs 49 | if (currentPreOutput[k] === expandGroup.layers[position].id) { 50 | // If the output matches the id of the layer to be expanded 51 | currentPreOutput[k] = newLayer.id; // Set the output to be the new ID 52 | } 53 | } 54 | } 55 | for (var l in newLayer.properties.output) { 56 | // For all outputs of this layer 57 | newLayer.properties.output[l] = newLayer.properties.output[l] + maxID; // The current output gets updated to reflect the new IDs of the group that expands the layer 58 | } 59 | } else if (i === output.outputID) { 60 | // If it is the input to the group that expands the other 61 | newLayer.properties.output = 62 | expandGroup.layers[position].properties.output; // the putput to this layer is the same as the output of the expanded Layer 63 | for (var m in expandGroup.layers[position].properties.output) { 64 | // Foe all of these outputs 65 | var currentPreInput = 66 | expandGroup.layers[ 67 | layerCommon.getLayerByID( 68 | expandGroup.layers[position].properties.output[m], 69 | expandGroup.layers 70 | ) 71 | ].properties.input; // Get the Inputs 72 | for (var n in currentPreInput) { 73 | // Iterate over the Inputs 74 | if (currentPreInput[n] === expandGroup.layers[position].id) { 75 | // If the input matches the id of the Layer to be expanded 76 | currentPreInput[n] = newLayer.id; // Set teh input to be the new ID 77 | } 78 | } 79 | } 80 | for (var o in newLayer.properties.input) { 81 | // For all inputs of this Layer 82 | newLayer.properties.input[o] = newLayer.properties.input[o] + maxID; // The current input gets updated to reflect the new IDs of the group that expands the layer 83 | } 84 | } else { 85 | // Neither in nor output of the group that expands the layer 86 | for (var p in newLayer.properties.output) { 87 | // For all outputs 88 | newLayer.properties.output[p] = newLayer.properties.output[p] + maxID; // The current output gets updated to reflect the new IDs of the group that expands the Layer 89 | } 90 | for (var q in newLayer.properties.input) { 91 | // Fro all Inputs 92 | newLayer.properties.input[q] = newLayer.properties.input[q] + maxID; // The current input gets updated to reflect the new IDs of the group that expands the Layer 93 | } 94 | } 95 | expandGroup.layers.push(newLayer); // Add the new layer 96 | } 97 | expandGroup.layers.splice(position, 1); // Remove the expanded Layer 98 | } 99 | 100 | // Get a group by its name 101 | export function getGroupByName(name, groups) { 102 | for (var i in groups) { 103 | // For all group 104 | if (name === groups[i].name) { 105 | // If the names match 106 | return { id: i, group: groups[i] }; // Return the group 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /net2vis/src/groups/Sort.js: -------------------------------------------------------------------------------- 1 | // Sort the Groups based on their dependancies 2 | export function sortGroups(groups, legendItems) { 3 | for (var i = 0; i < groups.length - 1; i++) { 4 | // Go over all groups 5 | if (groupMustGoToEnd(groups, i)) { 6 | // Check if the group should be moved to the end 7 | moveGroupToEnd(groups, i, legendItems); // Move the Group to the end 8 | i = i - 1; // Do not skip a group 9 | } 10 | } 11 | } 12 | 13 | // Check if a Group has to go to the end of the list 14 | function groupMustGoToEnd(groups, i) { 15 | for (var j = i + 1; j < groups.length; j++) { 16 | // For all groups after this group 17 | for (var k in groups[i].layers) { 18 | // For all the layers of the original Group 19 | if (groups[i].layers[k].name === groups[j].name) { 20 | // If the layer is the currently inspected Group 21 | return true; // The Group that was provided needs to be moved 22 | } 23 | } 24 | } 25 | } 26 | 27 | // Move a Group and the Legend Item to the End 28 | function moveGroupToEnd(groups, i, legendItems) { 29 | for (var j in legendItems) { 30 | // For all Legend Items 31 | if (j === groups[i].name) { 32 | // If it is the searched one 33 | var temp = legendItems[j]; // Save it 34 | delete legendItems[j]; // Delete it from the List 35 | legendItems[j] = temp; // Add it to the List 36 | } 37 | } 38 | var moved = groups.splice(i, 1); // Get the Group to be moved out of the Groups list 39 | groups = groups.push(moved[0]); // Add it back in at the end 40 | } 41 | -------------------------------------------------------------------------------- /net2vis/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | import "./styles/index.css"; 8 | 9 | // Render the Application into the html 'root' element 10 | ReactDOM.render(, document.getElementById("root")); 11 | registerServiceWorker(); 12 | -------------------------------------------------------------------------------- /net2vis/src/layers/Common.js: -------------------------------------------------------------------------------- 1 | // Returns the layer index gien an ID 2 | export function getLayerByID(id, layers) { 3 | for (var i in layers) { 4 | // Iterate over all layers 5 | if (layers[i].id === id) { 6 | // Layer ID matches 7 | return i; 8 | } 9 | } 10 | return -1; // No Layer with this ID 11 | } 12 | 13 | // Returning the maximum ID in the current network. 14 | export function maxID(network) { 15 | var id = 0; // Initialize the max ID 16 | for (var i in network.layers) { 17 | // Check all layers 18 | id = network.layers[i].id > id ? network.layers[i].id : id; // Set to bigger of current layer id and id 19 | } 20 | return id; 21 | } 22 | -------------------------------------------------------------------------------- /net2vis/src/layers/Hiding.js: -------------------------------------------------------------------------------- 1 | import * as common from "./Common"; 2 | 3 | export function hideLayers(network, layerTypes) { 4 | var net = network; 5 | for (var key in layerTypes) { 6 | var layerType = layerTypes[key]; 7 | if (layerType.hidden) { 8 | hideLayersByType(key, net); 9 | } 10 | } 11 | return net; 12 | } 13 | 14 | function hideLayersByType(key, network) { 15 | var layer = 0; 16 | while (layer < network.layers.length) { 17 | if (key === network.layers[layer].name) { 18 | var inputs = network.layers[layer].properties.input; 19 | var outputs = network.layers[layer].properties.output; 20 | for (var input in inputs) { 21 | var outOfIn = 22 | network.layers[common.getLayerByID(inputs[input], network.layers)] 23 | .properties.output; 24 | for (var outIn in outOfIn) { 25 | if (outOfIn[outIn] === network.layers[layer].id) { 26 | outOfIn.splice(outIn, 1); 27 | } 28 | } 29 | for (var newOut in outputs) { 30 | outOfIn.push(outputs[newOut]); 31 | } 32 | } 33 | for (var output in outputs) { 34 | var inOfOut = 35 | network.layers[common.getLayerByID(outputs[output], network.layers)] 36 | .properties.input; 37 | for (var inOut in inOfOut) { 38 | if (inOfOut[inOut] === network.layers[layer].id) { 39 | inOfOut.splice(inOut, 1); 40 | } 41 | } 42 | for (var newIn in inputs) { 43 | inOfOut.push(inputs[newIn]); 44 | } 45 | } 46 | network.layers.splice(layer, 1); 47 | } else { 48 | layer = layer + 1; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /net2vis/src/layers/Splitting.js: -------------------------------------------------------------------------------- 1 | import * as common from './Common'; 2 | 3 | export function addSplitLayers(network) { 4 | var net = network; 5 | replaceMultiOutLayers(net); 6 | return net; 7 | } 8 | 9 | function replaceMultiOutLayers(network) { 10 | for (var layer in network.layers) { 11 | var outputs = network.layers[layer].properties.output; 12 | if (outputs.length > 1) { 13 | var newID = common.maxID(network) + 1; 14 | var newLayer = { 15 | id: newID, 16 | name: 'Split', 17 | properties: { 18 | dimensions: { 19 | in: network.layers[layer].properties.dimensions.out, 20 | out: network.layers[layer].properties.dimensions.out, 21 | }, 22 | input: [network.layers[layer].id], 23 | output: outputs, 24 | properties: {}, 25 | }, 26 | }; 27 | network.layers[layer].properties.output = [newID]; 28 | for (var output in outputs) { 29 | var inputs = 30 | network.layers[common.getLayerByID(outputs[output], network.layers)] 31 | .properties.input; 32 | for (var input in inputs) { 33 | if (inputs[input] === network.layers[layer].id) { 34 | inputs[input] = newID; 35 | } 36 | } 37 | } 38 | network.layers.push(newLayer); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /net2vis/src/media/download_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/net2vis/src/media/download_icon.png -------------------------------------------------------------------------------- /net2vis/src/media/group_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/net2vis/src/media/group_icon.png -------------------------------------------------------------------------------- /net2vis/src/reducers/AlertSnackReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function alertSnackReducer( 5 | state = initialState.alert_snack, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.UPDATE_ALERT_SNACK_SUCCESS: 10 | return action.alertSnack; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /net2vis/src/reducers/CodeReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function codeReducer(state = initialState.code, action) { 5 | switch (action.type) { 6 | case types.LOAD_CODE_SUCCESS: 7 | return action.code; 8 | case types.UPDATE_CODE_SUCESS: 9 | return action.code; 10 | default: 11 | return state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /net2vis/src/reducers/ColorModeReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function colorModeReducer( 5 | state = initialState.color_mode, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_SELECTION_COLOR_MODE: 10 | return { selection: action.mode, generation: state.generation }; 11 | case types.SET_GENERATION_COLOR_MODE: 12 | return { selection: state.selection, generation: action.mode }; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /net2vis/src/reducers/CompressionReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | import * as occurences from "../groups/Occurences"; 4 | import * as concatenate from "../groups/Concatenation"; 5 | import * as hiding from "../layers/Hiding"; 6 | 7 | export default function compressionReducer( 8 | state = initialState.compressed_network, 9 | action 10 | ) { 11 | switch (action.type) { 12 | case types.INITIALIZE_COMPRESSED_NETWORK: 13 | var net = JSON.parse(JSON.stringify(action.network)); 14 | net = hiding.hideLayers(net, action.layerTypes); 15 | for (var i in action.groups) { 16 | if (action.groups[i].active) { 17 | var occur = occurences.findGroupOccurences(action.groups[i], net); // Check, where this group can be found 18 | for (var j in occur) { 19 | net = concatenate.concatenateLayers( 20 | occur[j], 21 | net, 22 | action.groups[i] 23 | ); 24 | } 25 | } 26 | } 27 | return net; 28 | default: 29 | return state; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /net2vis/src/reducers/DisplayReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function displayReducer(state = initialState.display, action) { 5 | switch (action.type) { 6 | case types.TOGGLE_CODE: 7 | return { ...state, code_toggle: !state.code_toggle }; 8 | case types.TOGGLE_PREFERENCES: 9 | return { ...state, preferences_toggle: !state.preferences_toggle }; 10 | case types.TOGGLE_LEGEND: 11 | return { ...state, legend_toggle: !state.legend_toggle }; 12 | case types.TOGGLE_ALERT: 13 | return { ...state, alert_toggle: !state.alert_toggle }; 14 | case types.TOGGLE_HELP: 15 | return { ...state, help_toggle: !state.help_toggle }; 16 | case types.TOGGLE_UPLOAD: 17 | return { ...state, upload_toggle: !state.upload_toggle }; 18 | default: 19 | return state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /net2vis/src/reducers/ErrorReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function errorReducer(state = initialState.error, action) { 5 | switch (action.type) { 6 | case types.ADD_ERROR: 7 | return action.data; 8 | case types.REMOVE_ERROR: 9 | return {}; 10 | default: 11 | return state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /net2vis/src/reducers/GroupsReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function groupsReducer(state = initialState.groups, action) { 5 | switch (action.type) { 6 | case types.LOAD_GROUPS_SUCCESS: 7 | return action.groups; 8 | case types.UPDATE_GROUPS: 9 | return action.groups; 10 | default: 11 | return state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /net2vis/src/reducers/IDReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function idReducer(state = initialState.id, action) { 5 | switch (action.type) { 6 | case types.SET_ID: 7 | return action.id; 8 | default: 9 | return state; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /net2vis/src/reducers/LayerExtremeDimensionsReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function layerExtremeDimensionsReducer( 5 | state = initialState.layer_extreme_dimensions, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_LAYERS_EXTREMES: 10 | var max = 0, 11 | min = Infinity; 12 | var max_f = 0, 13 | min_f = Infinity; 14 | var max_d = 0, 15 | min_d = Infinity; 16 | const layers = action.network.layers; 17 | for (var i in layers) { 18 | const dimensions = layers[i].properties.dimensions; 19 | if (dimensions.out.length > 1) { 20 | for (let j = 0; j < dimensions.out.length - 1; j++) { 21 | max = dimensions.out[j] > max ? dimensions.out[j] : max; 22 | min = dimensions.out[j] < min ? dimensions.out[j] : min; 23 | } 24 | max_f = 25 | dimensions.out[dimensions.out.length - 1] > max_f 26 | ? dimensions.out[dimensions.out.length - 1] 27 | : max_f; 28 | min_f = 29 | dimensions.out[dimensions.out.length - 1] < min_f 30 | ? dimensions.out[dimensions.out.length - 1] 31 | : min_f; 32 | } else if (dimensions.in.length === 1) { 33 | const dimensions = layers[i].properties.dimensions; 34 | max_d = dimensions.out[0] > max_d ? dimensions.out[0] : max_d; 35 | min_d = dimensions.out[0] < min_d ? dimensions.out[0] : min_d; 36 | } 37 | if (dimensions.in.length > 1) { 38 | for (let j = 0; j < dimensions.in.length - 1; j++) { 39 | max = dimensions.in[j] > max ? dimensions.in[j] : max; 40 | min = dimensions.in[j] < min ? dimensions.in[j] : min; 41 | } 42 | } 43 | } 44 | return { 45 | max_size: max, 46 | min_size: min, 47 | max_features: max_f, 48 | min_features: min_f, 49 | max_dense: max_d, 50 | min_dense: min_d, 51 | }; 52 | default: 53 | return state; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /net2vis/src/reducers/LayerTypesSettingsReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | import * as colors from "../colors"; 4 | 5 | export default function layerTypesSettignsReducer( 6 | state = initialState.layer_types_settings, 7 | action 8 | ) { 9 | switch (action.type) { 10 | case types.LOAD_LAYER_TYPES_SUCCESS: 11 | if (action.network === undefined) { 12 | return action.layerTypes; 13 | } 14 | var lTypes = action.layerTypes; // Get the layer types 15 | for (const layer of action.network.layers) { 16 | // For all layers 17 | if (lTypes[layer.name] === undefined) { 18 | // If the layer is not yet in layertypes 19 | lTypes[layer.name] = { 20 | // Add the layer type 21 | color: colors.generateNewColor(lTypes, action.generationMode), // Set the color 22 | alias: layer.name, // Set the name 23 | texture: colors.generateNewTexture(lTypes), // Set the fallback texture 24 | hidden: false, 25 | dense: layer.properties.dimensions.out.length === 1, 26 | }; 27 | } 28 | } 29 | return lTypes; 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /net2vis/src/reducers/LegendBboxReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function legendBboxReducer( 5 | state = initialState.legend_bbox, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_LEGEND_BBOX: 10 | return action.bbox; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /net2vis/src/reducers/LegendPreferencesReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function legendPreferencesReducer( 5 | state = initialState.legend_preferences, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.UPDATE_LEGEND_PREFERENCES_SUCCESS: 10 | return action.legend_preferences; 11 | case types.LOAD_LEGEND_PREFERENCES_SUCCESS: 12 | return action.legend_preferences; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /net2vis/src/reducers/LegendTransformReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function legendTransformReducer( 5 | state = initialState.legend_transform, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.MOVE_LEGEND: 10 | var x = state.x - action.group_displacement[0]; 11 | var y = state.y - action.group_displacement[1]; 12 | return { x: x, y: y, scale: state.scale }; 13 | case types.ZOOM_LEGEND: // Zoming the Legend Graph 14 | var factor = 1.2; 15 | if (action.legend_zoom > 0) { 16 | // Zooming out 17 | factor = 1.0 / (action.legend_zoom / factor); // Ensure, that the factor is of appropriate Size 18 | } else if (action.legend_zoom < 0) { 19 | // Zooming in 20 | factor = -action.legend_zoom / 1.1 / factor; // Ensure, that the factor is of appropriate Size 21 | } 22 | var scale = state.scale * factor; // Multiply the previous scale with the current factor 23 | return { x: state.x, y: state.y, scale: scale }; 24 | default: 25 | return state; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /net2vis/src/reducers/NetworkBboxReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function networkBboxReducer( 5 | state = initialState.network_bbox, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_NETWORK_BBOX: 10 | return action.bbox; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /net2vis/src/reducers/NetworkReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function networkReducer(state = initialState.network, action) { 5 | switch (action.type) { 6 | case types.LOAD_NETWORK_SUCCESS: 7 | return action.network; 8 | default: 9 | return state; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /net2vis/src/reducers/PreferencesModeReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function preferencesModeReducer( 5 | state = initialState.preferences_mode, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_PREFERENCE_MODE: 10 | return action.name; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /net2vis/src/reducers/PreferencesReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function preferencesReducer( 5 | state = initialState.preferences, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.UPDATE_PREFERENCES_SUCCESS: 10 | return action.preferences; 11 | case types.LOAD_PREFERENCES_SUCCESS: 12 | let prefs = action.preferences; 13 | if (prefs.show_name === undefined) { 14 | prefs.show_name = { 15 | value: false, 16 | type: "switch", 17 | description: "Name Label", 18 | }; 19 | } 20 | if (prefs.channels_first === undefined) { 21 | prefs.channels_first = { 22 | value: false, 23 | type: "switch", 24 | description: "Channels First", 25 | }; 26 | } 27 | return prefs; 28 | default: 29 | return state; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /net2vis/src/reducers/SelectedLegendItemReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function selectedLegendItemReducer( 5 | state = initialState.selected_legend_item, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SET_SELECTED_LEGEND_ITEM: 10 | return action.name; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /net2vis/src/reducers/SelectionReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function selectionReducer( 5 | state = initialState.selection, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.SELECT_LAYER: // One new Layer To Select 10 | return state.concat(action.id); 11 | case types.SELECT_LAYERS: // Multiple New Layers to select 12 | var toSelect = []; 13 | for (var i in action.layers) { 14 | toSelect.push(action.layers[i].id); 15 | } 16 | return toSelect; 17 | case types.DESELECT_LAYER: // One Layer to deselect 18 | return state.filter((item) => item !== action.id); 19 | case types.DESELECT_LAYERS: // Deselect all Layers 20 | return []; 21 | default: 22 | return state; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /net2vis/src/reducers/TransformReducer.js: -------------------------------------------------------------------------------- 1 | import initialState from "./initialState"; 2 | import * as types from "../actions/types"; 3 | 4 | export default function transformReducer( 5 | state = initialState.group_transform, 6 | action 7 | ) { 8 | switch (action.type) { 9 | case types.MOVE_GROUP: 10 | var x = state.x - action.group_displacement[0]; 11 | var y = state.y - action.group_displacement[1]; 12 | return { x: x, y: y, scale: state.scale }; 13 | case types.ZOOM_GROUP: // Zoming the Network Graph 14 | let factor = 1.2; 15 | if (action.group_zoom > 0) { 16 | // Zooming out 17 | factor = 1.0 / (action.group_zoom / factor); // Ensure, that the factor is of appropriate Size 18 | } else if (action.group_zoom < 0) { 19 | // Zooming in 20 | factor = -action.group_zoom / 1.1 / factor; // Ensure, that the factor is of appropriate Size 21 | } 22 | var scale = state.scale * factor; // Multiply the previous scale with the current factor 23 | return { x: state.x, y: state.y, scale: scale }; 24 | default: 25 | return state; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /net2vis/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | // Import all Reducers 2 | import { combineReducers } from "redux"; 3 | import id from "./IDReducer"; 4 | import network from "./NetworkReducer"; 5 | import code from "./CodeReducer"; 6 | import group_transform from "./TransformReducer"; 7 | import legend_transform from "./LegendTransformReducer"; 8 | import display from "./DisplayReducer"; 9 | import layer_types_settings from "./LayerTypesSettingsReducer"; 10 | import preferences from "./PreferencesReducer"; 11 | import error from "./ErrorReducer"; 12 | import layer_extreme_dimensions from "./LayerExtremeDimensionsReducer"; 13 | import selection from "./SelectionReducer"; 14 | import preferences_mode from "./PreferencesModeReducer"; 15 | import selected_legend_item from "./SelectedLegendItemReducer"; 16 | import groups from "./GroupsReducer"; 17 | import compressed_network from "./CompressionReducer"; 18 | import legend_preferences from "./LegendPreferencesReducer"; 19 | import color_mode from "./ColorModeReducer"; 20 | import network_bbox from "./NetworkBboxReducer"; 21 | import legend_bbox from "./LegendBboxReducer"; 22 | import alert_snack from "./AlertSnackReducer"; 23 | 24 | // Combine all Reducers 25 | export default combineReducers({ 26 | id, 27 | network, 28 | code, 29 | group_transform, 30 | legend_transform, 31 | display, 32 | layer_types_settings, 33 | preferences, 34 | error, 35 | layer_extreme_dimensions, 36 | selection, 37 | preferences_mode, 38 | selected_legend_item, 39 | groups, 40 | compressed_network, 41 | legend_preferences, 42 | color_mode, 43 | network_bbox, 44 | legend_bbox, 45 | alert_snack, 46 | }); 47 | -------------------------------------------------------------------------------- /net2vis/src/reducers/initialState.js: -------------------------------------------------------------------------------- 1 | // Set the initial State of the Application 2 | export default { 3 | id: "", 4 | network: {}, 5 | code: "", 6 | group_transform: { 7 | x: 0, 8 | y: 0, 9 | scale: 1, 10 | }, 11 | legend_transform: { 12 | x: 0, 13 | y: 0, 14 | scale: 1, 15 | }, 16 | display: { 17 | code_toggle: true, 18 | preferences_toggle: true, 19 | legend_toggle: true, 20 | alert_toggle: false, 21 | help_toggle: false, 22 | upload_toggle: false, 23 | }, 24 | layer_types_settings: {}, 25 | preferences: { 26 | layer_display_min_height: { 27 | value: 30, 28 | type: "number", 29 | description: "Minimum Layer Height", 30 | }, 31 | layer_display_max_height: { 32 | value: 150, 33 | type: "number", 34 | description: "Maximum Layer Height", 35 | }, 36 | layer_display_min_width: { 37 | value: 20, 38 | type: "number", 39 | description: "Minimum width of Layers", 40 | }, 41 | layer_display_max_width: { 42 | value: 80, 43 | type: "number", 44 | description: "Maximum width of Layers", 45 | }, 46 | layers_spacing_horizontal: { 47 | value: 0, 48 | type: "number", 49 | description: "Horizontal spacing between Layers", 50 | }, 51 | layers_spacing_vertical: { 52 | value: 0, 53 | type: "number", 54 | description: "Vertical spacing between Layers", 55 | }, 56 | features_mapping: { 57 | value: "none", 58 | type: "choice", 59 | description: "Visual mapping of the Features", 60 | }, 61 | show_dimensions: { 62 | value: true, 63 | type: "switch", 64 | description: "Dimensions Label", 65 | }, 66 | show_features: { 67 | value: true, 68 | type: "switch", 69 | description: "Features Label", 70 | }, 71 | show_name: { 72 | value: false, 73 | type: "switch", 74 | description: "Name Label", 75 | }, 76 | add_splitting: { 77 | value: false, 78 | type: "switch", 79 | description: "Replace Split Layers", 80 | }, 81 | show_samples: { 82 | value: true, 83 | type: "switch", 84 | description: "Input/Output Samples", 85 | }, 86 | channels_first: { 87 | value: false, 88 | type: "switch", 89 | description: "Channels First", 90 | }, 91 | no_colors: { value: false, type: "switch", description: "Disable Colors" }, 92 | stroke_width: { value: 4, type: "number", description: "Stroke Width" }, 93 | }, 94 | layer_extreme_dimensions: { 95 | max_size: 2, 96 | min_size: 1, 97 | max_features: 2, 98 | min_features: 1, 99 | max_dense: 2, 100 | min_dense: 1, 101 | }, 102 | error: {}, 103 | selection: [], 104 | preferences_mode: "network", 105 | selected_legend_item: "", 106 | groups: [], 107 | compressed_network: {}, 108 | legend_preferences: { 109 | element_spacing: { 110 | value: 70, 111 | type: "number", 112 | description: "Spacing between Elements", 113 | }, 114 | layer_height: { value: 30, type: "number", description: "Layer Height" }, 115 | layer_width: { value: 10, type: "number", description: "Layer Width" }, 116 | layers_spacing_horizontal: { 117 | value: 5, 118 | type: "number", 119 | description: "Horizontal spacing between Layers", 120 | }, 121 | layers_spacing_vertical: { 122 | value: 10, 123 | type: "number", 124 | description: "Vertical spacing between Layers", 125 | }, 126 | complex_spacing: { 127 | value: 15, 128 | type: "number", 129 | description: "Spacing before complex Layer", 130 | }, 131 | stroke_width: { value: 2, type: "number", description: "Stroke Width" }, 132 | reverse_order: { 133 | value: false, 134 | type: "switch", 135 | description: "Reverse Legend Order", 136 | }, 137 | }, 138 | color_mode: { 139 | selection: "Palette", 140 | generation: "Palette", 141 | }, 142 | network_bbox: {}, 143 | legend_bbox: {}, 144 | alert_snack: { 145 | open: false, 146 | message: "", 147 | }, 148 | }; 149 | -------------------------------------------------------------------------------- /net2vis/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === "localhost" || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === "[::1]" || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener("load", () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | "This web app is being served cache-first by a service " + 44 | "worker. To learn more, visit https://goo.gl/SC7cgQ" 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then((registration) => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === "installed") { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log("New content is available; please refresh."); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log("Content is cached for offline use."); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch((error) => { 80 | console.error("Error during service worker registration:", error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then((response) => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get("content-type").indexOf("javascript") === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then((registration) => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | "No internet connection found. App is running in offline mode." 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ("serviceWorker" in navigator) { 113 | navigator.serviceWorker.ready.then((registration) => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /net2vis/src/selection/index.js: -------------------------------------------------------------------------------- 1 | import * as common from "../layers/Common"; 2 | 3 | // Get all paths from a node s to node d in the network 4 | export function allPaths(s, d, network) { 5 | var paths = []; // Initially, no Paths found 6 | traverseWay(s, d, network, [], paths); // Traverse the way from s on 7 | return paths; 8 | } 9 | 10 | // Traverse the Network from node u on 11 | function traverseWay(u, d, network, path, paths) { 12 | path.push(u); 13 | if (u.id === d.id) { 14 | // If u is the destination 15 | paths.push(path); // Add the path to the pathes 16 | } else if (u.properties.output.length === 0) { 17 | // If an output node has been reached 18 | paths.push(path); // Add the uncomplete path to the pathes 19 | } else { 20 | // Way goes on 21 | for (var i = 0; i < u.properties.output.length; i++) { 22 | // For all Outputs of the current node 23 | if (i === 0) { 24 | // For the first of all outputs 25 | var nextLayer = 26 | network.layers[ 27 | common.getLayerByID(u.properties.output[i], network.layers) 28 | ]; // Get the next Layer 29 | traverseWay(nextLayer, d, network, path, paths); // Go further along the way 30 | } else { 31 | // Not the first output of the current node 32 | var newPath = JSON.parse(JSON.stringify(path)); // Copy the path, since a new path has been found 33 | var nextWay = 34 | network.layers[ 35 | common.getLayerByID(u.properties.output[i], network.layers) 36 | ]; // Get the next Layer along this way 37 | traverseWay(nextWay, d, network, newPath, paths); // Go along this alternate way 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Reduce the paths to just every layer they contain once 44 | export function reducePaths(paths) { 45 | var items = []; // No Layers initially 46 | for (var i in paths) { 47 | // For all Paths 48 | for (var j in paths[i]) { 49 | // For all their Layers 50 | var present = false; // Placeholder for if the Layer is already in the Layers variable 51 | for (var k in items) { 52 | // For all Layers in the Layers Variable 53 | if (items[k].id === paths[i][j].id) { 54 | // If the currently inspected Layer is the current one in the Layers variable 55 | present = true; // Layer already present 56 | } 57 | } 58 | if (!present) { 59 | // If Layer not present 60 | items.push(paths[i][j]); // Add it to the Layers 61 | } 62 | } 63 | } 64 | return items; 65 | } 66 | 67 | // Check multi inputs for if all their inputs are contained in the paths 68 | export function checkMultiInput(paths) { 69 | for (var i in paths) { 70 | // For all Paths 71 | for (var j in paths[i]) { 72 | // For all Layers in these Paths 73 | if (paths[i][j].properties.input.length > 1) { 74 | // If it has more than one Input 75 | for (var k in paths[i][j].properties.input) { 76 | // For all these Inputs 77 | var inPaths = false; // Placeholder for if the input is contained in the paths 78 | for (var l in paths) { 79 | // For all paths 80 | for (var m in paths[l]) { 81 | // For all Layers in these Paths 82 | if (paths[l][m].id === paths[i][j].properties.input[k]) { 83 | // Check if the current Layer ID matches the inspected Input 84 | inPaths = true; // Set the Placeholder to true 85 | } 86 | } 87 | } 88 | if (!inPaths) { 89 | // If the input is not in the paths 90 | return false; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | return true; 97 | } 98 | -------------------------------------------------------------------------------- /net2vis/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require("http-proxy-middleware"); 2 | 3 | module.exports = function (app) { 4 | app.use(createProxyMiddleware("/api", { target: "http://localhost:5000" })); 5 | }; 6 | -------------------------------------------------------------------------------- /net2vis/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // General Properties 2 | html, 3 | body { 4 | height: 100%; 5 | overflow: hidden; 6 | font-family: Roboto, sans-serif; 7 | } 8 | 9 | // Header only Properties (For Menu Bar) 10 | header { 11 | position: fixed; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | color: white; 16 | background-color: #a32638; 17 | 18 | .wrapper { 19 | display: flex; 20 | justify-content: space-between; 21 | width: 100%; 22 | margin-left: 50px; 23 | } 24 | 25 | .menu { 26 | display: flex; 27 | flex-direction: row; 28 | } 29 | 30 | .menuitem { 31 | display: flex; 32 | flex-direction: row; 33 | height: calc(100% - 2px); 34 | border-bottom: 2px solid rgba(0, 0, 0, 0); 35 | padding-left: 12px; 36 | padding-right: 12px; 37 | } 38 | 39 | .menuselect { 40 | padding-left: 24px; 41 | padding-right: 24px; 42 | display: flex; 43 | align-items: center; 44 | } 45 | 46 | .menuitem:hover { 47 | background-color: #1b80de; 48 | text-decoration: none; 49 | cursor: pointer; 50 | } 51 | 52 | div { 53 | display: block; 54 | color: white; 55 | text-align: center; 56 | line-height: 62px; 57 | } 58 | 59 | div a:hover { 60 | background-color: rgb(141, 47, 61); 61 | text-decoration: none; 62 | cursor: pointer; 63 | } 64 | 65 | .selected { 66 | border-bottom: 2px solid #ffffff; 67 | } 68 | 69 | img { 70 | display: block; 71 | margin: auto; 72 | height: 30px; 73 | } 74 | } 75 | 76 | .preferencesWrapper { 77 | display: flex; 78 | height: 100%; 79 | flex-direction: column; 80 | justify-content: space-between; 81 | } 82 | 83 | .innerPreferencesWrapper { 84 | display: flex; 85 | flex-direction: column; 86 | overflow: auto; 87 | } 88 | 89 | // Some Content Helpers 90 | .content { 91 | padding: 70px 0px 0px 0px; 92 | height: 100%; 93 | background-color: #f5f5f5; 94 | overflow: hidden; 95 | } 96 | 97 | .full { 98 | width: 100%; 99 | height: 100%; 100 | } 101 | 102 | .codePaper { 103 | width: 100%; 104 | height: calc(100% - 8px); 105 | } 106 | 107 | #codeDiv { 108 | height: calc(100% - 48px); 109 | } 110 | 111 | .codeItem { 112 | padding-bottom: 8px; 113 | padding-right: 10px; 114 | padding-left: 10px; 115 | flex: 1 1 0%; 116 | } 117 | 118 | // Different Areas 119 | #ColorPicker { 120 | padding: 10px; 121 | } 122 | 123 | // Grids for the Areas of the Application 124 | .mainGrid { 125 | height: 100%; 126 | } 127 | 128 | .codeGrid { 129 | height: 100%; 130 | width: 400px; 131 | } 132 | 133 | .svgGrid { 134 | height: calc(100% - 300px); 135 | width: 100%; 136 | } 137 | 138 | .preferencesGrid { 139 | height: 100%; 140 | width: 300px; 141 | } 142 | 143 | .legendGrid { 144 | height: 300px; 145 | width: 100%; 146 | } 147 | 148 | // Classes for Styling of individual Elements 149 | .noselect { 150 | -webkit-touch-callout: none; /* iOS Safari */ 151 | -webkit-user-select: none; /* Safari */ 152 | -khtml-user-select: none; /* Konqueror HTML */ 153 | -moz-user-select: none; /* Firefox */ 154 | -ms-user-select: none; /* Internet Explorer/Edge */ 155 | user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ 156 | } 157 | 158 | .preferenceItem { 159 | padding-right: 10px; 160 | } 161 | 162 | .inputElement { 163 | width: calc(100%); 164 | } 165 | 166 | .formselect { 167 | margin-top: 16px; 168 | margin-bottom: 8px; 169 | } 170 | 171 | .paddedButton { 172 | padding-bottom: 8px; 173 | padding-right: 10px; 174 | } 175 | 176 | .legendItem { 177 | width: 200px; 178 | height: 50px; 179 | margin: 10px; 180 | line-height: 50px; 181 | text-align: center; 182 | color: black; 183 | } 184 | 185 | .reflex-size-aware { 186 | overflow: hidden; 187 | } 188 | 189 | .ace_scrollbar { 190 | display: none !important; 191 | } 192 | 193 | .ace_editor { 194 | background: rgba(0, 0, 0, 0) !important; 195 | } 196 | 197 | .ace_gutter { 198 | background: #f5f5f5 !important; 199 | z-index: auto !important; 200 | } 201 | 202 | svg text { 203 | cursor: default; 204 | -webkit-user-select: none; 205 | -moz-user-select: none; 206 | -ms-user-select: none; 207 | user-select: none; 208 | } 209 | 210 | #loader { 211 | width: 100%; 212 | height: 100%; 213 | top: 0; 214 | left: 0; 215 | display: none; 216 | position: fixed; 217 | background-color: rgba($color: black, $alpha: 0.3); 218 | justify-content: center; 219 | align-items: center; 220 | } 221 | 222 | .spinner { 223 | border: 16px solid #f3f3f3; /* Light grey */ 224 | border-top: 16px solid #2196f3; /* Blue */ 225 | border-radius: 50%; 226 | width: 120px; 227 | height: 120px; 228 | animation: spin 2s linear infinite; 229 | } 230 | 231 | @keyframes spin { 232 | 0% { 233 | transform: rotate(0deg); 234 | } 235 | 100% { 236 | transform: rotate(360deg); 237 | } 238 | } 239 | 240 | .updateButtonContainer { 241 | display: flex; 242 | } 243 | 244 | .dropzoneContainer { 245 | border: #f3f3f3; 246 | border-style: dashed; 247 | padding: 5px; 248 | border-radius: 4px; 249 | } 250 | -------------------------------------------------------------------------------- /net2vis_teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/net2vis_teaser.png -------------------------------------------------------------------------------- /net2vis_teaser_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viscom-ulm/Net2Vis/a37935eda001ff89a7ef1d355d2ce76d3fb5fa02/net2vis_teaser_legend.png --------------------------------------------------------------------------------