cyyeh commited on
Commit
1480ae6
·
1 Parent(s): 859d328
.pre-commit-config.yaml CHANGED
@@ -21,7 +21,7 @@ repos:
21
  language: system
22
  entry: pipenv run flake8 py_code_analyzer
23
  types: [python]
24
- exclude: setup.py
25
 
26
  - id: mypy
27
  name: mypy
 
21
  language: system
22
  entry: pipenv run flake8 py_code_analyzer
23
  types: [python]
24
+ exclude: setup.py|pyvis
25
 
26
  - id: mypy
27
  name: mypy
pyvis DELETED
@@ -1 +0,0 @@
1
- Subproject commit e8249fb97d5797eaccce50d5c87bd45b76d9b5df
 
 
pyvis/.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # binaries
2
+ *.pyc
3
+ .cache/
4
+ .coverage
5
+
6
+ # notebook checkpoints
7
+ .ipynb_checkpoints/
8
+ notebooks/.ipynb_checkpoints
9
+
10
+ # test related
11
+ .pytest_cache/
12
+ pyvis/test.py
13
+
14
+ # local files
15
+ pyvis/full_graph.txt
16
+ pyvis/source/stormofswords.csv
17
+
18
+ # package details
19
+ pyvis.egg-info/
20
+ build/
21
+ dist/
22
+ pyvis/make.bat
23
+
24
+ # vscode specific
25
+ .vscode/
26
+ venv
pyvis/CONTRIBUTING.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pull Requests
2
+
3
+ Here are some guidelines for pull requests:
4
+
5
+ * All work is submitted via Pull Requests.
6
+ * Pull Requests can be submitted as soon as there is code worth discussing. The worst case is that the PR is closed.
7
+ * Pull Requests should be made against master
8
+ * Pull Requests should be tested, if feasible:
9
+ - bugfixes should include regression tests.
10
+ - new behavior should at least get minimal exercise.
11
+ - new features should include a screenshot
12
+ * Don't make 'cleanup' pull requests just to change code style. We don't follow any style guide strictly, and we consider formatting changes unnecessary noise. If you're making functional changes, you can clean up the specific pieces of code you're working on.
13
+
14
+ Pyvis wraps vis.js so JavaScript functionality issues should be directed at the main `vis.js` project.
15
+
16
+ # Other contributions
17
+
18
+ Outside of Pull Requests (PRs), we welcome additions/corrections/clarification to the existing documentation as contributions at least as valuable as code submissions.
pyvis/MANIFEST.in ADDED
@@ -0,0 +1 @@
 
 
1
+ recursive-include pyvis/templates *.html
pyvis/README.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Pyvis - a Python library for visualizing networks
2
+
3
+ ![](pyvis/source/tut.gif?raw=true)
4
+
5
+ ## Description
6
+ Pyvis is built around [visjs](http://visjs.org/), a JavaScript visualization library.
7
+
8
+ ## Documentation
9
+ Pyvis' full documentation can be found at http://pyvis.readthedocs.io/en/latest/
10
+ ## Installation
11
+ You can install pyvis through pip:
12
+
13
+ ```bash
14
+ pip install pyvis
15
+ ```
16
+ Or if you have an archive of the project simply run the following from the top level directory:
17
+
18
+ ```bash
19
+ python setup.py install
20
+ ```
21
+
22
+ ## Dependencies
23
+ [networkx](https://networkx.github.io/)
24
+
25
+ [jinja2](http://jinja.pocoo.org/)
26
+
27
+ [ipython](https://ipython.org/ipython-doc/2/install/install.html)
28
+
29
+ [jsonpickle](https://jsonpickle.github.io/)
30
+
31
+ ## Quick Start
32
+ The most basic use case of a pyvis instance is to create a Network object and invoke methods:
33
+
34
+ ```python
35
+ from pyvis.network import Network
36
+
37
+ g = Network()
38
+ g.add_node(0)
39
+ g.add_node(1)
40
+ g.add_edge(0, 1)
41
+ g.show("basic.html")
42
+ ```
43
+
44
+ ## Interactive Notebook playground with examples
45
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/WestHealth/pyvis/master?filepath=notebooks%2Fexample.ipynb)
pyvis/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .pyvis.network import Network
pyvis/notebooks/example.ipynb ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import sys\n",
10
+ "sys.path.append('../')\n",
11
+ "from pyvis.network import Network"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "markdown",
16
+ "metadata": {},
17
+ "source": [
18
+ "### Basic Example"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "code",
23
+ "execution_count": 2,
24
+ "metadata": {},
25
+ "outputs": [
26
+ {
27
+ "data": {
28
+ "text/html": [
29
+ "\n",
30
+ " <iframe\n",
31
+ " width=\"500px\"\n",
32
+ " height=\"500px\"\n",
33
+ " src=\"example.html\"\n",
34
+ " frameborder=\"0\"\n",
35
+ " allowfullscreen\n",
36
+ " ></iframe>\n",
37
+ " "
38
+ ],
39
+ "text/plain": [
40
+ "<IPython.lib.display.IFrame at 0x7fbe1d273d30>"
41
+ ]
42
+ },
43
+ "execution_count": 2,
44
+ "metadata": {},
45
+ "output_type": "execute_result"
46
+ }
47
+ ],
48
+ "source": [
49
+ "g = Network(notebook=True)\n",
50
+ "g.add_nodes(range(5))\n",
51
+ "g.add_edges([\n",
52
+ " (0, 2),\n",
53
+ " (0, 3),\n",
54
+ " (0, 4),\n",
55
+ " (1, 1),\n",
56
+ " (1, 3),\n",
57
+ " (1, 2)\n",
58
+ "])\n",
59
+ "g.show(\"example.html\")"
60
+ ]
61
+ },
62
+ {
63
+ "cell_type": "markdown",
64
+ "metadata": {},
65
+ "source": [
66
+ "### Dot File example"
67
+ ]
68
+ },
69
+ {
70
+ "cell_type": "code",
71
+ "execution_count": 9,
72
+ "metadata": {
73
+ "scrolled": true
74
+ },
75
+ "outputs": [
76
+ {
77
+ "data": {
78
+ "text/html": [
79
+ "\n",
80
+ " <iframe\n",
81
+ " width=\"500px\"\n",
82
+ " height=\"500px\"\n",
83
+ " src=\"dot.html\"\n",
84
+ " frameborder=\"0\"\n",
85
+ " allowfullscreen\n",
86
+ " ></iframe>\n",
87
+ " "
88
+ ],
89
+ "text/plain": [
90
+ "<IPython.lib.display.IFrame at 0x7fbe3d2ead00>"
91
+ ]
92
+ },
93
+ "execution_count": 9,
94
+ "metadata": {},
95
+ "output_type": "execute_result"
96
+ }
97
+ ],
98
+ "source": [
99
+ "h = Network(notebook=True)\n",
100
+ "h.from_DOT(\"test.dot\")\n",
101
+ "\n",
102
+ "# All properties have to be enclosed by double quotes and \n",
103
+ "# there and there must be no comma at the end of a list.\n",
104
+ "# See https://visjs.github.io/vis-network/docs/network/ for all options\n",
105
+ "h.set_options(\"\"\"\n",
106
+ "var options = {\n",
107
+ " \"physics\": {\n",
108
+ " \"enabled\": true,\n",
109
+ " \"barnesHut\": {\n",
110
+ " \"theta\": 0.5,\n",
111
+ " \"gravitationalConstant\": -2000,\n",
112
+ " \"centralGravity\": 0.3,\n",
113
+ " \"springLength\": 200,\n",
114
+ " \"springConstant\": 0.04,\n",
115
+ " \"damping\": 0.09,\n",
116
+ " \"avoidOverlap\": 0\n",
117
+ " },\n",
118
+ " \"maxVelocity\": 50,\n",
119
+ " \"minVelocity\": 0.1,\n",
120
+ " \"solver\": \"barnesHut\",\n",
121
+ " \"stabilization\": {\n",
122
+ " \"enabled\": true,\n",
123
+ " \"iterations\": 1000,\n",
124
+ " \"updateInterval\": 100,\n",
125
+ " \"onlyDynamicEdges\": false,\n",
126
+ " \"fit\": true\n",
127
+ " }\n",
128
+ " }\n",
129
+ "}\n",
130
+ "\"\"\")\n",
131
+ "h.show(\"dot.html\")"
132
+ ]
133
+ },
134
+ {
135
+ "cell_type": "markdown",
136
+ "metadata": {},
137
+ "source": [
138
+ "### Basic NetworkX example"
139
+ ]
140
+ },
141
+ {
142
+ "cell_type": "code",
143
+ "execution_count": 10,
144
+ "metadata": {},
145
+ "outputs": [],
146
+ "source": [
147
+ "import networkx as nx"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": 11,
153
+ "metadata": {
154
+ "scrolled": false
155
+ },
156
+ "outputs": [
157
+ {
158
+ "data": {
159
+ "text/html": [
160
+ "\n",
161
+ " <iframe\n",
162
+ " width=\"500px\"\n",
163
+ " height=\"500px\"\n",
164
+ " src=\"example.html\"\n",
165
+ " frameborder=\"0\"\n",
166
+ " allowfullscreen\n",
167
+ " ></iframe>\n",
168
+ " "
169
+ ],
170
+ "text/plain": [
171
+ "<IPython.lib.display.IFrame at 0x7f3ef910af40>"
172
+ ]
173
+ },
174
+ "execution_count": 11,
175
+ "metadata": {},
176
+ "output_type": "execute_result"
177
+ }
178
+ ],
179
+ "source": [
180
+ "nxg = nx.random_tree(20)\n",
181
+ "g.from_nx(nxg)\n",
182
+ "g.show(\"example.html\")"
183
+ ]
184
+ },
185
+ {
186
+ "cell_type": "markdown",
187
+ "metadata": {},
188
+ "source": [
189
+ "### Disabling Physics interaction"
190
+ ]
191
+ },
192
+ {
193
+ "cell_type": "code",
194
+ "execution_count": 6,
195
+ "metadata": {},
196
+ "outputs": [
197
+ {
198
+ "data": {
199
+ "text/html": [
200
+ "\n",
201
+ " <iframe\n",
202
+ " width=\"500px\"\n",
203
+ " height=\"500px\"\n",
204
+ " src=\"example.html\"\n",
205
+ " frameborder=\"0\"\n",
206
+ " allowfullscreen\n",
207
+ " \n",
208
+ " ></iframe>\n",
209
+ " "
210
+ ],
211
+ "text/plain": [
212
+ "<IPython.lib.display.IFrame at 0x7f882040c6d0>"
213
+ ]
214
+ },
215
+ "execution_count": 6,
216
+ "metadata": {},
217
+ "output_type": "execute_result"
218
+ }
219
+ ],
220
+ "source": [
221
+ "g.toggle_physics(False)\n",
222
+ "g.show(\"example.html\")"
223
+ ]
224
+ },
225
+ {
226
+ "cell_type": "markdown",
227
+ "metadata": {},
228
+ "source": [
229
+ "### Explicit coordinates to layout nodes"
230
+ ]
231
+ },
232
+ {
233
+ "cell_type": "code",
234
+ "execution_count": 7,
235
+ "metadata": {},
236
+ "outputs": [
237
+ {
238
+ "data": {
239
+ "text/html": [
240
+ "\n",
241
+ " <iframe\n",
242
+ " width=\"500px\"\n",
243
+ " height=\"500px\"\n",
244
+ " src=\"example.html\"\n",
245
+ " frameborder=\"0\"\n",
246
+ " allowfullscreen\n",
247
+ " \n",
248
+ " ></iframe>\n",
249
+ " "
250
+ ],
251
+ "text/plain": [
252
+ "<IPython.lib.display.IFrame at 0x7f87dc679b50>"
253
+ ]
254
+ },
255
+ "execution_count": 7,
256
+ "metadata": {},
257
+ "output_type": "execute_result"
258
+ }
259
+ ],
260
+ "source": [
261
+ "g = Network(notebook=True)\n",
262
+ "g.add_nodes([1,2,3],\n",
263
+ " value=[10, 100, 400],\n",
264
+ " title=[\"I am node 1\", \"node 2 here\", \"and im node 3\"],\n",
265
+ " x=[21.4, 21.4, 21.4], y=[100.2, 223.54, 32.1],\n",
266
+ " label=[\"NODE 1\", \"NODE 2\", \"NODE 3\"],\n",
267
+ " color=[\"#00ff1e\", \"#162347\", \"#dd4b39\"])\n",
268
+ "g.show(\"example.html\")"
269
+ ]
270
+ },
271
+ {
272
+ "cell_type": "markdown",
273
+ "metadata": {},
274
+ "source": [
275
+ "### Full Game of Thrones example"
276
+ ]
277
+ },
278
+ {
279
+ "cell_type": "code",
280
+ "execution_count": 8,
281
+ "metadata": {},
282
+ "outputs": [
283
+ {
284
+ "data": {
285
+ "text/html": [
286
+ "\n",
287
+ " <iframe\n",
288
+ " width=\"100%\"\n",
289
+ " height=\"750px\"\n",
290
+ " src=\"example.html\"\n",
291
+ " frameborder=\"0\"\n",
292
+ " allowfullscreen\n",
293
+ " \n",
294
+ " ></iframe>\n",
295
+ " "
296
+ ],
297
+ "text/plain": [
298
+ "<IPython.lib.display.IFrame at 0x7f87da76e430>"
299
+ ]
300
+ },
301
+ "execution_count": 8,
302
+ "metadata": {},
303
+ "output_type": "execute_result"
304
+ }
305
+ ],
306
+ "source": [
307
+ "import pandas as pd\n",
308
+ "\n",
309
+ "got_net = Network(notebook=True, height=\"750px\", width=\"100%\", bgcolor=\"#222222\", font_color=\"white\")\n",
310
+ "\n",
311
+ "# set the physics layout of the network\n",
312
+ "got_net.barnes_hut()\n",
313
+ "got_data = pd.read_csv(\"https://www.macalester.edu/~abeverid/data/stormofswords.csv\")\n",
314
+ "\n",
315
+ "sources = got_data['Source']\n",
316
+ "targets = got_data['Target']\n",
317
+ "weights = got_data['Weight']\n",
318
+ "\n",
319
+ "edge_data = zip(sources, targets, weights)\n",
320
+ "\n",
321
+ "for e in edge_data:\n",
322
+ " src = e[0]\n",
323
+ " dst = e[1]\n",
324
+ " w = e[2]\n",
325
+ "\n",
326
+ " got_net.add_node(src, src, title=src)\n",
327
+ " got_net.add_node(dst, dst, title=dst)\n",
328
+ " got_net.add_edge(src, dst, value=w)\n",
329
+ "\n",
330
+ "neighbor_map = got_net.get_adj_list()\n",
331
+ "\n",
332
+ "# add neighbor data to node hover data\n",
333
+ "for node in got_net.nodes:\n",
334
+ " node[\"title\"] += \" Neighbors:<br>\" + \"<br>\".join(neighbor_map[node[\"id\"]])\n",
335
+ " node[\"value\"] = len(neighbor_map[node[\"id\"]]) # this value attrribute for the node affects node size\n",
336
+ "\n",
337
+ "got_net.show(\"example.html\")"
338
+ ]
339
+ },
340
+ {
341
+ "cell_type": "markdown",
342
+ "metadata": {},
343
+ "source": [
344
+ "### Experimenting with options UI\n",
345
+ "Scroll down underneath the graph to play around with the physics settings to acheive optimal layout and behavior. You can use the generate options button to display the JSON representation of the configuration."
346
+ ]
347
+ },
348
+ {
349
+ "cell_type": "code",
350
+ "execution_count": 9,
351
+ "metadata": {},
352
+ "outputs": [
353
+ {
354
+ "data": {
355
+ "text/html": [
356
+ "\n",
357
+ " <iframe\n",
358
+ " width=\"100%\"\n",
359
+ " height=\"750px\"\n",
360
+ " src=\"example.html\"\n",
361
+ " frameborder=\"0\"\n",
362
+ " allowfullscreen\n",
363
+ " \n",
364
+ " ></iframe>\n",
365
+ " "
366
+ ],
367
+ "text/plain": [
368
+ "<IPython.lib.display.IFrame at 0x7f87da76ee50>"
369
+ ]
370
+ },
371
+ "execution_count": 9,
372
+ "metadata": {},
373
+ "output_type": "execute_result"
374
+ }
375
+ ],
376
+ "source": [
377
+ "got_net.show_buttons(filter_=\"physics\")\n",
378
+ "got_net.show(\"example.html\")"
379
+ ]
380
+ },
381
+ {
382
+ "cell_type": "code",
383
+ "execution_count": 13,
384
+ "metadata": {},
385
+ "outputs": [],
386
+ "source": [
387
+ "got_net.set_options('''var options = {\n",
388
+ " \"physics\": {\n",
389
+ " \"barnesHut\": {\n",
390
+ " \"gravitationalConstant\": -80000,\n",
391
+ " \"springLength\": 250,\n",
392
+ " \"springConstant\": 0.001\n",
393
+ " },\n",
394
+ " \"maxVelocity\": 34,\n",
395
+ " \"minVelocity\": 0.75\n",
396
+ " }\n",
397
+ "}''')"
398
+ ]
399
+ },
400
+ {
401
+ "cell_type": "code",
402
+ "execution_count": 10,
403
+ "metadata": {},
404
+ "outputs": [],
405
+ "source": [
406
+ "import networkx as nx"
407
+ ]
408
+ },
409
+ {
410
+ "cell_type": "code",
411
+ "execution_count": 11,
412
+ "metadata": {},
413
+ "outputs": [
414
+ {
415
+ "data": {
416
+ "text/html": [
417
+ "\n",
418
+ " <iframe\n",
419
+ " width=\"100%\"\n",
420
+ " height=\"750px\"\n",
421
+ " src=\"nx.html\"\n",
422
+ " frameborder=\"0\"\n",
423
+ " allowfullscreen\n",
424
+ " \n",
425
+ " ></iframe>\n",
426
+ " "
427
+ ],
428
+ "text/plain": [
429
+ "<IPython.lib.display.IFrame at 0x7f88443e75b0>"
430
+ ]
431
+ },
432
+ "execution_count": 11,
433
+ "metadata": {},
434
+ "output_type": "execute_result"
435
+ }
436
+ ],
437
+ "source": [
438
+ "nx_graph = nx.cycle_graph(10)\n",
439
+ "nx_graph.nodes[1]['title'] = 'Number 1'\n",
440
+ "nx_graph.nodes[1]['group'] = 1\n",
441
+ "nx_graph.nodes[3]['title'] = 'I belong to a different group!'\n",
442
+ "nx_graph.nodes[3]['group'] = 10\n",
443
+ "nx_graph.add_node(20, size=20, title='couple', group=2)\n",
444
+ "nx_graph.add_node(21, size=15, title='couple', group=2)\n",
445
+ "nx_graph.add_edge(20, 21, weight=5)\n",
446
+ "nx_graph.add_node(25, size=25, label='lonely', title='lonely node', group=3)\n",
447
+ "\n",
448
+ "nt = Network(notebook=True, height=\"750px\", width=\"100%\")\n",
449
+ "\n",
450
+ "nt.from_nx(nx_graph)\n",
451
+ "nt.show(\"nx.html\")"
452
+ ]
453
+ },
454
+ {
455
+ "cell_type": "code",
456
+ "execution_count": null,
457
+ "metadata": {},
458
+ "outputs": [],
459
+ "source": []
460
+ }
461
+ ],
462
+ "metadata": {
463
+ "kernelspec": {
464
+ "display_name": "Python 3",
465
+ "language": "python",
466
+ "name": "python3"
467
+ },
468
+ "language_info": {
469
+ "codemirror_mode": {
470
+ "name": "ipython",
471
+ "version": 3
472
+ },
473
+ "file_extension": ".py",
474
+ "mimetype": "text/x-python",
475
+ "name": "python",
476
+ "nbconvert_exporter": "python",
477
+ "pygments_lexer": "ipython3",
478
+ "version": "3.8.10"
479
+ }
480
+ },
481
+ "nbformat": 4,
482
+ "nbformat_minor": 2
483
+ }
pyvis/notebooks/test.dot ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ digraph {
2
+ a [label=12, entity_id=12, entity_class="truck"];
3
+ b [label=7, entity_id=7,entity_class="bike"];
4
+ c [label=3, entity_id=3, entity_class="car"];
5
+ a -> b[label="solid edge"];
6
+ a -> b [label="dashed edge", style=dashed];
7
+ a -> c [label="dashed edge", style=dashed];
8
+ a -> c [label="dotted edge", style=dotted];
9
+ }
pyvis/pyproject.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta:__legacy__"
pyvis/pyvis/Makefile ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line.
5
+ SPHINXOPTS =
6
+ SPHINXBUILD = sphinx-build
7
+ PAPER =
8
+ BUILDDIR = build
9
+
10
+ # User-friendly check for sphinx-build
11
+ ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12
+ $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
13
+ endif
14
+
15
+ # Internal variables.
16
+ PAPEROPT_a4 = -D latex_paper_size=a4
17
+ PAPEROPT_letter = -D latex_paper_size=letter
18
+ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19
+ # the i18n builder cannot share the environment and doctrees with the others
20
+ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21
+
22
+ .PHONY: help
23
+ help:
24
+ @echo "Please use \`make <target>' where <target> is one of"
25
+ @echo " html to make standalone HTML files"
26
+ @echo " dirhtml to make HTML files named index.html in directories"
27
+ @echo " singlehtml to make a single large HTML file"
28
+ @echo " pickle to make pickle files"
29
+ @echo " json to make JSON files"
30
+ @echo " htmlhelp to make HTML files and a HTML help project"
31
+ @echo " qthelp to make HTML files and a qthelp project"
32
+ @echo " applehelp to make an Apple Help Book"
33
+ @echo " devhelp to make HTML files and a Devhelp project"
34
+ @echo " epub to make an epub"
35
+ @echo " epub3 to make an epub3"
36
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
38
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39
+ @echo " text to make text files"
40
+ @echo " man to make manual pages"
41
+ @echo " texinfo to make Texinfo files"
42
+ @echo " info to make Texinfo files and run them through makeinfo"
43
+ @echo " gettext to make PO message catalogs"
44
+ @echo " changes to make an overview of all changed/added/deprecated items"
45
+ @echo " xml to make Docutils-native XML files"
46
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47
+ @echo " linkcheck to check all external links for integrity"
48
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49
+ @echo " coverage to run coverage check of the documentation (if enabled)"
50
+ @echo " dummy to check syntax errors of document sources"
51
+
52
+ .PHONY: clean
53
+ clean:
54
+ rm -rf $(BUILDDIR)/*
55
+
56
+ .PHONY: html
57
+ html:
58
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
59
+ @echo
60
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
61
+
62
+ .PHONY: dirhtml
63
+ dirhtml:
64
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
65
+ @echo
66
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
67
+
68
+ .PHONY: singlehtml
69
+ singlehtml:
70
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
71
+ @echo
72
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
73
+
74
+ .PHONY: pickle
75
+ pickle:
76
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
77
+ @echo
78
+ @echo "Build finished; now you can process the pickle files."
79
+
80
+ .PHONY: json
81
+ json:
82
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
83
+ @echo
84
+ @echo "Build finished; now you can process the JSON files."
85
+
86
+ .PHONY: htmlhelp
87
+ htmlhelp:
88
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
89
+ @echo
90
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
91
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
92
+
93
+ .PHONY: qthelp
94
+ qthelp:
95
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
96
+ @echo
97
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
98
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
99
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyvis.qhcp"
100
+ @echo "To view the help file:"
101
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyvis.qhc"
102
+
103
+ .PHONY: applehelp
104
+ applehelp:
105
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
106
+ @echo
107
+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
108
+ @echo "N.B. You won't be able to view it unless you put it in" \
109
+ "~/Library/Documentation/Help or install it in your application" \
110
+ "bundle."
111
+
112
+ .PHONY: devhelp
113
+ devhelp:
114
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
115
+ @echo
116
+ @echo "Build finished."
117
+ @echo "To view the help file:"
118
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/pyvis"
119
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyvis"
120
+ @echo "# devhelp"
121
+
122
+ .PHONY: epub
123
+ epub:
124
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
125
+ @echo
126
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
127
+
128
+ .PHONY: epub3
129
+ epub3:
130
+ $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
131
+ @echo
132
+ @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
133
+
134
+ .PHONY: latex
135
+ latex:
136
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
137
+ @echo
138
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
139
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
140
+ "(use \`make latexpdf' here to do that automatically)."
141
+
142
+ .PHONY: latexpdf
143
+ latexpdf:
144
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
145
+ @echo "Running LaTeX files through pdflatex..."
146
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
147
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
148
+
149
+ .PHONY: latexpdfja
150
+ latexpdfja:
151
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
152
+ @echo "Running LaTeX files through platex and dvipdfmx..."
153
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
154
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
155
+
156
+ .PHONY: text
157
+ text:
158
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
159
+ @echo
160
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
161
+
162
+ .PHONY: man
163
+ man:
164
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
165
+ @echo
166
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
167
+
168
+ .PHONY: texinfo
169
+ texinfo:
170
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
171
+ @echo
172
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
173
+ @echo "Run \`make' in that directory to run these through makeinfo" \
174
+ "(use \`make info' here to do that automatically)."
175
+
176
+ .PHONY: info
177
+ info:
178
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
179
+ @echo "Running Texinfo files through makeinfo..."
180
+ make -C $(BUILDDIR)/texinfo info
181
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
182
+
183
+ .PHONY: gettext
184
+ gettext:
185
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
186
+ @echo
187
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
188
+
189
+ .PHONY: changes
190
+ changes:
191
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
192
+ @echo
193
+ @echo "The overview file is in $(BUILDDIR)/changes."
194
+
195
+ .PHONY: linkcheck
196
+ linkcheck:
197
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
198
+ @echo
199
+ @echo "Link check complete; look for any errors in the above output " \
200
+ "or in $(BUILDDIR)/linkcheck/output.txt."
201
+
202
+ .PHONY: doctest
203
+ doctest:
204
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
205
+ @echo "Testing of doctests in the sources finished, look at the " \
206
+ "results in $(BUILDDIR)/doctest/output.txt."
207
+
208
+ .PHONY: coverage
209
+ coverage:
210
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
211
+ @echo "Testing of coverage in the sources finished, look at the " \
212
+ "results in $(BUILDDIR)/coverage/python.txt."
213
+
214
+ .PHONY: xml
215
+ xml:
216
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
217
+ @echo
218
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
219
+
220
+ .PHONY: pseudoxml
221
+ pseudoxml:
222
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
223
+ @echo
224
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
225
+
226
+ .PHONY: dummy
227
+ dummy:
228
+ $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
229
+ @echo
230
+ @echo "Build finished. Dummy builder generates no files."
pyvis/pyvis/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from . import network
2
+ from ._version import __version__
pyvis/pyvis/_version.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "0.2.0" # bump version
pyvis/pyvis/edge.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ class Edge(object):
2
+ def __init__(self, source, dest, directed=False, **options):
3
+ self.options = options
4
+ self.options["from"] = source
5
+ self.options["to"] = dest
6
+ if directed:
7
+ if "arrows" not in self.options:
8
+ self.options["arrows"] = "to"
pyvis/pyvis/network.py ADDED
@@ -0,0 +1,984 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import platform
4
+ import shutil
5
+ import tempfile
6
+ import webbrowser
7
+ from collections import defaultdict
8
+ from platform import uname
9
+
10
+ import jsonpickle
11
+ import networkx as nx
12
+ from IPython.core.display import HTML
13
+ from IPython.display import IFrame
14
+ from jinja2 import Template
15
+
16
+ from .edge import Edge
17
+ from .node import Node
18
+ from .options import Configure, Options
19
+ from .utils import check_html
20
+
21
+
22
+ class Network(object):
23
+ """
24
+ The Network class is the focus of this library. All viz functionality
25
+ should be implemented off of a Network instance.
26
+
27
+ To instantiate:
28
+
29
+ >>> nt = Network()
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ height="500px",
35
+ width="100%",
36
+ directed=False,
37
+ notebook=False,
38
+ neighborhood_highlight=False,
39
+ select_menu=False,
40
+ bgcolor="#ffffff",
41
+ font_color=False,
42
+ layout=None,
43
+ heading="",
44
+ ):
45
+ """
46
+ :param height: The height of the canvas
47
+ :param width: The width of the canvas
48
+ :param directed: Whether or not to use a directed graph. This is false
49
+ by default.
50
+ :param notebook: True if using jupyter notebook.
51
+ :param bgcolor: The background color of the canvas.
52
+ :font_color: The color of the node labels text
53
+ :layout: Use hierarchical layout if this is set
54
+
55
+ :type height: num or str
56
+ :type width: num or str
57
+ :type directed: bool
58
+ :type notebook: bool
59
+ :type bgcolor: str
60
+ :type font_color: str
61
+ :type layout: bool
62
+ """
63
+ self.nodes = []
64
+ self.edges = []
65
+ self.height = height
66
+ self.width = width
67
+ self.heading = heading
68
+ self.html = ""
69
+ self.shape = "dot"
70
+ self.font_color = font_color
71
+ self.directed = directed
72
+ self.bgcolor = bgcolor
73
+ self.use_DOT = False
74
+ self.dot_lang = ""
75
+ self.options = Options(layout)
76
+ self.widget = False
77
+ self.node_ids = []
78
+ self.node_map = {}
79
+ self.template = None
80
+ self.conf = False
81
+ self.path = os.path.dirname(__file__) + "/templates/template.html"
82
+ self.neighborhood_highlight = neighborhood_highlight
83
+ self.select_menu = select_menu
84
+
85
+ if notebook:
86
+ self.prep_notebook()
87
+
88
+ def __str__(self):
89
+ """
90
+ override print to show readable graph data
91
+ """
92
+ return str(
93
+ json.dumps(
94
+ {
95
+ "Nodes": self.node_ids,
96
+ "Edges": self.edges,
97
+ "Height": self.height,
98
+ "Width": self.width,
99
+ "Heading": self.heading,
100
+ },
101
+ indent=4,
102
+ )
103
+ )
104
+
105
+ def __repr__(self):
106
+ return "{} |N|={} |E|={:,}".format(
107
+ self.__class__, self.num_nodes(), self.num_edges()
108
+ )
109
+
110
+ def add_node(self, n_id, label=None, shape="dot", **options):
111
+ """
112
+ This method adds a node to the network, given a mandatory node ID.
113
+ Node labels default to node ids if no label is specified during the
114
+ call.
115
+
116
+ >>> nt = Network("500px", "500px")
117
+ >>> nt.add_node(0, label="Node 0")
118
+ >>> nt.add_node(1, label="Node 1", color = "blue")
119
+
120
+ :param n_id: The id of the node. The id is mandatory for nodes and
121
+ they have to be unique. This should obviously be set per
122
+ node, not globally.
123
+
124
+ :param label: The label is the piece of text shown in or under the
125
+ node, depending on the shape.
126
+
127
+ :param borderWidth: The width of the border of the node.
128
+
129
+ :param borderWidthSelected: The width of the border of the node when
130
+ it is selected. When undefined, the
131
+ borderWidth * 2 is used.
132
+
133
+ :param brokenImage: When the shape is set to image or circularImage,
134
+ this option can be an URL to a backup image in
135
+ case the URL supplied in the image option cannot
136
+ be resolved.
137
+
138
+ :param group: When not undefined, the node will belong to the defined
139
+ group. Styling information of that group will apply to
140
+ this node. Node specific styling overrides group styling.
141
+
142
+ :param hidden: When true, the node will not be shown. It will still be
143
+ part of the physics simulation though!
144
+
145
+ :param image: When the shape is set to image or circularImage, this
146
+ option should be the URL to an image. If the image
147
+ cannot be found, the brokenImage option can be used.
148
+
149
+ :param labelHighlightBold: Determines whether or not the label becomes
150
+ bold when the node is selected.
151
+
152
+ :param level: When using the hierarchical layout, the level determines
153
+ where the node is going to be positioned.
154
+
155
+ :param mass: The barnesHut physics model (which is enabled by default)
156
+ is based on an inverted gravity model. By increasing
157
+ the mass of a node, you increase it's repulsion. Values
158
+ lower than 1 are not recommended.
159
+
160
+ :param physics: When false, the node is not part of the physics
161
+ simulation. It will not move except for from
162
+ manual dragging.
163
+
164
+ :param shape: The shape defines what the node looks like. There are
165
+ two types of nodes. One type has the label inside of
166
+ it and the other type has the label underneath it. The
167
+ types with the label inside of it are: ellipse, circle,
168
+ database, box, text. The ones with the label outside of
169
+ it are: image, circularImage, diamond, dot, star,
170
+ triangle, triangleDown, square and icon.
171
+
172
+ :param size: The size is used to determine the size of node shapes that
173
+ do not have the label inside of them. These shapes are:
174
+ image, circularImage, diamond, dot, star, triangle,
175
+ triangleDown, square and icon.
176
+
177
+ :param title: Title to be displayed when the user hovers over the node.
178
+ The title can be an HTML element or a string containing
179
+ plain text or HTML.
180
+
181
+ :param value: When a value is set, the nodes will be scaled using the
182
+ options in the scaling object defined above.
183
+
184
+ :param x: This gives a node an initial x position. When using the
185
+ hierarchical layout, either the x or y position is set by the
186
+ layout engine depending on the type of view. The other value
187
+ remains untouched. When using stabilization, the stabilized
188
+ position may be different from the initial one. To lock the
189
+ node to that position use the physics or fixed options.
190
+
191
+ :param y: This gives a node an initial y position. When using the
192
+ hierarchical layout,either the x or y position is set by
193
+ the layout engine depending on the type of view. The
194
+ other value remains untouched. When using stabilization,
195
+ the stabilized position may be different from the initial
196
+ one. To lock the node to that position use the physics or
197
+ fixed options.
198
+
199
+ :type n_id: str or int
200
+ :type label: str or int
201
+ :type borderWidth: num (optional)
202
+ :type borderWidthSelected: num (optional)
203
+ :type brokenImage: str (optional)
204
+ :type group: str (optional)
205
+ :type hidden: bool (optional)
206
+ :type image: str (optional)
207
+ :type labelHighlightBold: bool (optional)
208
+ :type level: num (optional)
209
+ :type mass: num (optional)
210
+ :type physics: bool (optional)
211
+ :type shape: str (optional)
212
+ :type size: num (optional)
213
+ :type title: str or html element (optional)
214
+ :type value: num (optional)
215
+ :type x: num (optional)
216
+ :type y: num (optional)
217
+ """
218
+ assert isinstance(n_id, str) or isinstance(n_id, int)
219
+ if label:
220
+ node_label = label
221
+ else:
222
+ node_label = n_id
223
+ if n_id not in self.node_ids:
224
+
225
+ n = Node(
226
+ n_id, shape, label=node_label, font_color=self.font_color, **options
227
+ )
228
+
229
+ # if "group" in options:
230
+ # n = Node(n_id, shape, label=node_label, font_color=self.font_color, **options)
231
+ # else:
232
+ # n = Node(n_id, shape, label=node_label, color=color, font_color=self.font_color, **options)
233
+
234
+ self.nodes.append(n.options)
235
+ self.node_ids.append(n_id)
236
+ self.node_map[n_id] = n.options
237
+
238
+ def add_nodes(self, nodes, **kwargs):
239
+ """
240
+ This method adds multiple nodes to the network from a list.
241
+ Default behavior uses values of 'nodes' for node ID and node label
242
+ properties. You can also specify other lists of properties to go
243
+ along each node.
244
+
245
+ Example:
246
+
247
+ >>> g = net.Network()
248
+ >>> g.add_nodes([1, 2, 3], size=[2, 4, 6], title=["n1", "n2", "n3"])
249
+ >>> g.nodes
250
+ >>> [{'id': 1, 'label': 1, 'shape': 'dot', 'size': 2, 'title': 'n1'},
251
+
252
+ Output:
253
+
254
+ >>> {'id': 2, 'label': 2, 'shape': 'dot', 'size': 4, 'title': 'n2'},
255
+ >>> {'id': 3, 'label': 3, 'shape': 'dot', 'size': 6, 'title': 'n3'}]
256
+
257
+
258
+ :param nodes: A list of nodes.
259
+
260
+ :type nodes: list
261
+ """
262
+ valid_args = ["size", "value", "title", "x", "y", "label", "color", "shape"]
263
+ for k in kwargs:
264
+ assert k in valid_args, "invalid arg '" + k + "'"
265
+
266
+ nd = defaultdict(dict)
267
+ for i in range(len(nodes)):
268
+ for k, v in kwargs.items():
269
+ assert len(v) == len(nodes), (
270
+ "keyword arg %s [length %s] does not match"
271
+ "[length %s] of nodes" % (k, len(v), len(nodes))
272
+ )
273
+ nd[nodes[i]].update({k: v[i]})
274
+
275
+ for node in nodes:
276
+ # check if node is `number-like`
277
+ try:
278
+ node = int(node)
279
+ self.add_node(node, **nd[node])
280
+ except:
281
+ # or node could be string
282
+ assert isinstance(node, str)
283
+ self.add_node(node, **nd[node])
284
+
285
+ def num_nodes(self):
286
+ """
287
+ Return number of nodes
288
+
289
+ :returns: :py:class:`int`
290
+ """
291
+ return len(self.node_ids)
292
+
293
+ def num_edges(self):
294
+ """
295
+ Return number of edges
296
+
297
+ :returns: :py:class:`int`
298
+ """
299
+ return len(self.edges)
300
+
301
+ def add_edge(self, source, to, **options):
302
+ """
303
+
304
+ Adding edges is done based off of the IDs of the nodes. Order does
305
+ not matter unless dealing with a directed graph.
306
+
307
+ >>> nt.add_edge(0, 1) # adds an edge from node ID 0 to node ID
308
+ >>> nt.add_edge(0, 1, value = 4) # adds an edge with a width of 4
309
+
310
+
311
+ :param arrowStrikethrough: When false, the edge stops at the arrow.
312
+ This can be useful if you have thick lines
313
+ and you want the arrow to end in a point.
314
+ Middle arrows are not affected by this.
315
+
316
+ :param from: Edges are between two nodes, one to and one from. This
317
+ is where you define the from node. You have to supply
318
+ the corresponding node ID. This naturally only applies
319
+ to individual edges.
320
+
321
+ :param hidden: When true, the edge is not drawn. It is part still part
322
+ of the physics simulation however!
323
+
324
+ :param physics: When true, the edge is part of the physics simulation.
325
+ When false, it will not act as a spring.
326
+
327
+ :param title: The title is shown in a pop-up when the mouse moves over
328
+ the edge.
329
+
330
+ :param to: Edges are between two nodes, one to and one from. This is
331
+ where you define the to node. You have to supply the
332
+ corresponding node ID. This naturally only applies to
333
+ individual edges.
334
+
335
+ :param value: When a value is set, the edges' width will be scaled
336
+ using the options in the scaling object defined above.
337
+
338
+ :param width: The width of the edge. If value is set, this is not used.
339
+
340
+
341
+ :type arrowStrikethrough: bool
342
+ :type from: str or num
343
+ :type hidden: bool
344
+ :type physics: bool
345
+ :type title: str
346
+ :type to: str or num
347
+ :type value: num
348
+ :type width: num
349
+ """
350
+ edge_exists = False
351
+
352
+ # verify nodes exists
353
+ assert source in self.get_nodes(), "non existent node '" + str(source) + "'"
354
+
355
+ assert to in self.get_nodes(), "non existent node '" + str(to) + "'"
356
+
357
+ # we only check existing edge for undirected graphs
358
+ if not self.directed:
359
+ for e in self.edges:
360
+ frm = e["from"]
361
+ dest = e["to"]
362
+ if (source == dest and to == frm) or (source == frm and to == dest):
363
+ # edge already exists
364
+ edge_exists = True
365
+
366
+ if not edge_exists:
367
+ e = Edge(source, to, self.directed, **options)
368
+ self.edges.append(e.options)
369
+
370
+ def add_edges(self, edges):
371
+ """
372
+ This method serves to add multiple edges between existing nodes
373
+ in the network instance. Adding of the edges is done based off
374
+ of the IDs of the nodes. Order does not matter unless dealing with a
375
+ directed graph.
376
+
377
+ :param edges: A list of tuples, each tuple consists of source of edge,
378
+ edge destination and and optional width.
379
+
380
+ :type arrowStrikethrough: list of tuples
381
+ """
382
+ for edge in edges:
383
+ # if incoming tuple contains a weight
384
+ if len(edge) == 3:
385
+ self.add_edge(edge[0], edge[1], width=edge[2])
386
+ else:
387
+ self.add_edge(edge[0], edge[1])
388
+
389
+ def get_network_data(self):
390
+ """
391
+ Extract relevant information about this network in order to inject into
392
+ a Jinja2 template.
393
+
394
+ Returns:
395
+ nodes (list), edges (list), height (
396
+ string), width (string), options (object)
397
+
398
+ Usage:
399
+
400
+ >>> nodes, edges, heading, height, width, options = net.get_network_data()
401
+ """
402
+ if isinstance(self.options, dict):
403
+ return (
404
+ self.nodes,
405
+ self.edges,
406
+ self.heading,
407
+ self.height,
408
+ self.width,
409
+ json.dumps(self.options),
410
+ )
411
+ else:
412
+ return (
413
+ self.nodes,
414
+ self.edges,
415
+ self.heading,
416
+ self.height,
417
+ self.width,
418
+ self.options.to_json(),
419
+ )
420
+
421
+ def save_graph(self, name):
422
+ """
423
+ Save the graph as html in the current directory with name.
424
+
425
+ :param name: the name of the html file to save as
426
+ :type name: str
427
+ """
428
+ check_html(name)
429
+ self.write_html(name)
430
+
431
+ def generate_html(self, notebook=False):
432
+ """
433
+ This method generates HTML from the data structures supporting the nodes, edges,
434
+ and options and updates the template to generate the HTML holding
435
+ the visualization.
436
+
437
+ :type notebook: bool
438
+
439
+ Returns
440
+ :type out: str
441
+ """
442
+
443
+ # here, check if an href is present in the hover data
444
+ use_link_template = False
445
+ for n in self.nodes:
446
+ title = n.get("title", None)
447
+ if title:
448
+ if "href" in title:
449
+ """
450
+ this tells the template to override default hover
451
+ mechanic, as the tooltip would move with the mouse
452
+ cursor which made interacting with hover data useless.
453
+ """
454
+ use_link_template = True
455
+ break
456
+ if not notebook:
457
+ with open(self.path) as html:
458
+ content = html.read()
459
+ template = Template(content)
460
+ else:
461
+ template = self.template
462
+
463
+ nodes, edges, heading, height, width, options = self.get_network_data()
464
+
465
+ # check if physics is enabled
466
+ if isinstance(self.options, dict):
467
+ if "physics" in self.options and "enabled" in self.options["physics"]:
468
+ physics_enabled = self.options["physics"]["enabled"]
469
+ else:
470
+ physics_enabled = True
471
+ else:
472
+ physics_enabled = self.options.physics.enabled
473
+
474
+ out = template.render(
475
+ height=height,
476
+ width=width,
477
+ nodes=nodes,
478
+ edges=edges,
479
+ heading=heading,
480
+ options=options,
481
+ physics_enabled=physics_enabled,
482
+ use_DOT=self.use_DOT,
483
+ dot_lang=self.dot_lang,
484
+ widget=self.widget,
485
+ bgcolor=self.bgcolor,
486
+ conf=self.conf,
487
+ tooltip_link=use_link_template,
488
+ neighborhood_highlight=self.neighborhood_highlight,
489
+ select_menu=self.select_menu,
490
+ )
491
+
492
+ return out
493
+
494
+ def write_html(self, name="index.html", local=True, notebook=False):
495
+ """
496
+ This method gets the data structures supporting the nodes, edges,
497
+ and options and updates the template to write the HTML holding
498
+ the visualization.
499
+ :type name_html: str
500
+ """
501
+ check_html(name)
502
+ self.html = self.generate_html(notebook=notebook)
503
+
504
+ if notebook:
505
+ if os.path.exists("lib"):
506
+ shutil.rmtree(f"lib")
507
+ shutil.copytree(f"{os.path.dirname(__file__)}/lib", "lib")
508
+ with open(name, "w+") as out:
509
+ out.write(self.html)
510
+ return IFrame(name, width=self.width, height="600px")
511
+ else:
512
+ if local:
513
+ tempdir = "."
514
+ else:
515
+ tempdir = tempfile.mkdtemp()
516
+ # with tempfile.mkdtemp() as tempdir:
517
+ if os.path.exists(f"{tempdir}/lib"):
518
+ shutil.rmtree(f"{tempdir}/lib")
519
+ shutil.copytree(f"{os.path.dirname(__file__)}/lib", f"{tempdir}/lib")
520
+
521
+ with open(f"{tempdir}/{name}", "w+") as out:
522
+ out.write(self.html)
523
+ webbrowser.open(f"{tempdir}/{name}")
524
+
525
+ def show(self, name, local=True):
526
+ """
527
+ Writes a static HTML file and saves it locally before opening.
528
+
529
+ :param: name: the name of the html file to save as
530
+ :type name: str
531
+ """
532
+ check_html(name)
533
+ if self.template is not None:
534
+ return self.write_html(name, local, notebook=True)
535
+ else:
536
+ self.write_html(name, local)
537
+ # webbrowser.open(name)
538
+
539
+ def prep_notebook(self, custom_template=False, custom_template_path=None):
540
+ """
541
+ Loads the template data into the template attribute of the network.
542
+ This should be done in a jupyter notebook environment before showing
543
+ the network.
544
+
545
+ Example:
546
+ >>> net.prep_notebook()
547
+ >>> net.show("nb.html")
548
+
549
+
550
+ :param path: the relative path pointing to a template html file
551
+ :type path: string
552
+ """
553
+ if custom_template and custom_template_path:
554
+ self.set_template(custom_template_path)
555
+ with open(self.path) as html:
556
+ content = html.read()
557
+ self.template = Template(content)
558
+
559
+ def set_template(self, path_to_template):
560
+ self.path = path_to_template
561
+
562
+ def from_DOT(self, dot):
563
+ """
564
+ This method takes the contents of .DOT file and converts it
565
+ to a PyVis visualization.
566
+
567
+ Assuming the contents of test.dot contains:
568
+ digraph sample3 {
569
+ A -> {B ; C ; D}
570
+ C -> {B ; A}
571
+ }
572
+
573
+ Usage:
574
+
575
+ >>> nt.Network("500px", "500px")
576
+ >>> nt.from_DOT("test.dot")
577
+ >>> nt.show("dot.html")
578
+
579
+ :param dot: The path of the dotfile being converted.
580
+ :type dot: .dot file
581
+
582
+ """
583
+ self.use_DOT = True
584
+ file = open(dot, "r")
585
+ s = str(file.read())
586
+ self.dot_lang = " ".join(s.splitlines())
587
+ self.dot_lang = self.dot_lang.replace('"', '\\"')
588
+
589
+ def get_adj_list(self):
590
+ """
591
+ This method returns the user an adjacency list representation
592
+ of the network.
593
+
594
+ :returns: dictionary mapping of Node ID to list of Node IDs it
595
+ is connected to.
596
+ """
597
+ a_list = {}
598
+ for i in self.nodes:
599
+ a_list[i["id"]] = set()
600
+ if self.directed:
601
+ for e in self.edges:
602
+ source = e["from"]
603
+ dest = e["to"]
604
+ a_list[source].add(dest)
605
+ else:
606
+ for e in self.edges:
607
+ source = e["from"]
608
+ dest = e["to"]
609
+ if dest not in a_list[source] and source not in a_list[dest]:
610
+ a_list[source].add(dest)
611
+ a_list[dest].add(source)
612
+ return a_list
613
+
614
+ def neighbors(self, node):
615
+ """
616
+ Given a node id, return the set of neighbors of this particular node.
617
+
618
+ :param node: The node to get the neighbors from
619
+ :type node: str or int
620
+
621
+ :returns: set
622
+ """
623
+ assert isinstance(node, str) or isinstance(
624
+ node, int
625
+ ), "error: expected int or str for node but got %s" % type(node)
626
+ assert node in self.node_ids, "error: %s node not in network" % node
627
+ return self.get_adj_list()[node]
628
+
629
+ def from_nx(
630
+ self,
631
+ nx_graph,
632
+ node_size_transf=(lambda x: x),
633
+ edge_weight_transf=(lambda x: x),
634
+ default_node_size=10,
635
+ default_edge_weight=1,
636
+ show_edge_weights=True,
637
+ edge_scaling=False,
638
+ ):
639
+ """
640
+ This method takes an exisitng Networkx graph and translates
641
+ it to a PyVis graph format that can be accepted by the VisJs
642
+ API in the Jinja2 template. This operation is done in place.
643
+
644
+ :param nx_graph: The Networkx graph object that is to be translated.
645
+ :type nx_graph: networkx.Graph instance
646
+ :param node_size_transf: function to transform the node size for plotting
647
+ :type node_size_transf: func
648
+ :param edge_weight_transf: function to transform the edge weight for plotting
649
+ :type edge_weight_transf: func
650
+ :param default_node_size: default node size if not specified
651
+ :param default_edge_weight: default edge weight if not specified
652
+ >>> nx_graph = nx.cycle_graph(10)
653
+ >>> nx_graph.nodes[1]['title'] = 'Number 1'
654
+ >>> nx_graph.nodes[1]['group'] = 1
655
+ >>> nx_graph.nodes[3]['title'] = 'I belong to a different group!'
656
+ >>> nx_graph.nodes[3]['group'] = 10
657
+ >>> nx_graph.add_node(20, size=20, title='couple', group=2)
658
+ >>> nx_graph.add_node(21, size=15, title='couple', group=2)
659
+ >>> nx_graph.add_edge(20, 21, weight=5)
660
+ >>> nx_graph.add_node(25, size=25, label='lonely', title='lonely node', group=3)
661
+ >>> nt = Network("500px", "500px")
662
+ # populates the nodes and edges data structures
663
+ >>> nt.from_nx(nx_graph)
664
+ >>> nt.show("nx.html")
665
+ """
666
+ assert isinstance(nx_graph, nx.Graph)
667
+ edges = nx_graph.edges(data=True)
668
+ nodes = nx_graph.nodes(data=True)
669
+
670
+ if len(edges) > 0:
671
+ for e in edges:
672
+ if "size" not in nodes[e[0]].keys():
673
+ nodes[e[0]]["size"] = default_node_size
674
+ nodes[e[0]]["size"] = int(node_size_transf(nodes[e[0]]["size"]))
675
+ if "size" not in nodes[e[1]].keys():
676
+ nodes[e[1]]["size"] = default_node_size
677
+ nodes[e[1]]["size"] = int(node_size_transf(nodes[e[1]]["size"]))
678
+ self.add_node(e[0], **nodes[e[0]])
679
+ self.add_node(e[1], **nodes[e[1]])
680
+
681
+ # if user does not pass a 'weight' argument
682
+ if "value" not in e[2] or "width" not in e[2]:
683
+ if edge_scaling:
684
+ width_type = "value"
685
+ else:
686
+ width_type = "width"
687
+ if "weight" not in e[2].keys():
688
+ e[2]["weight"] = default_edge_weight
689
+ e[2][width_type] = edge_weight_transf(e[2]["weight"])
690
+ # replace provided weight value and pass to 'value' or 'width'
691
+ e[2][width_type] = e[2].pop("weight")
692
+
693
+ self.add_edge(e[0], e[1], **e[2])
694
+
695
+ for node in nx.isolates(nx_graph):
696
+ if "size" not in nodes[node].keys():
697
+ nodes[node]["size"] = default_node_size
698
+ self.add_node(node, **nodes[node])
699
+
700
+ def get_nodes(self):
701
+ """
702
+ This method returns an iterable list of node ids
703
+
704
+ :returns: list
705
+ """
706
+ return self.node_ids
707
+
708
+ def get_node(self, n_id):
709
+ """
710
+ Lookup node by ID and return it.
711
+
712
+ :param n_id: The ID given to the node.
713
+
714
+ :returns: dict containing node properties
715
+ """
716
+ return self.node_map[n_id]
717
+
718
+ def get_edges(self):
719
+ """
720
+ This method returns an iterable list of edge objects
721
+
722
+ :returns: list
723
+ """
724
+ return self.edges
725
+
726
+ def barnes_hut(
727
+ self,
728
+ gravity=-80000,
729
+ central_gravity=0.3,
730
+ spring_length=250,
731
+ spring_strength=0.001,
732
+ damping=0.09,
733
+ overlap=0,
734
+ ):
735
+ """
736
+ BarnesHut is a quadtree based gravity model. It is the fastest. default
737
+ and recommended solver for non-hierarchical layouts.
738
+
739
+ :param gravity: The more negative the gravity value is, the stronger the
740
+ repulsion is.
741
+ :param central_gravity: The gravity attractor to pull the entire network
742
+ to the center.
743
+ :param spring_length: The rest length of the edges
744
+ :param spring_strength: The strong the edges springs are
745
+ :param damping: A value ranging from 0 to 1 of how much of the velocity
746
+ from the previous physics simulation iteration carries
747
+ over to the next iteration.
748
+ :param overlap: When larger than 0, the size of the node is taken into
749
+ account. The distance will be calculated from the radius
750
+ of the encompassing circle of the node for both the
751
+ gravity model. Value 1 is maximum overlap avoidance.
752
+
753
+ :type gravity: int
754
+ :type central_gravity: float
755
+ :type spring_length: int
756
+ :type spring_strength: float
757
+ :type damping: float
758
+ :type overlap: float
759
+ """
760
+ self.options.physics.use_barnes_hut(locals())
761
+
762
+ def repulsion(
763
+ self,
764
+ node_distance=100,
765
+ central_gravity=0.2,
766
+ spring_length=200,
767
+ spring_strength=0.05,
768
+ damping=0.09,
769
+ ):
770
+ """
771
+ Set the physics attribute of the entire network to repulsion.
772
+ When called, it sets the solver attribute of physics to repulsion.
773
+
774
+ :param node_distance: This is the range of influence for the repulsion.
775
+ :param central_gravity: The gravity attractor to pull the entire network
776
+ to the center.
777
+ :param spring_length: The rest length of the edges
778
+ :param spring_strength: The strong the edges springs are
779
+ :param damping: A value ranging from 0 to 1 of how much of the velocity
780
+ from the previous physics simulation iteration carries
781
+ over to the next iteration.
782
+
783
+ :type node_distance: int
784
+ :type central_gravity float
785
+ :type spring_length: int
786
+ :type spring_strength: float
787
+ :type damping: float
788
+ """
789
+ self.options.physics.use_repulsion(locals())
790
+
791
+ def hrepulsion(
792
+ self,
793
+ node_distance=120,
794
+ central_gravity=0.0,
795
+ spring_length=100,
796
+ spring_strength=0.01,
797
+ damping=0.09,
798
+ ):
799
+ """
800
+ This model is based on the repulsion solver but the levels are
801
+ taken into account and the forces are normalized.
802
+
803
+ :param node_distance: This is the range of influence for the repulsion.
804
+ :param central_gravity: The gravity attractor to pull the entire network
805
+ to the center.
806
+ :param spring_length: The rest length of the edges
807
+ :param spring_strength: The strong the edges springs are
808
+ :param damping: A value ranging from 0 to 1 of how much of the velocity
809
+ from the previous physics simulation iteration carries
810
+ over to the next iteration.
811
+
812
+ :type node_distance: int
813
+ :type central_gravity float
814
+ :type spring_length: int
815
+ :type spring_strength: float
816
+ :type damping: float
817
+ """
818
+ self.options.physics.use_hrepulsion(locals())
819
+
820
+ def force_atlas_2based(
821
+ self,
822
+ gravity=-50,
823
+ central_gravity=0.01,
824
+ spring_length=100,
825
+ spring_strength=0.08,
826
+ damping=0.4,
827
+ overlap=0,
828
+ ):
829
+ """
830
+ The forceAtlas2Based solver makes use of some of the equations provided
831
+ by them and makes use of the barnesHut implementation in vis. The main
832
+ differences are the central gravity model, which is here distance
833
+ independent, and the repulsion being linear instead of quadratic. Finally,
834
+ all node masses have a multiplier based on the amount of connected edges
835
+ plus one.
836
+
837
+ :param gravity: The more negative the gravity value is, the stronger the
838
+ repulsion is.
839
+ :param central_gravity: The gravity attractor to pull the entire network
840
+ to the center.
841
+ :param spring_length: The rest length of the edges
842
+ :param spring_strength: The strong the edges springs are
843
+ :param damping: A value ranging from 0 to 1 of how much of the velocity
844
+ from the previous physics simulation iteration carries
845
+ over to the next iteration.
846
+ :param overlap: When larger than 0, the size of the node is taken into
847
+ account. The distance will be calculated from the radius
848
+ of the encompassing circle of the node for both the
849
+ gravity model. Value 1 is maximum overlap avoidance.
850
+
851
+ :type gravity: int
852
+ :type central_gravity: float
853
+ :type spring_length: int
854
+ :type spring_strength: float
855
+ :type damping: float
856
+ :type overlap: float
857
+ """
858
+ self.options.physics.use_force_atlas_2based(locals())
859
+
860
+ def to_json(self, max_depth=1, **args):
861
+ return jsonpickle.encode(self, max_depth=max_depth, **args)
862
+
863
+ def set_edge_smooth(self, smooth_type):
864
+ """
865
+ Sets the smooth.type attribute of the edges.
866
+
867
+ :param smooth_type: Possible options: 'dynamic', 'continuous',
868
+ 'discrete', 'diagonalCross', 'straightCross',
869
+ 'horizontal', 'vertical', 'curvedCW',
870
+ 'curvedCCW', 'cubicBezier'.
871
+ When using dynamic, the edges will have an
872
+ invisible support node guiding the shape.
873
+ This node is part of the physics simulation.
874
+ Default is set to continous.
875
+
876
+ :type smooth_type: string
877
+ """
878
+ self.options.edges.smooth.enabled = True
879
+ self.options.edges.smooth.type = smooth_type
880
+
881
+ def toggle_hide_edges_on_drag(self, status):
882
+ """
883
+ Displays or hides edges while dragging the network. This makes
884
+ panning of the network easy.
885
+
886
+ :param status: True if edges should be hidden on drag
887
+
888
+ :type status: bool
889
+ """
890
+ self.options.interaction.hideEdgesOnDrag = status
891
+
892
+ def toggle_hide_nodes_on_drag(self, status):
893
+ """
894
+ Displays or hides nodes while dragging the network. This makes
895
+ panning of the network easy.
896
+
897
+ :param status: When set to True, the nodes will hide on drag.
898
+ Default is set to False.
899
+
900
+ :type status: bool
901
+ """
902
+ self.options.interaction.hideNodesOnDrag = status
903
+
904
+ def inherit_edge_colors(self, status):
905
+ """
906
+ Edges take on the color of the node they are coming from.
907
+
908
+ :param status: True if edges should adopt color coming from.
909
+ :type status: bool
910
+ """
911
+ self.options.edges.inherit_colors(status)
912
+
913
+ def show_buttons(self, filter_=None):
914
+ """
915
+ Displays or hides certain widgets to dynamically modify the
916
+ network.
917
+
918
+ Usage:
919
+ >>> g.show_buttons(filter_=['nodes', 'edges', 'physics'])
920
+
921
+ Or to show all options:
922
+ >>> g.show_buttons()
923
+
924
+ :param status: When set to True, the widgets will be shown.
925
+ Default is set to False.
926
+ :param filter_: Only include widgets specified by `filter_`.
927
+ Valid options: True (gives all widgets)
928
+ List of `nodes`, `edges`,
929
+ `layout`, `interaction`,
930
+ `manipulation`, `physics`,
931
+ `selection`, `renderer`.
932
+
933
+ :type status: bool
934
+ :type filter_: bool or list:
935
+ """
936
+ self.conf = True
937
+ self.options.configure = Configure(enabled=True, filter_=filter_)
938
+ self.widget = True
939
+
940
+ def toggle_physics(self, status):
941
+ """
942
+ Toggles physics simulation
943
+
944
+ :param status: When False, nodes are not part of the physics
945
+ simulation. They will not move except for from
946
+ manual dragging.
947
+ Default is set to True.
948
+
949
+ :type status: bool
950
+ """
951
+ self.options.physics.enabled = status
952
+
953
+ def toggle_drag_nodes(self, status):
954
+ """
955
+ Toggles the dragging of the nodes in the network.
956
+
957
+ :param status: When set to True, the nodes can be dragged around
958
+ in the network. Default is set to True.
959
+
960
+ :type status: bool
961
+ """
962
+ self.options.interaction.dragNodes = status
963
+
964
+ def toggle_stabilization(self, status):
965
+ """
966
+ Toggles the stablization of the network.
967
+
968
+ :param status: Default is set to True.
969
+
970
+ :type status: bool
971
+ """
972
+ self.options.physics.toggle_stabilization(status)
973
+
974
+ def set_options(self, options):
975
+ """
976
+ Overrides the default options object passed to the VisJS framework.
977
+ Delegates to the :meth:`options.Options.set` routine.
978
+
979
+ :param options: The string representation of the Javascript-like object
980
+ to be used to override default options.
981
+
982
+ :type options: str
983
+ """
984
+ self.options = self.options.set(options)
pyvis/pyvis/node.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ class Node(object):
2
+ def __init__(self, n_id, shape, label, font_color=False, **opts):
3
+ self.options = opts
4
+ self.options["id"] = n_id
5
+ self.options["label"] = label
6
+ self.options["shape"] = shape
7
+ if font_color:
8
+ self.options["font"] = dict(color=font_color)
pyvis/pyvis/options.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .physics import *
2
+
3
+
4
+ class EdgeOptions(object):
5
+ """
6
+ This is where the construction of the edges' options takes place.
7
+ So far, the edge smoothness can be switched through this object
8
+ as well as the edge color's inheritance.
9
+ """
10
+
11
+ def __init__(self):
12
+ self.smooth = self.Smooth()
13
+ self.color = self.Color()
14
+
15
+ def inherit_colors(self, status):
16
+ """
17
+ Whether or not to inherit colors from the source node.
18
+ If this is set to `from` then the edge will take the color
19
+ of the source node. If it is set to `to` then the color will
20
+ be that of the destination node.
21
+
22
+ .. note:: If set to `True` then the `from` behavior is adopted
23
+ and vice versa.
24
+ """
25
+ self.color.inherit = status
26
+
27
+ def toggle_smoothness(self, smooth_type):
28
+ """
29
+ Change smooth option for edges. When using dynamic, the edges will
30
+ have an invisible support node guiding the shape. This node is part
31
+ of the physics simulation,
32
+
33
+ :param smooth_type: Possible options are dynamic, continuous, discrete,
34
+ diagonalCross, straightCross, horizontal, vertical,
35
+ curvedCW, curvedCCW, cubicBezier
36
+
37
+ :type smooth_type: str
38
+ """
39
+ self.smooth.type = smooth_type
40
+
41
+ def __repr__(self):
42
+ return str(self.__dict__)
43
+
44
+ class Smooth(object):
45
+ """
46
+ When the edges are made to be smooth, the edges are drawn as a
47
+ dynamic quadratic bezier curve. The drawing of these curves
48
+ takes longer than that of the straight curves but it looks better.
49
+ There is a difference between dynamic smooth curves and static
50
+ smooth curves. The dynamic smooth curves have an invisible support
51
+ node that takes part in the physics simulation. If there are a lot
52
+ of edges, another kind of smooth than dynamic would be better for
53
+ performance.
54
+ """
55
+
56
+ def __repr__(self):
57
+ return str(self.__dict__)
58
+
59
+ def __init__(self):
60
+ self.enabled = True
61
+ self.type = "dynamic"
62
+
63
+ class Color(object):
64
+ """
65
+ The color object contains the color information of the edge
66
+ in every situation. When the edge only needs a single color value
67
+ like 'rgb(120,32,14)', '#ffffff' or 'red' can be supplied instead
68
+ of an object.
69
+ """
70
+
71
+ def __repr__(self):
72
+ return str(self.__dict__)
73
+
74
+ def __init__(self):
75
+ self.inherit = True
76
+
77
+
78
+ class Interaction(object):
79
+ """
80
+ Used for all user interaction with the network. Handles mouse
81
+ and touch events as well as the navigation buttons and the popups.
82
+ """
83
+
84
+ def __repr__(self):
85
+ return str(self.__dict__)
86
+
87
+ def __init__(self):
88
+ self.hideEdgesOnDrag = False
89
+ self.hideNodesOnDrag = False
90
+ self.dragNodes = True
91
+
92
+ def __getitem__(self, item):
93
+ return self.__dict__[item]
94
+
95
+
96
+ class Configure(object):
97
+ """
98
+ Handles the HTML part of the canvas and generates
99
+ an interactive option editor with filtering.
100
+ """
101
+
102
+ def __repr__(self):
103
+ return str(self.__dict__)
104
+
105
+ def __init__(self, enabled=False, filter_=None):
106
+ self.enabled = enabled
107
+ if filter_:
108
+ self.filter = filter_
109
+
110
+ def __getitem__(self, item):
111
+ return self.__dict__[item]
112
+
113
+
114
+ class Layout(object):
115
+ """
116
+ Acts as the camera that looks on the canvas.
117
+ Does the animation, zooming and focusing.
118
+ """
119
+
120
+ def __repr__(self):
121
+ return str(self.__dict__)
122
+
123
+ def __init__(self, randomSeed=None, improvedLayout=True):
124
+ if not randomSeed:
125
+ self.randomSeed = 0
126
+ else:
127
+ self.randomSeed = randomSeed
128
+ self.improvedLayout = improvedLayout
129
+ self.hierarchical = self.Hierarchical(enabled=True)
130
+
131
+ def set_separation(self, distance):
132
+ """
133
+ The distance between the different levels.
134
+ """
135
+ self.hierarchical.levelSeparation = distance
136
+
137
+ def set_tree_spacing(self, distance):
138
+ """
139
+ Distance between different trees (independent networks). This is
140
+ only for the initial layout. If you enable physics, the repulsion
141
+ model will denote the distance between the trees.
142
+ """
143
+ self.hierarchical.treeSpacing = distance
144
+
145
+ def set_edge_minimization(self, status):
146
+ """
147
+ Method for reducing whitespace. Can be used alone or together with
148
+ block shifting. Enabling block shifting will usually speed up the
149
+ layout process. Each node will try to move along its free axis to
150
+ reduce the total length of it's edges. This is mainly for the
151
+ initial layout. If you enable physics, they layout will be determined
152
+ by the physics. This will greatly speed up the stabilization time
153
+ """
154
+ self.hierarchical.edgeMinimization = status
155
+
156
+ class Hierarchical(object):
157
+ def __getitem__(self, item):
158
+ return self.__dict__[item]
159
+
160
+ def __init__(
161
+ self,
162
+ enabled=False,
163
+ levelSeparation=150,
164
+ treeSpacing=200,
165
+ blockShifting=True,
166
+ edgeMinimization=True,
167
+ parentCentralization=True,
168
+ sortMethod="hubsize",
169
+ ):
170
+
171
+ self.enabled = enabled
172
+ self.levelSeparation = levelSeparation
173
+ self.treeSpacing = treeSpacing
174
+ self.blockShifting = blockShifting
175
+ self.edgeMinimization = edgeMinimization
176
+ self.parentCentralization = parentCentralization
177
+ self.sortMethod = sortMethod
178
+
179
+
180
+ class Options(object):
181
+ """
182
+ Represents the global options of the network.
183
+ This object consists of indiviual sub-objects
184
+ that map to VisJS's modules of:
185
+ - configure
186
+ - layout
187
+ - interaction
188
+ - physics
189
+ - edges
190
+
191
+ The JSON representation of this object is directly passed
192
+ in to the VisJS framework.
193
+ In the future this can be expanded to completely mimic
194
+ the structure VisJS can expect.
195
+ """
196
+
197
+ def __repr__(self):
198
+ return str(self.__dict__)
199
+
200
+ def __getitem__(self, item):
201
+ return self.__dict__[item]
202
+
203
+ def __init__(self, layout=None):
204
+ if layout:
205
+ self.layout = Layout()
206
+ self.interaction = Interaction()
207
+ self.configure = Configure()
208
+ self.physics = Physics()
209
+ self.edges = EdgeOptions()
210
+
211
+ def set(self, new_options):
212
+ """
213
+ This method should accept a JSON string and replace its internal
214
+ options structure with the given argument after parsing it.
215
+ In practice, this method should be called after using the browser
216
+ to experiment with different physics and layout options, using
217
+ the generated JSON options structure that is spit out from the
218
+ front end to serve as input to this method as a string.
219
+
220
+ :param new_options: The JSON like string of the options that will
221
+ override.
222
+
223
+ :type new_options: str
224
+ """
225
+
226
+ options = new_options.replace("\n", "").replace(" ", "")
227
+ first_bracket = options.find("{")
228
+ options = options[first_bracket:]
229
+ options = json.loads(options)
230
+ return options
231
+
232
+ def to_json(self):
233
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
pyvis/pyvis/physics.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+
4
+ class Physics(object):
5
+
6
+ engine_chosen = False
7
+
8
+ def __getitem__(self, item):
9
+ return self.__dict__[item]
10
+
11
+ def __repr__(self):
12
+ return str(self.__dict__)
13
+
14
+ class barnesHut(object):
15
+ """
16
+ BarnesHut is a quadtree based gravity model.
17
+ This is the fastest, default and recommended.
18
+ """
19
+
20
+ def __init__(self, params):
21
+ self.gravitationalConstant = params["gravity"]
22
+ self.centralGravity = params["central_gravity"]
23
+ self.springLength = params["spring_length"]
24
+ self.springConstant = params["spring_strength"]
25
+ self.damping = params["damping"]
26
+ self.avoidOverlap = params["overlap"]
27
+
28
+ class forceAtlas2Based(object):
29
+ """
30
+ Force Atlas 2 has been develoved by Jacomi et all (2014)
31
+ for use with Gephi. The force Atlas based solver makes use
32
+ of some of the equations provided by them and makes use of
33
+ some of the barnesHut implementation in vis. The Main differences
34
+ are the central gravity model, which is here distance independent,
35
+ and repulsion being linear instead of quadratic. Finally, all node
36
+ masses have a multiplier based on the amount of connected edges
37
+ plus one.
38
+ """
39
+
40
+ def __init__(self, params):
41
+ self.gravitationalConstant = params["gravity"]
42
+ self.centralGravity = params["central_gravity"]
43
+ self.springLength = params["spring_length"]
44
+ self.springConstant = params["spring_strength"]
45
+ self.damping = params["damping"]
46
+ self.avoidOverlap = params["overlap"]
47
+
48
+ class Repulsion(object):
49
+ """
50
+ The repulsion model assumes nodes have a simplified field
51
+ around them. Its force lineraly decreases from 1
52
+ (at 0.5*nodeDistace and smaller) to 0 (at 2*nodeDistance)
53
+ """
54
+
55
+ def __init__(self, params):
56
+ self.nodeDistance = params["node_distance"]
57
+ self.centralGravity = params["central_gravity"]
58
+ self.springLength = params["spring_length"]
59
+ self.springConstant = params["spring_strength"]
60
+ self.damping = params["damping"]
61
+
62
+ class hierarchicalRepulsion(object):
63
+ """
64
+ This model is based on the repulsion solver but the levels
65
+ are taken into accound and the forces
66
+ are normalized.
67
+ """
68
+
69
+ def __init__(self, params):
70
+ self.nodeDistance = params["node_distance"]
71
+ self.centralGravity = params["central_gravity"]
72
+ self.springLength = params["spring_length"]
73
+ self.springConstant = params["spring_strength"]
74
+ self.damping = params["damping"]
75
+
76
+ class Stabilization(object):
77
+ """
78
+ This makes the network stabilized on load using default settings.
79
+ """
80
+
81
+ def __getitem__(self, item):
82
+ return self.__dict__[item]
83
+
84
+ def __init__(self):
85
+ self.enabled = True
86
+ self.iterations = 1000
87
+ self.updateInterval = 50
88
+ self.onlyDynamicEdges = False
89
+ self.fit = True
90
+
91
+ def toggle_stabilization(self, status):
92
+ self.enabled = status
93
+
94
+ def __init__(self):
95
+ self.enabled = True
96
+ self.stabilization = self.Stabilization()
97
+
98
+ def use_barnes_hut(self, params):
99
+ self.barnesHut = self.barnesHut(params)
100
+
101
+ def use_force_atlas_2based(self, params):
102
+ self.forceAtlas2Based = self.forceAtlas2Based(params)
103
+ self.solver = "forceAtlas2Based"
104
+
105
+ def use_repulsion(self, params):
106
+ self.repulsion = self.Repulsion(params)
107
+ self.solver = "repulsion"
108
+
109
+ def use_hrepulsion(self, params):
110
+ self.hierarchicalRepulsion = self.hierarchicalRepulsion(params)
111
+ self.solver = "hierarchicalRepulsion"
112
+
113
+ def toggle_stabilization(self, status):
114
+ self.stabilization.toggle_stabilization(status)
115
+
116
+ def to_json(self):
117
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
pyvis/pyvis/source/conf.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # pyvis documentation build configuration file, created by
4
+ # sphinx-quickstart on Wed Nov 02 20:10:44 2016.
5
+ #
6
+ # This file is execfile()d with the current directory set to its
7
+ # containing dir.
8
+ #
9
+ # Note that not all possible configuration values are present in this
10
+ # autogenerated file.
11
+ #
12
+ # All configuration values have a default; values that are commented out
13
+ # serve to show the default.
14
+
15
+ import os
16
+ import sys
17
+
18
+ import sphinx_rtd_theme
19
+
20
+ # dynamically read the version
21
+ exec(open("../_version.py").read())
22
+
23
+ # If extensions (or modules to document with autodoc) are in another directory,
24
+ # add these directories to sys.path here. If the directory is relative to the
25
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
26
+ sys.path.insert(0, os.path.abspath("../.."))
27
+
28
+ # os.path.abspath('mydir/myfile.txt')
29
+
30
+
31
+ # -- General configuration ------------------------------------------------
32
+
33
+ # If your documentation needs a minimal Sphinx version, state it here.
34
+ # needs_sphinx = '1.0'
35
+
36
+ # Add any Sphinx extension module names here, as strings. They can be
37
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
38
+ # ones.
39
+ extensions = [
40
+ "sphinx.ext.autodoc",
41
+ "sphinx.ext.intersphinx",
42
+ "sphinx.ext.ifconfig",
43
+ "sphinx.ext.viewcode",
44
+ ]
45
+
46
+ # Add any paths that contain templates here, relative to this directory.
47
+ templates_path = ["_templates"]
48
+
49
+ # The suffix(es) of source filenames.
50
+ # You can specify multiple suffix as a list of string:
51
+ # source_suffix = ['.rst', '.md']
52
+ source_suffix = ".rst"
53
+
54
+ # The encoding of source files.
55
+ # source_encoding = 'utf-8-sig'
56
+
57
+ # The master toctree document.
58
+ master_doc = "index"
59
+
60
+ # General information about the project.
61
+ project = "pyvis"
62
+ copyright = "2016-2018, West Health Institute"
63
+ author = "Giancarlo Perrone"
64
+
65
+ # The version info for the project you're documenting, acts as replacement for
66
+ # |version| and |release|, also used in various other places throughout the
67
+ # built documents.
68
+ #
69
+ # The short X.Y version.
70
+ version = __version__
71
+ # The full version, including alpha/beta/rc tags.
72
+ release = "0.1.3.1"
73
+
74
+ # The language for content autogenerated by Sphinx. Refer to documentation
75
+ # for a list of supported languages.
76
+ #
77
+ # This is also used if you do content translation via gettext catalogs.
78
+ # Usually you set "language" from the command line for these cases.
79
+ language = None
80
+
81
+ # There are two options for replacing |today|: either, you set today to some
82
+ # non-false value, then it is used:
83
+ # today = ''
84
+ # Else, today_fmt is used as the format for a strftime call.
85
+ # today_fmt = '%B %d, %Y'
86
+
87
+ # List of patterns, relative to source directory, that match files and
88
+ # directories to ignore when looking for source files.
89
+ # This patterns also effect to html_static_path and html_extra_path
90
+ exclude_patterns = []
91
+
92
+ # The reST default role (used for this markup: `text`) to use for all
93
+ # documents.
94
+ # default_role = None
95
+
96
+ # If true, '()' will be appended to :func: etc. cross-reference text.
97
+ # add_function_parentheses = True
98
+
99
+ # If true, the current module name will be prepended to all description
100
+ # unit titles (such as .. function::).
101
+ # add_module_names = True
102
+
103
+ # If true, sectionauthor and moduleauthor directives will be shown in the
104
+ # output. They are ignored by default.
105
+ # show_authors = False
106
+
107
+ # The name of the Pygments (syntax highlighting) style to use.
108
+ pygments_style = "sphinx"
109
+
110
+ # A list of ignored prefixes for module index sorting.
111
+ # modindex_common_prefix = []
112
+
113
+ # If true, keep warnings as "system message" paragraphs in the built documents.
114
+ # keep_warnings = False
115
+
116
+ # If true, `todo` and `todoList` produce output, else they produce nothing.
117
+ todo_include_todos = False
118
+
119
+
120
+ # -- Options for HTML output ----------------------------------------------
121
+
122
+ # The theme to use for HTML and HTML Help pages. See the documentation for
123
+ # a list of builtin themes.
124
+ html_theme = "sphinx_rtd_theme"
125
+
126
+ # Theme options are theme-specific and customize the look and feel of a theme
127
+ # further. For a list of options available for each theme, see the
128
+ # documentation.
129
+ # html_theme_options = {}
130
+
131
+ # Add any paths that contain custom themes here, relative to this directory.
132
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
133
+
134
+ # The name for this set of Sphinx documents.
135
+ # "<project> v<release> documentation" by default.
136
+ # html_title = u'pyvis v0.0.1'
137
+
138
+ # A shorter title for the navigation bar. Default is the same as html_title.
139
+ # html_short_title = None
140
+
141
+ # The name of an image file (relative to this directory) to place at the top
142
+ # of the sidebar.
143
+ # html_logo = None
144
+
145
+ # The name of an image file (relative to this directory) to use as a favicon of
146
+ # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
147
+ # pixels large.
148
+ # html_favicon = None
149
+
150
+ # Add any paths that contain custom static files (such as style sheets) here,
151
+ # relative to this directory. They are copied after the builtin static files,
152
+ # so a file named "default.css" will overwrite the builtin "default.css".
153
+ html_static_path = ["_static"]
154
+
155
+ # Add any extra paths that contain custom files (such as robots.txt or
156
+ # .htaccess) here, relative to this directory. These files are copied
157
+ # directly to the root of the documentation.
158
+ # html_extra_path = []
159
+
160
+ # If not None, a 'Last updated on:' timestamp is inserted at every page
161
+ # bottom, using the given strftime format.
162
+ # The empty string is equivalent to '%b %d, %Y'.
163
+ # html_last_updated_fmt = None
164
+
165
+ # If true, SmartyPants will be used to convert quotes and dashes to
166
+ # typographically correct entities.
167
+ # html_use_smartypants = True
168
+
169
+ # Custom sidebar templates, maps document names to template names.
170
+ # html_sidebars = {}
171
+
172
+ # Additional templates that should be rendered to pages, maps page names to
173
+ # template names.
174
+ # html_additional_pages = {}
175
+
176
+ # If false, no module index is generated.
177
+ # html_domain_indices = True
178
+
179
+ # If false, no index is generated.
180
+ # html_use_index = True
181
+
182
+ # If true, the index is split into individual pages for each letter.
183
+ # html_split_index = False
184
+
185
+ # If true, links to the reST sources are added to the pages.
186
+ # html_show_sourcelink = True
187
+
188
+ # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
189
+ # html_show_sphinx = True
190
+
191
+ # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
192
+ # html_show_copyright = True
193
+
194
+ # If true, an OpenSearch description file will be output, and all pages will
195
+ # contain a <link> tag referring to it. The value of this option must be the
196
+ # base URL from which the finished HTML is served.
197
+ # html_use_opensearch = ''
198
+
199
+ # This is the file name suffix for HTML files (e.g. ".xhtml").
200
+ # html_file_suffix = None
201
+
202
+ # Language to be used for generating the HTML full-text search index.
203
+ # Sphinx supports the following languages:
204
+ # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
205
+ # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
206
+ # html_search_language = 'en'
207
+
208
+ # A dictionary with options for the search language support, empty by default.
209
+ # 'ja' uses this config value.
210
+ # 'zh' user can custom change `jieba` dictionary path.
211
+ # html_search_options = {'type': 'default'}
212
+
213
+ # The name of a javascript file (relative to the configuration directory) that
214
+ # implements a search results scorer. If empty, the default will be used.
215
+ # html_search_scorer = 'scorer.js'
216
+
217
+ # Output file base name for HTML help builder.
218
+ htmlhelp_basename = "pyvisdoc"
219
+
220
+ # -- Options for LaTeX output ---------------------------------------------
221
+
222
+ latex_elements = {
223
+ # The paper size ('letterpaper' or 'a4paper').
224
+ #'papersize': 'letterpaper',
225
+ # The font size ('10pt', '11pt' or '12pt').
226
+ #'pointsize': '10pt',
227
+ # Additional stuff for the LaTeX preamble.
228
+ #'preamble': '',
229
+ # Latex figure (float) alignment
230
+ #'figure_align': 'htbp',
231
+ }
232
+
233
+ # Grouping the document tree into LaTeX files. List of tuples
234
+ # (source start file, target name, title,
235
+ # author, documentclass [howto, manual, or own class]).
236
+ latex_documents = [
237
+ (
238
+ master_doc,
239
+ "pyvis.tex",
240
+ "pyvis Documentation",
241
+ "Giancarlo Perrone, Shashank Soni",
242
+ "manual",
243
+ ),
244
+ ]
245
+
246
+ # The name of an image file (relative to this directory) to place at the top of
247
+ # the title page.
248
+ # latex_logo = None
249
+
250
+ # For "manual" documents, if this is true, then toplevel headings are parts,
251
+ # not chapters.
252
+ # latex_use_parts = False
253
+
254
+ # If true, show page references after internal links.
255
+ # latex_show_pagerefs = False
256
+
257
+ # If true, show URL addresses after external links.
258
+ # latex_show_urls = False
259
+
260
+ # Documents to append as an appendix to all manuals.
261
+ # latex_appendices = []
262
+
263
+ # If false, no module index is generated.
264
+ # latex_domain_indices = True
265
+
266
+
267
+ # -- Options for manual page output ---------------------------------------
268
+
269
+ # One entry per manual page. List of tuples
270
+ # (source start file, name, description, authors, manual section).
271
+ man_pages = [(master_doc, "pyvis", "pyvis Documentation", [author], 1)]
272
+
273
+ # If true, show URL addresses after external links.
274
+ # man_show_urls = False
275
+
276
+
277
+ # -- Options for Texinfo output -------------------------------------------
278
+
279
+ # Grouping the document tree into Texinfo files. List of tuples
280
+ # (source start file, target name, title, author,
281
+ # dir menu entry, description, category)
282
+ texinfo_documents = [
283
+ (
284
+ master_doc,
285
+ "pyvis",
286
+ "pyvis Documentation",
287
+ author,
288
+ "pyvis",
289
+ "One line description of project.",
290
+ "Miscellaneous",
291
+ ),
292
+ ]
293
+
294
+ # Documents to append as an appendix to all manuals.
295
+ # texinfo_appendices = []
296
+
297
+ # If false, no module index is generated.
298
+ # texinfo_domain_indices = True
299
+
300
+ # How to display URL addresses: 'footnote', 'no', or 'inline'.
301
+ # texinfo_show_urls = 'footnote'
302
+
303
+ # If true, do not generate a @detailmenu in the "Top" node's menu.
304
+ # texinfo_no_detailmenu = False
305
+
306
+
307
+ # Example configuration for intersphinx: refer to the Python standard library.
308
+ intersphinx_mapping = {"https://docs.python.org/": None}
pyvis/pyvis/source/documentation.rst ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ =============
2
+ Documentation
3
+ =============
4
+
5
+ .. automodule:: pyvis.network
6
+ :members:
7
+
8
+ .. automodule:: pyvis.options
9
+ :members:
pyvis/pyvis/source/index.rst ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .. image:: net.png
3
+ :width: 30%
4
+ .. image:: net2.png
5
+ :width: 30%
6
+ .. image:: net3.png
7
+ :width: 30%
8
+
9
+ Interactive network visualizations
10
+ ==================================
11
+ .. image:: tut.gif
12
+
13
+ Contents:
14
+ =========
15
+ .. toctree::
16
+ :maxdepth: 2
17
+
18
+ install
19
+ introduction
20
+ tutorial
21
+ license
22
+ documentation
23
+
24
+
25
+
26
+
27
+ Indices and tables
28
+ ------------------
29
+
30
+ * :ref:`genindex`
31
+ * :ref:`modindex`
32
+ * :ref:`search`
33
+ * :ref:`glossary`
pyvis/pyvis/source/install.rst ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ============
2
+ Installation
3
+ ============
4
+
5
+ ----------------
6
+ Install with pip
7
+ ----------------
8
+
9
+ .. code-block:: bash
10
+
11
+ $ pip install pyvis
12
+
13
+ Or you can download an archive of the project here_. To install, unpack it
14
+ and run the following in the top-level directory:
15
+
16
+ .. code-block:: bash
17
+
18
+ $ python setup.py install
19
+
20
+ .. _here: https://github.com/WestHealth/pyvis/
pyvis/pyvis/source/introduction.rst ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ============
2
+ Introduction
3
+ ============
4
+
5
+ The goal of this project is to build a python based approach to constructing and visualizing
6
+ network graphs in the same space. A pyvis network can be customized on a per node or per edge
7
+ basis. Nodes can be given colors, sizes, labels, and other metadata. Each graph can be interacted
8
+ with, allowing the dragging, hovering, and selection of nodes and edges. Each graph's layout
9
+ algorithm can be tweaked as well to allow experimentation with rendering of larger graphs.
10
+
11
+ Pyvis is built around the amazing VisJS_ library.
12
+
13
+ .. _VisJS: https://visjs.github.io/vis-network/examples/
pyvis/pyvis/source/jup.png ADDED
pyvis/pyvis/source/license.rst ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ============
2
+ License
3
+ ============
4
+
5
+ Pyvis is distributed with the BSD 3 Clause license.
6
+
7
+ Copyright (c) 2018, West Health Institute
8
+ All rights reserved.
9
+
10
+ Redistribution and use in source and binary forms, with or without modification,
11
+ are permitted provided that the following conditions are met:
12
+
13
+ - Redistributions of source code must retain the above copyright notice,
14
+ this list of conditions and the following disclaimer.
15
+
16
+ - Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+
20
+ - Neither the name of West Health Institute nor the names of its contributors may
21
+ be used to endorse or promote products derived from this software without
22
+ specific prior written permission.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
28
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
31
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
pyvis/pyvis/source/net.png ADDED
pyvis/pyvis/source/net2.png ADDED
pyvis/pyvis/source/net3.png ADDED
pyvis/pyvis/source/rednode.png ADDED
pyvis/pyvis/source/tutorial.rst ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ============
3
+ Tutorial
4
+ ============
5
+ The pyvis library is meant for quick generation of visual network graphs
6
+ with minimal python code. It is designed as a wrapper around the popular
7
+ Javascript visJS library found at this link_.
8
+
9
+ .. _link: https://visjs.github.io/vis-network/examples/
10
+
11
+
12
+ Getting started
13
+ ---------------
14
+ All networks must be instantiated as a ``Network`` class instance
15
+
16
+ >>> from pyvis.network import Network
17
+ >>> net = Network()
18
+
19
+ Add nodes to the network
20
+ ------------------------
21
+ >>> net.add_node(1, label="Node 1") # node id = 1 and label = Node 1
22
+ >>> net.add_node(2) # node id and label = 2
23
+
24
+ Here, the first parameter to the add_node method is the desired ID to give the
25
+ Node. This can be a string or a numeric. The label argument is the string that
26
+ will be visibly attached to the node in the final visualization. If no label
27
+ argument is specified then the node id will be used as a label.
28
+
29
+ .. note:: The ``ID`` parameter must be unique
30
+
31
+ You can also add a list of nodes
32
+
33
+ >>> nodes = ["a", "b", "c", "d"]
34
+ >>> net.add_nodes(nodes) # node ids and labels = ["a", "b", "c", "d"]
35
+ >>> net.add_nodes("hello") # node ids and labels = ["h", "e", "l", "o"]
36
+
37
+ .. note:: :meth:`network.Network.add_nodes` accepts any iterable as long as the contents are strings or numerics
38
+
39
+ Node properties
40
+ ---------------
41
+ A call to :meth:`add_node` supports various node properties that can be set
42
+ individually. All of these properties can be found here_, courtesy of VisJS_.
43
+ For the direct Python translation of these attributes, reference the
44
+ :meth:`network.Network.add_node` docs.
45
+
46
+ .. _here: https://visjs.github.io/vis-network/docs/network/nodes.html
47
+ .. _VisJS: https://visjs.github.io/vis-network/docs/network/
48
+
49
+ .. note:: Through no fault of pyvis, some of the attributes in the VisJS_ documentation do not
50
+ work as expected, or at all. Pyvis can translate into the JavaScript
51
+ elements for VisJS_ but after that it's up to VisJS_!
52
+
53
+ Indexing a Node
54
+ ---------------
55
+ Use the :meth:`get_node` method to index a node by its ID:
56
+
57
+ >>> net.add_nodes(["a", "b", "c"])
58
+ >>> net.get_node("c")
59
+ >>> {'id': 'c', 'label': 'c', 'shape': 'dot'}
60
+
61
+
62
+ Adding list of nodes with properties
63
+ ------------------------------------
64
+ When using the :meth:`network.Network.add_nodes` method optional keyword arguments can be
65
+ passed in to add properties to these nodes as well. The valid properties in this case are
66
+
67
+ >>> ['size', 'value', 'title', 'x', 'y', 'label', 'color']
68
+
69
+ Each of these keyword args must be the same length as the nodes parameter to the method.
70
+
71
+ Example:
72
+
73
+ >>> g = Network()
74
+ >>> g.add_nodes([1,2,3], value=[10, 100, 400],
75
+ title=['I am node 1', 'node 2 here', 'and im node 3'],
76
+ x=[21.4, 54.2, 11.2],
77
+ y=[100.2, 23.54, 32.1],
78
+ label=['NODE 1', 'NODE 2', 'NODE 3'],
79
+ color=['#00ff1e', '#162347', '#dd4b39'])
80
+
81
+ .. raw:: html
82
+ :file: mulnodes.html
83
+
84
+ .. note:: If you mouse over each node you will see that the ``title`` of a node
85
+ attribute is responsible for rendering data on mouse hover. You can add ``HTML``
86
+ in your ``title`` string and it will be rendered as such.
87
+
88
+ .. note:: The ``color`` attribute can also be a plain HTML ``color`` like red or blue. You can also
89
+ specify the full ``rgba`` specification if needed. The VisJS_ documentation has more
90
+ details.
91
+
92
+ Detailed optional argument documentation for nodes are in the
93
+ :meth:`network.Network.add_node` method documentation.
94
+
95
+ Edges
96
+ -----
97
+
98
+ Assuming the network's nodes exist, the edges can then be added according to node id's
99
+
100
+ >>> net.add_node(0, label='a')
101
+ >>> net.add_node(1, label='b')
102
+ >>> net.add_edge(0, 1)
103
+
104
+ Edges can contain a ``weight`` attribute as well
105
+
106
+ >>> net.add_edge(0, 1, weight=.87)
107
+
108
+ Edges can be customized and documentation on options can be found at
109
+ :meth:`network.Network.add_edge` method documentation, or by referencing the
110
+ original VisJS edge_ module docs.
111
+
112
+ .. _edge: https://visjs.github.io/vis-network/docs/network/edges.html
113
+
114
+ `Networkx <https://networkx.github.io/>`_ integration
115
+ ------------------------------------------------------
116
+
117
+ An easy way to visualize and construct pyvis networks is to use `Networkx <https://networkx.github.io>`_
118
+ and use pyvis's built-in networkx helper method to translate the graph. Note that the
119
+ Networkx node properties with the same names as those consumed by pyvis (e.g., ``title``) are
120
+ translated directly to the correspondingly-named pyvis node attributes.
121
+
122
+ >>> from pyvis.network import Network
123
+ >>> import networkx as nx
124
+ >>> nx_graph = nx.cycle_graph(10)
125
+ >>> nx_graph.nodes[1]['title'] = 'Number 1'
126
+ >>> nx_graph.nodes[1]['group'] = 1
127
+ >>> nx_graph.nodes[3]['title'] = 'I belong to a different group!'
128
+ >>> nx_graph.nodes[3]['group'] = 10
129
+ >>> nx_graph.add_node(20, size=20, title='couple', group=2)
130
+ >>> nx_graph.add_node(21, size=15, title='couple', group=2)
131
+ >>> nx_graph.add_edge(20, 21, weight=5)
132
+ >>> nx_graph.add_node(25, size=25, label='lonely', title='lonely node', group=3)
133
+ >>> nt = Network('500px', '500px')
134
+ # populates the nodes and edges data structures
135
+ >>> nt.from_nx(nx_graph)
136
+ >>> nt.show('nx.html')
137
+
138
+ .. raw:: html
139
+ :file: nx.html
140
+
141
+
142
+ Visualization
143
+ -------------
144
+
145
+ The displaying of a graph is achieved by a single method call on
146
+ :meth:`network.Network.show()` after the underlying network is constructed.
147
+ The interactive visualization is presented as a static HTML file.
148
+
149
+ >>> net.toggle_physics(True)
150
+ >>> net.show('mygraph.html')
151
+
152
+ .. note:: Triggering the :meth:`toggle_physics` method allows for more fluid graph interactions
153
+
154
+ Example: Visualizing a Game of Thrones character network
155
+ --------------------------------------------------------
156
+
157
+ The following code block is a minimal example of the capabilities of pyvis.
158
+
159
+ .. code-block:: python
160
+
161
+ from pyvis.network import Network
162
+ import pandas as pd
163
+
164
+ got_net = Network(height='750px', width='100%', bgcolor='#222222', font_color='white')
165
+
166
+ # set the physics layout of the network
167
+ got_net.barnes_hut()
168
+ got_data = pd.read_csv('https://www.macalester.edu/~abeverid/data/stormofswords.csv')
169
+
170
+ sources = got_data['Source']
171
+ targets = got_data['Target']
172
+ weights = got_data['Weight']
173
+
174
+ edge_data = zip(sources, targets, weights)
175
+
176
+ for e in edge_data:
177
+ src = e[0]
178
+ dst = e[1]
179
+ w = e[2]
180
+
181
+ got_net.add_node(src, src, title=src)
182
+ got_net.add_node(dst, dst, title=dst)
183
+ got_net.add_edge(src, dst, value=w)
184
+
185
+ neighbor_map = got_net.get_adj_list()
186
+
187
+ # add neighbor data to node hover data
188
+ for node in got_net.nodes:
189
+ node['title'] += ' Neighbors:<br>' + '<br>'.join(neighbor_map[node['id']])
190
+ node['value'] = len(neighbor_map[node['id']])
191
+
192
+ got_net.show('gameofthrones.html')
193
+
194
+
195
+ If you want to try out the above code, the csv data source can be `downloaded <https://www.macalester.edu/~abeverid/data/stormofswords.csv>`_
196
+
197
+ .. note:: The ``title`` attribute of each node is responsible for rendering data on node hover.
198
+
199
+ .. raw:: html
200
+ :file: gameofthrones.html
201
+
202
+ Using the configuration UI to dynamically tweak ``Network`` settings
203
+ ----------------------------------------------------------------
204
+ You also have the option of supplying your visualization with a UI used to
205
+ dynamically alter some of the settings pertaining to your network. This could
206
+ be useful for finding the most optimal parameters to your graph's physics and
207
+ layout function.
208
+
209
+ >>> net.show_buttons(filter_=['physics'])
210
+
211
+ .. image:: buttons.gif
212
+
213
+ .. note:: You can copy/paste the output from the `generate options` button in the above UI
214
+ into :meth:`network.Network.set_options` to finalize your results from experimentation
215
+ with the settings.
216
+
217
+ .. image:: set_options_ex.gif
218
+
219
+ Using pyvis within `Jupyter <https://jupyter.org>`_ notebook
220
+ ------------------------------------------------------------
221
+
222
+ Pyvis supports `Jupyter <https://jupyter.org>`_ notebook embedding through the
223
+ use of the :meth:`network.Network` constructor. The network instance must be
224
+ "prepped" during instantiation by supplying the `notebook=True` kwarg.
225
+ Example:
226
+
227
+ .. image:: jup.png
pyvis/pyvis/tests/__init__.py ADDED
File without changes
pyvis/pyvis/tests/test_graph.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import unittest
3
+
4
+ from ..network import Network
5
+
6
+
7
+ class NodeTestCase(unittest.TestCase):
8
+ def setUp(self):
9
+ self.g = Network()
10
+
11
+ def test_one_node_g(self):
12
+ self.g.add_node(0, 0)
13
+ self.assertEqual(0, self.g.nodes[0]["id"])
14
+ self.assertEqual(0, self.g.nodes[0]["label"])
15
+ self.assertTrue(0 in self.g.get_nodes())
16
+
17
+ def test_one_conn(self):
18
+ self.g.add_nodes([0, 1])
19
+ self.assertTrue(1 in self.g.get_nodes())
20
+ self.assertTrue(0 in self.g.get_nodes())
21
+ self.g.add_edge(0, 1)
22
+ self.assertTrue(
23
+ self.g.get_edges()[0]["from"] == 0 and self.g.get_edges()[0]["to"] == 1
24
+ )
25
+
26
+ def test_no_dup_edges(self):
27
+ self.g.add_nodes([0, 1])
28
+ self.g.add_edge(0, 1)
29
+ self.assertTrue(len(self.g.get_edges()) == 1)
30
+ self.g.add_edge(1, 0)
31
+ self.assertTrue(len(self.g.get_edges()) == 1)
32
+
33
+ def test_no_dup_nodes(self):
34
+ self.g.add_node(100, 100)
35
+ self.g.add_node(100, 100)
36
+ self.assertTrue(len(self.g.nodes) == 1)
37
+ self.g.add_node(100, "n101")
38
+ self.assertTrue(len(self.g.nodes) == 1)
39
+
40
+ def test_node_labels(self):
41
+ self.g.add_node(1, "n2")
42
+ self.g.add_node(2, "n3")
43
+ self.g.add_node(3, "n4")
44
+ self.assertEqual(
45
+ set(map(lambda s: s["label"], self.g.nodes)), set(["n2", "n3", "n4"])
46
+ )
47
+
48
+ def test_node_titles(self):
49
+ self.g.add_nodes(range(10))
50
+
51
+ for n in self.g.nodes:
52
+ n["title"] = "node #" + str(n["id"])
53
+
54
+ ref = ["node #" + str(i) for i in range(10)]
55
+ self.assertEqual(set(map(lambda s: s["title"], self.g.nodes)), set(ref))
56
+
57
+ def test_node_sizes(self):
58
+ self.g.add_nodes(range(10))
59
+
60
+ for n in self.g.nodes:
61
+ n["size"] = n["id"] * 2
62
+
63
+ ref = [i * 2 for i in range(10)]
64
+ self.assertEqual(set(map(lambda s: s["size"], self.g.nodes)), set(ref))
65
+
66
+ def test_adding_nodes(self):
67
+ g = self.g
68
+ g.add_nodes(range(5))
69
+ self.assertTrue(g.get_nodes(), range(5))
70
+
71
+ def test_adding_nodes_with_props(self):
72
+ g = self.g
73
+ g.add_nodes(
74
+ range(4),
75
+ size=range(4),
76
+ color=range(4),
77
+ title=range(4),
78
+ label=range(4),
79
+ x=range(4),
80
+ y=range(4),
81
+ )
82
+ for i, n in enumerate(g.nodes):
83
+ self.assertEqual(n["size"], i)
84
+ self.assertEqual(n["color"], i)
85
+ self.assertEqual(n["title"], i)
86
+ self.assertEqual(n["label"], i)
87
+ self.assertEqual(n["x"], i)
88
+ self.assertEqual(n["y"], i)
89
+
90
+ def test_labels(self):
91
+ g = self.g
92
+ g.add_node(0)
93
+ self.assertTrue(g.nodes[0]["label"] == 0)
94
+ g.add_node(50, label="NODE 50")
95
+ self.assertTrue(g.nodes[1]["label"] == "NODE 50")
96
+
97
+ def test_nodes_with_stringids(self):
98
+ g = self.g
99
+ g.add_nodes(["n1", "n2", "n3"])
100
+ self.assertTrue(g.node_ids == ["n1", "n2", "n3"])
101
+
102
+ def test_adj_list(self):
103
+ g = self.g
104
+ g.add_nodes(range(4))
105
+ g.add_edges([(0, 1, 1), (0, 2, 1), (0, 3, 1), (1, 3, 1)])
106
+ alist = g.get_adj_list()
107
+ self.assertEqual(len(alist), g.num_nodes())
108
+ self.assertEqual(alist[0], set([1, 2, 3]))
109
+ self.assertEqual(alist[1], set([0, 3]))
110
+
111
+ def test_neighbors(self):
112
+ g = self.g
113
+ g.add_nodes(range(5))
114
+ g.add_edge(0, 1)
115
+ g.add_edge(0, 2)
116
+ g.add_edge(1, 3)
117
+ g.add_edge(1, 4)
118
+ self.assertEqual(g.neighbors(0), set([1, 2]))
119
+
120
+ def test_length(self):
121
+ g = self.g
122
+ g.add_node(0)
123
+ self.assertEqual(g.num_nodes(), 1)
124
+
125
+ def test_get_network_data(self):
126
+ self.assertEqual(len(self.g.get_network_data()), 6)
127
+
128
+
129
+ class EdgeTestCase(unittest.TestCase):
130
+ def setUp(self):
131
+ self.g = Network()
132
+ self.g.add_nodes([0, 1, 2, 3])
133
+
134
+ def test_non_existent_edge(self):
135
+ self.assertRaises(AssertionError, self.g.add_edge, 5, 1)
136
+ self.assertRaises(AssertionError, self.g.add_edge, "node1", "node2")
137
+
138
+ def test_no_edge_length(self):
139
+ self.assertTrue(self.g.num_nodes() == 4)
140
+ self.assertTrue(self.g.num_edges() == 0)
141
+
142
+ def test_add_one_edge(self):
143
+ self.g.add_edge(0, 1)
144
+ self.assertTrue(self.g.num_edges() == 1)
145
+ self.assertTrue({"from": 0, "to": 1} in self.g.edges)
146
+
147
+ def test_add_two_edges_no_dups(self):
148
+ self.g.add_edge(0, 1)
149
+ self.g.add_edge(0, 1)
150
+ self.assertTrue(self.g.num_edges() == 1)
151
+ self.g.add_edge(1, 2)
152
+ self.assertTrue(self.g.num_edges() == 2)
153
+ self.assertEqual([{"from": 0, "to": 1}, {"from": 1, "to": 2}], self.g.edges)
154
+
155
+ def test_add_edges_no_weights(self):
156
+ self.g.add_edges([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])
157
+ self.assertEqual(self.g.num_edges(), 6)
158
+ self.assertEqual(self.g.neighbors(0), set([1, 2, 3]))
159
+ self.assertEqual(self.g.neighbors(1), set([0, 2, 3]))
160
+ self.assertEqual(self.g.neighbors(2), set([0, 1, 3]))
161
+ self.assertEqual(self.g.neighbors(3), set([0, 1, 2]))
162
+ self.assertTrue("weight" not in [es for es in self.g.edges])
163
+
164
+ def test_add_edges_weights(self):
165
+ self.g.add_edges(
166
+ [(0, 1, 1), (0, 2, 2), (0, 3, 3), (1, 2, 4), (1, 3, 5), (2, 3, 6)]
167
+ )
168
+ self.assertEqual(self.g.num_edges(), 6)
169
+ self.assertEqual(self.g.neighbors(0), set([1, 2, 3]))
170
+ self.assertEqual(self.g.neighbors(1), set([0, 2, 3]))
171
+ self.assertEqual(self.g.neighbors(2), set([0, 1, 3]))
172
+ self.assertEqual(self.g.neighbors(3), set([0, 1, 2]))
173
+ for edges in self.g.edges:
174
+ self.assertTrue("width" in edges)
175
+ self.assertEqual(
176
+ list([1, 2, 3, 4, 5, 6]), list(map(lambda x: x["width"], self.g.edges))
177
+ )
178
+
179
+ def test_add_edges_mixed_weights(self):
180
+ self.g.add_edges([(0, 1, 1), (0, 2), (0, 3, 3), (1, 2), (1, 3, 5), (2, 3)])
181
+ self.assertEqual(self.g.num_edges(), 6)
182
+ self.assertEqual(self.g.neighbors(0), set([1, 2, 3]))
183
+ self.assertEqual(self.g.neighbors(1), set([0, 2, 3]))
184
+ self.assertEqual(self.g.neighbors(2), set([0, 1, 3]))
185
+ self.assertEqual(self.g.neighbors(3), set([0, 1, 2]))
186
+ self.assertEqual(
187
+ list([1, None, 3, None, 5, None]),
188
+ list(map(lambda x: x.get("width", None), self.g.edges)),
189
+ )
190
+
191
+ def test_add_edge_directed(self):
192
+ self.g.directed = True
193
+ self.g.add_edge(0, 1)
194
+ self.assertTrue(self.g.edges)
195
+ for e in self.g.edges:
196
+ self.assertTrue(e["arrows"] == "to")
197
+
198
+
199
+ class UtilsTestCase(unittest.TestCase):
200
+ def setUp(self):
201
+ self.g = Network()
202
+ self.g.add_nodes([0, 1, 2, 3])
203
+
204
+ def test_html_naming(self):
205
+ self.assertRaises(AssertionError, self.g.write_html, "4nodes.htl")
206
+ self.assertRaises(AssertionError, self.g.write_html, "4nodes.hltm")
207
+ self.assertRaises(AssertionError, self.g.write_html, "4nodes. htl")
208
+ self.g.write_html("4nodes.html")
209
+ self.assertTrue("4nodes.html" in os.listdir("."))
210
+ os.remove("4nodes.html")
211
+
212
+
213
+ class PhysicsTestCase(unittest.TestCase):
214
+ def setUp(self):
215
+ self.g = Network()
216
+ self.g.add_nodes([0, 1, 2, 3])
217
+
218
+ def test_barnes_hut(self):
219
+ self.g.barnes_hut()
220
+ self.assertTrue("barnesHut" in vars(self.g.options.physics))
221
+
222
+ def test_repulsion(self):
223
+ self.g.repulsion()
224
+ self.assertTrue("repulsion" in vars(self.g.options.physics))
225
+
226
+ def test_hrepulsion(self):
227
+ self.g.hrepulsion()
228
+ self.assertTrue("hierarchicalRepulsion" in vars(self.g.options.physics))
229
+
230
+ def test_force_atlas_2based(self):
231
+ self.g.force_atlas_2based()
232
+ self.assertTrue("forceAtlas2Based" in vars(self.g.options.physics))
233
+
234
+ def test_toggle_physics(self):
235
+ self.assertTrue(self.g.options.physics["enabled"])
236
+ self.g.toggle_physics(False)
237
+ self.assertFalse(self.g.options.physics["enabled"])
238
+
239
+ def test_stabilization(self):
240
+ self.g.toggle_stabilization(True)
241
+ self.assertTrue(self.g.options.physics.stabilization["enabled"])
242
+ self.g.toggle_stabilization(False)
243
+ self.assertFalse(self.g.options.physics.stabilization["enabled"])
244
+
245
+
246
+ class InteractionTestCase(unittest.TestCase):
247
+ def setUp(self):
248
+ self.g = Network()
249
+ self.g.add_nodes([0, 1, 2, 3])
250
+
251
+ def test_toggle_drag_nodes(self):
252
+ self.assertTrue(self.g.options.interaction["dragNodes"])
253
+ self.g.toggle_drag_nodes(False)
254
+ self.assertFalse(self.g.options.interaction["dragNodes"])
255
+
256
+ def test_toggle_hide_edges_on_drag(self):
257
+ self.assertFalse(self.g.options.interaction["hideEdgesOnDrag"])
258
+ self.g.toggle_hide_edges_on_drag(True)
259
+ self.assertTrue(self.g.options.interaction["hideEdgesOnDrag"])
260
+
261
+ def test_toggle_hide_nodes_on_drag(self):
262
+ self.assertFalse(self.g.options.interaction["hideNodesOnDrag"])
263
+ self.g.toggle_hide_nodes_on_drag(True)
264
+ self.assertTrue(self.g.options.interaction["hideNodesOnDrag"])
265
+
266
+
267
+ class ConfigureTestCase(unittest.TestCase):
268
+ def setUp(self):
269
+ self.g = Network()
270
+ self.g.add_nodes([0, 1, 2, 3])
271
+
272
+ def test_show_buttons(self):
273
+ self.assertFalse(self.g.options.configure["enabled"])
274
+ self.g.show_buttons(True)
275
+ self.assertTrue(self.g.options.configure["enabled"])
276
+
277
+
278
+ class EdgeOptionsTestCase(unittest.TestCase):
279
+ def setUp(self):
280
+ self.g = Network()
281
+ self.g.add_nodes([0, 1, 2, 3])
282
+
283
+ def test_set_edge_smooth(self):
284
+ self.assertEqual(self.g.options.edges.smooth.type, "dynamic")
285
+ self.g.set_edge_smooth("continuous")
286
+ self.assertEqual(self.g.options.edges.smooth.type, "continuous")
287
+
288
+ def test_inherit_colors(self):
289
+ self.assertTrue(self.g.options.edges.color.inherit)
290
+ self.g.inherit_edge_colors(False)
291
+ self.assertFalse(self.g.options.edges.color.inherit)
292
+
293
+
294
+ class LayoutTestCase(unittest.TestCase):
295
+ def setUp(self):
296
+ self.g = Network(layout=True)
297
+
298
+ def test_can_enable_init(self):
299
+ self.assertTrue(self.g.options["layout"])
300
+
301
+ def test_layout_disabled(self):
302
+ self.g = Network()
303
+ self.assertRaises(KeyError, lambda: self.g.options["layout"])
304
+
305
+ def test_levelSeparation(self):
306
+ self.assertTrue(self.g.options.layout.hierarchical.levelSeparation)
307
+
308
+ def test_treeSpacing(self):
309
+ self.assertTrue(self.g.options.layout.hierarchical.treeSpacing)
310
+
311
+ def test_blockShifting(self):
312
+ self.assertTrue(self.g.options.layout.hierarchical.blockShifting)
313
+
314
+ def test_edgeMinimization(self):
315
+ self.assertTrue(self.g.options.layout.hierarchical.edgeMinimization)
316
+
317
+ def test_parentCentralization(self):
318
+ self.assertTrue(self.g.options.layout.hierarchical.parentCentralization)
319
+
320
+ def test_sortMethod(self):
321
+ self.assertTrue(self.g.options.layout.hierarchical.sortMethod)
322
+
323
+ def test_set_edge_minimization(self):
324
+ self.g.options.layout.set_separation(10)
325
+ self.assertTrue(self.g.options.layout.hierarchical.levelSeparation == 10)
326
+
327
+ def test_set_tree_spacing(self):
328
+ self.g.options.layout.set_tree_spacing(10)
329
+ self.assertTrue(self.g.options.layout.hierarchical.treeSpacing == 10)
330
+
331
+ def test_set_edge_minimization(self):
332
+ self.g.options.layout.set_edge_minimization(True)
333
+ self.assertTrue(self.g.options.layout.hierarchical.edgeMinimization == True)
334
+ self.g.options.layout.set_edge_minimization(False)
335
+ self.assertTrue(self.g.options.layout.hierarchical.edgeMinimization == False)
pyvis/pyvis/tests/test_me.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ from ..network import Network
4
+
5
+
6
+ def test_canvas_size():
7
+ """
8
+ Test the canvas size
9
+ """
10
+ net = Network(500, 500)
11
+ assert net.width == 500 and net.height == 500
12
+
13
+
14
+ def test_add_node():
15
+ """
16
+ Test adding a node to the network.
17
+ """
18
+ net = Network()
19
+
20
+ net.add_node(0, "Test")
21
+
22
+ assert "Test" in net.nodes[0].values()
23
+
24
+
25
+ def test_add_ten_nodes():
26
+ """
27
+ Test adding multiple nodes to this network
28
+ """
29
+ net = Network()
30
+
31
+ for i in range(10):
32
+ net.add_node(i, "Test " + str(i))
33
+
34
+ assert len(net.nodes) == 10
35
+
36
+
37
+ def test_add_nodes_with_options():
38
+ """
39
+ Test adding nodes with different options
40
+ """
41
+ net = Network()
42
+
43
+ sizes = [10, 20, 30]
44
+
45
+ net.add_node(0, "Node 0", color="green", size=10)
46
+ net.add_node(1, "Node 1", color="blue", size=20)
47
+ net.add_node(2, "Node 2", color="yellow", size=30)
48
+
49
+ assert (sizes[node["id"]] == node["size"] for node in net.nodes)
50
+
51
+
52
+ def test_add_edge():
53
+ """
54
+ Test adding an edge between nodes
55
+ """
56
+
57
+ net = Network()
58
+
59
+ for i in range(10):
60
+ net.add_node(i, "Node " + str(i))
61
+
62
+ net.add_edge(0, 1)
63
+ net.add_edge(0, 2)
64
+ net.add_edge(0, 3)
65
+ net.add_edge(0, 4)
66
+ net.add_edge(0, 5)
67
+ net.add_edge(0, 6)
68
+ net.add_edge(0, 7)
69
+ net.add_edge(0, 8)
70
+ net.add_edge(0, 9)
71
+
72
+ assert net.get_adj_list()[0] == set([2, 1, 3, 4, 5, 6, 7, 8, 9])
73
+
74
+
75
+ def test_add_numpy_nodes():
76
+ """
77
+ Test adding numpy array nodes since these
78
+ nodes will have specific numpy types
79
+ """
80
+ arrayNodes = np.array([1, 2, 3, 4])
81
+ g = Network()
82
+ g.add_nodes(np.array([1, 2, 3, 4]))
83
+ assert g.get_nodes() == [1, 2, 3, 4]
pyvis/pyvis/utils.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utility and helper functions for use in pyvis
2
+
3
+
4
+ def check_html(name):
5
+ """
6
+ Given a name of graph to save or write, check if it is of valid syntax
7
+
8
+ :param: name: the name to check
9
+ :type name: str
10
+ """
11
+ assert len(name.split(".")) >= 2, "invalid file type for %s" % name
12
+ assert name.split(".")[-1] == "html", "%s is not a valid html file" % name
pyvis/setup.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import find_packages, setup
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ exec(open("pyvis/_version.py").read())
7
+ setup(
8
+ name="pyvis",
9
+ version=__version__,
10
+ description="A Python network graph visualization library",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ url="https://github.com/WestHealth/pyvis",
14
+ author="Jose Unpingco",
15
+ author_email="datascience@westhealth.org",
16
+ license="BSD",
17
+ packages=find_packages(),
18
+ include_package_data=True,
19
+ install_requires=[
20
+ "jinja2 >= 2.9.6",
21
+ "networkx >= 1.11",
22
+ "ipython >= 5.3.0",
23
+ "jsonpickle >= 1.4.1",
24
+ ],
25
+ python_requires=">3.6",
26
+ )