├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── plot_01.png ├── plot_02.png ├── plot_03.png └── plot_04.png ├── examples ├── README.txt ├── data │ ├── chicken_in.png │ └── gentoo.txt ├── ex_animation.py ├── ex_chicken.py ├── ex_cnet.py ├── ex_gentoo.py ├── ex_igraph.py ├── ex_list.py ├── ex_networkx.py └── ex_pathpy.py ├── network2tikz ├── __about__.py ├── __init__.py ├── canvas.py ├── drawing.py ├── exceptions.py ├── layout.py ├── plot.py └── units.py ├── setup.cfg ├── setup.py └── tests ├── test_cnet.py ├── test_igraph.py ├── test_layout.py ├── test_list.py ├── test_network2tikz.py ├── test_networkx.py ├── test_overlay.py └── test_pathpy.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # directories 107 | log/ 108 | .pytest_cache/ 109 | generated/ 110 | auto_examples/ 111 | tikz-network.sty 112 | /tests/tikz-network.sty 113 | /examples/tikz-network.sty 114 | /tests/default_network.pdf 115 | /tests/network.pdf 116 | /tests/network_edges.csv 117 | /tests/network_nodes.csv 118 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=$(shell python3 -c "import network2tikz; print(network2tikz.__version__)") 2 | 3 | default: 4 | @echo "\"make publish\"?" 5 | 6 | tag: 7 | # Make sure we're on the master branch 8 | @if [ "$(shell git rev-parse --abbrev-ref HEAD)" != "master" ]; then exit 1; fi 9 | @echo "Tagging v$(VERSION)..." 10 | git tag v$(VERSION) 11 | git push --tags 12 | 13 | upload: # setup.py 14 | @if [ "$(shell git rev-parse --abbrev-ref HEAD)" != "master" ]; then exit 1; fi 15 | rm -f dist/* 16 | python setup.py sdist 17 | python setup.py bdist_wheel --universal 18 | twine upload dist/* 19 | 20 | publish: tag upload 21 | 22 | clean: 23 | @find . | grep -E "(__pycache__|\.pyc|\.pyo$\)" | xargs rm -rf 24 | 25 | lint: 26 | pylint setup.py network2tikz/ test/*.py 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # network2tikz 2 | 3 | | Module: | network2tikz | 4 | |----------|-----------------------------------------| 5 | | Date: | 07 November 2018 | 6 | | Authors: | Jürgen Hackl | 7 | | Contact: | [hackl.j@gmx.at](mailto:hackl.j@gmx.at) | 8 | | License: | GNU GPLv3 | 9 | | Version: | 0.1.8 | 10 | | | | 11 | 12 | This is `network2tikz`, a Python tool for converting network 13 | visualizations into [TikZ](https://www.ctan.org/pkg/pgf) 14 | ([tikz-network](https://github.com/hackl/tikz-network)) 15 | figures, for native inclusion into your LaTeX documents. 16 | 17 | 18 | `network2tikz` works with Python 3 and supports (currently) the 19 | following Python network modules: 20 | 21 | - [cnet](https://github.com/hackl/cnet) 22 | - [python-igraph](http://igraph.org/python/) 23 | - [networkx](https://networkx.github.io/) 24 | - [pathpy](https://github.com/IngoScholtes/pathpy) 25 | - default node/edge lists 26 | 27 | The output of `network2tikz` is 28 | in [tikz-network](https://github.com/hackl/tikz-network), a LaTeX 29 | library that sits on top of [TikZ](https://www.ctan.org/pkg/pgf), 30 | which allows to visualize and modify the network plot for your 31 | specific needs and publications. 32 | 33 | Because you are not only getting an image of your network, but also 34 | the LaTeX source file, you can easily post-process the figures 35 | (e.g. adding drawings, texts, equations,...). 36 | 37 | Since *a picture is worth a thousand words* a small example: 38 | 39 | ```python 40 | nodes = ['a','b','c','d'] 41 | edges = [('a','b'), ('a','c'), ('c','d'),('d','b')] 42 | gender = ['f', 'm', 'f', 'm'] 43 | colors = {'m': 'blue', 'f': 'red'} 44 | 45 | style = {} 46 | style['node_label'] = ['Alice', 'Bob', 'Claire', 'Dennis'] 47 | style['node_color'] = [colors[g] for g in gender] 48 | style['node_opacity'] = .5 49 | style['edge_curved'] = .1 50 | 51 | from network2tikz import plot 52 | plot((nodes,edges),'network.tex',**style) 53 | ``` 54 | (see above) gives 55 | ```latex 56 | \documentclass{standalone} 57 | \usepackage{tikz-network} 58 | \begin{document} 59 | \begin{tikzpicture} 60 | \clip (0,0) rectangle (6,6); 61 | \Vertex[x=0.785,y=2.375,color=red,opacity=0.5,label=Alice]{a} 62 | \Vertex[x=5.215,y=5.650,color=blue,opacity=0.5,label=Bob]{b} 63 | \Vertex[x=3.819,y=0.350,color=red,opacity=0.5,label=Claire]{c} 64 | \Vertex[x=4.654,y=2.051,color=blue,opacity=0.5,label=Dennis]{d} 65 | \Edge[,bend=-8.531](a)(c) 66 | \Edge[,bend=-8.531](c)(d) 67 | \Edge[,bend=-8.531](d)(b) 68 | \Edge[,bend=-8.531](a)(b) 69 | \end{tikzpicture} 70 | \end{document} 71 | ``` 72 | and looks like 73 | 74 | example 75 | 76 | Tweaking the plot is straightforward and can be done as part of your 77 | LaTeX workflow. 78 | [The tikz-network manual](https://github.com/hackl/tikz-network/blob/master/manual.pdf) 79 | contains multiple examples of how to make your plot look even better. 80 | 81 | ## Installation 82 | 83 | `network2tikz` is [available from the Python Package Index](https://pypi.org/project/network2tikz/), so simply type 84 | ``` 85 | pip install -U network2tikz 86 | ``` 87 | to install/update. 88 | 89 | ## Usage 90 | 91 | 1. Generate, manipulation, and study of the structure, dynamics, and 92 | functions of your complex networks as usual, with your preferred 93 | python module. 94 | 95 | 2. Instead of the default plot functions (e.g. `igraph.plot()` or 96 | `networkx.draw()`) invoke `network2tikz` by 97 | ```python 98 | plot(G,'mytikz.tex') 99 | ``` 100 | to store your network visualisation as the TikZ file 101 | `mytikz.tex`. Load the module with: 102 | ```python 103 | from network2tikz import plot 104 | ``` 105 | **Advanced usage**: 106 | Of course, you always can improve your plot by manipulating the 107 | generated LaTeX file, but why not do it directly in Python? To do 108 | so, all visualization options available 109 | in [tikz-network](https://github.com/hackl/tikz-network) are also 110 | implemented in `network2tikz`. The appearance of the plot can be 111 | modified by keyword arguments (for a detailed explanation, please 112 | see below). 113 | ```python 114 | my_style = {} 115 | plot(G,'mytikz.tex',**my_style) 116 | ``` 117 | The arguments follow the options available in 118 | the [tikz-network](https://github.com/hackl/tikz-network) library 119 | and are also explained in 120 | the 121 | [tikz-network manual](https://github.com/hackl/tikz-network/blob/master/manual.pdf). 122 | 123 | Additionally, if you are more interested in the final output and 124 | not only the `.tex` file, used 125 | ```python 126 | plot(G,'mypdf.pdf') 127 | ``` 128 | to save your plot as a pdf, or 129 | ```python 130 | plot(G) 131 | ``` 132 | to create a temporal plot and directly show the result, 133 | i.e. similar to the matplotlib function `show()`. Finally, you can 134 | also create a node and edge list, which can be read and easily 135 | modified (in a post-processing step) 136 | with [tikz-network](https://github.com/hackl/tikz-network): 137 | ```python 138 | plot(G,'mycsv.csv') 139 | ``` 140 | 3. *Note:* 141 | > In order to compile the plot, make sure you have 142 | > installed [tikz-network](https://github.com/hackl/tikz-network)! 143 | --- 144 | 145 | 4. Compile the figure or add the contents of `mytikz.tex` into your 146 | LaTeX source code. With the option `standalone=false` only the TikZ 147 | figure will be saved, which can then be easily included in your 148 | LaTeX document via `\input{/path/to/mytikz.tex}`. 149 | 150 | ## Simple example 151 | 152 | For illustration purpose, a similar network as in 153 | the 154 | [python-igraph tutorial](http://igraph.org/python/doc/tutorial/tutorial.html) is 155 | used. If you are using another Python network module, and like to 156 | follow this example, please have a look at 157 | the 158 | [provided examples](https://github.com/hackl/network2tikz/tree/master/examples). 159 | 160 | 161 | Create network object and add some edges. 162 | 163 | ```python 164 | import igraph 165 | from network2tikz import plot 166 | 167 | net = igraph.Graph([(0,1), (0,2), (2,3), (3,4), (4,2), (2,5), (5,0), (6,3), 168 | (5,6), (6,6)],directed=True) 169 | ``` 170 | 171 | Adding node and edge properties. 172 | 173 | ```python 174 | net.vs["name"] = ["Alice", "Bob", "Claire", "Dennis", "Esther", "Frank", "George"] 175 | net.vs["age"] = [25, 31, 18, 47, 22, 23, 50] 176 | net.vs["gender"] = ["f", "m", "f", "m", "f", "m", "m"] 177 | net.es["is_formal"] = [False, False, True, True, True, False, True, False, 178 | False, False] 179 | ``` 180 | 181 | Already now the network can be plotted. 182 | 183 | ```python 184 | plot(net) 185 | ``` 186 | example 187 | 188 | Per default, the node positions are assigned uniform random. In order 189 | to create a layout, the layout methods of the network packages can be 190 | used. Or the position of the nodes can be directly assigned, in form 191 | of a dictionary, where the key is the node id and the value is a tuple 192 | of the node position in x and y. 193 | 194 | 195 | ```python 196 | layout = {0: (4.3191, -3.5352), 1: (0.5292, -0.5292), 197 | 2: (8.6559, -3.8008), 3: (12.4117, -7.5239), 198 | 4: (12.7, -1.7069), 5: (6.0022, -9.0323), 199 | 6: (9.7608, -12.7)} 200 | plot(net,layout=layout) 201 | ``` 202 | 203 | This should open an external pdf viewer showing a visual 204 | representation of the network, something like the one on the following 205 | figure: 206 | 207 | example 208 | 209 | We can simply re-using the previous layout object here, but we also 210 | specified that we need a bigger plot (8 x 8 cm) and a larger margin 211 | around the graph to fit the self loop and potential labels (1 cm). 212 | 213 | *Note:* 214 | > Per default, all size values are based on `cm`, and all line widths 215 | > are defined in `pt` units. With the general option `units` this can 216 | > be changed, see below. 217 | --- 218 | 219 | ```python 220 | plot(net, layout=layout, canvas=(8,8), margin=1) 221 | ``` 222 | example 223 | 224 | *Note:* 225 | > Instead of the command `margins` the command `margin` can be 226 | > used. Also instead of `canvas`, `figure_size` or `bbox` can be 227 | > used. For more information see table below. 228 | --- 229 | 230 | In to keep the properties of the visual representation of your network 231 | separate from the network itself. You can simply set up a Python 232 | dictionary containing the keyword arguments you would pass to `plot` 233 | and then use the double asterisk (`**`) operator to pass your specific 234 | styling attributes to `plot`: 235 | 236 | ```python 237 | color_dict = {'m': 'blue', 'f': 'red'} 238 | visual_style = {} 239 | ``` 240 | 241 | Node options 242 | 243 | ```python 244 | visual_style['vertex_size'] = .5 245 | visual_style['vertex_color'] = [color_dict[g] for g in net.vs['gender']] 246 | visual_style['vertex_opacity'] = .7 247 | visual_style['vertex_label'] = net.vs['name'] 248 | visual_style['vertex_label_position'] = 'below' 249 | ``` 250 | 251 | Edge options 252 | 253 | ```python 254 | visual_style['edge_width'] = [1 + 2 * int(f) for f in net.es('is_formal')] 255 | visual_style['edge_curved'] = 0.1 256 | ``` 257 | General options and plot command. 258 | 259 | ```python 260 | visual_style['layout'] = layout 261 | visual_style['canvas'] = (8,8) 262 | visual_style['margin'] = 1 263 | 264 | plot(net,**visual_style) 265 | ``` 266 | 267 | example 268 | 269 | Beside showing the network, we can also generate the latex source 270 | file, which can be used and modified later on. This is done by adding 271 | the output file name with the ending `'.tex'` 272 | 273 | ```python 274 | plot(net,'network.tex',**visual_style) 275 | ``` 276 | ```latex 277 | \documentclass{standalone} 278 | \usepackage{tikz-network} 279 | \begin{document} 280 | \begin{tikzpicture} 281 | \clip (0,0) rectangle (8.0,8.0); 282 | \Vertex[x=2.868,y=5.518,size=0.5,color=red,opacity=0.7,label=Alice,position=below]{a} 283 | \Vertex[x=1.000,y=7.000,size=0.5,color=blue,opacity=0.7,label=Bob,position=below]{b} 284 | \Vertex[x=5.006,y=5.387,size=0.5,color=red,opacity=0.7,label=Claire,position=below]{c} 285 | \Vertex[x=6.858,y=3.552,size=0.5,color=blue,opacity=0.7,label=Dennis,position=below]{d} 286 | \Vertex[x=7.000,y=6.419,size=0.5,color=red,opacity=0.7,label=Esther,position=below]{e} 287 | \Vertex[x=3.698,y=2.808,size=0.5,color=blue,opacity=0.7,label=Frank,position=below]{f} 288 | \Vertex[x=5.551,y=1.000,size=0.5,color=blue,opacity=0.7,label=George,position=below]{g} 289 | \Edge[,lw=1.0,bend=-8.531,Direct](a)(b) 290 | \Edge[,lw=1.0,bend=-8.531,Direct](a)(c) 291 | \Edge[,lw=3.0,bend=-8.531,Direct](c)(d) 292 | \Edge[,lw=3.0,bend=-8.531,Direct](d)(e) 293 | \Edge[,lw=3.0,bend=-8.531,Direct](e)(c) 294 | \Edge[,lw=1.0,bend=-8.531,Direct](c)(f) 295 | \Edge[,lw=3.0,bend=-8.531,Direct](f)(a) 296 | \Edge[,lw=1.0,bend=-8.531,Direct](f)(g) 297 | \Edge[,lw=1.0,bend=-8.531,Direct](g)(g) 298 | \Edge[,lw=1.0,bend=-8.531,Direct](g)(d) 299 | \end{tikzpicture} 300 | \end{document} 301 | ``` 302 | Instead of the tex file, a node and edge list can be generates, which 303 | can also be used with the tikz-network library. 304 | 305 | ```python 306 | plot(net,'network.csv',**visual_style) 307 | ``` 308 | The node list `network_nodes.csv`. 309 | ```text 310 | id,x,y,size,color,opacity,label,position 311 | a,2.868,5.518,0.5,red,0.7,Alice,below 312 | b,1.000,7.000,0.5,blue,0.7,Bob,below 313 | c,5.006,5.387,0.5,red,0.7,Claire,below 314 | d,6.858,3.552,0.5,blue,0.7,Dennis,below 315 | e,7.000,6.419,0.5,red,0.7,Esther,below 316 | f,3.698,2.808,0.5,blue,0.7,Frank,below 317 | g,5.551,1.000,0.5,blue,0.7,George,below 318 | ``` 319 | The edge list `network_edges.csv`. 320 | 321 | ```text 322 | u,v,lw,bend,Direct 323 | a,b,1.0,-8.531,true 324 | a,c,1.0,-8.531,true 325 | c,d,3.0,-8.531,true 326 | d,e,3.0,-8.531,true 327 | e,c,3.0,-8.531,true 328 | c,f,1.0,-8.531,true 329 | f,a,3.0,-8.531,true 330 | f,g,1.0,-8.531,true 331 | g,g,1.0,-8.531,true 332 | g,d,1.0,-8.531,true 333 | ``` 334 | 335 | ## The plot function in detail 336 | 337 | ```python 338 | network2tikz.plot(network, filename=None, type=None, **kwds) 339 | ``` 340 | 341 | ### Parameters 342 | 343 | - **network** : network object 344 | 345 | Network to be drawn. The network can be a 'cnet', 'networkx', 'igraph', 346 | 'pathpy' object, or a tuple of a node list and edge list. 347 | 348 | - **filename** : file, string or None, optional (default = None) 349 | 350 | File or filename to save. The file ending specifies the 351 | output. i.e. is the file ending with '.tex' a tex file will be 352 | created; if the file ends with '.pdf' a pdf is created; if the file 353 | ends with '.csv', two csv files are generated (filename_nodes.csv 354 | and filename_edges.csv). If the filename is a tuple of strings, the 355 | first entry will be used to name the node list and the second entry 356 | for the edge list; and if no ending and no type is defined a 357 | temporary pdf file is compiled and shown. 358 | 359 | - **type** : str or None, optional (default = None) 360 | 361 | Type of the output file. If no ending is defined trough the filename, 362 | the type of the output file can be specified by the type 363 | option. Currently the following output types are supported: 364 | 'tex', 'pdf', 'csv' and 'dat'. 365 | 366 | - **kwds** : keyword arguments, optional (default= no attributes) 367 | 368 | Attributes used to modify the appearance of the plot. 369 | For details see below. 370 | 371 | ### Keyword arguments for node styles 372 | 373 | - ``node_size`` : size of the node. The default is 0.6 cm. 374 | 375 | - ``node_color`` : color of the nodes. The default is light blue. Colors can 376 | be specified either by common color names, or by 3-tuples of floats 377 | (ranging between 0 and 255 for the R, G and B components). 378 | 379 | - ``node_opacity`` : opacity of the nodes. The default is 1. The range of the 380 | number lies between 0 and 1. Where 0 represents a fully transparent fill 381 | and 1 a solid fill. 382 | 383 | - ``node_label`` : labels drawn next to the nodes. 384 | 385 | - ``node_label_position`` : Per default the position of the label is in the 386 | center of the node. Classical Tikz commands can be used to change the 387 | position of the label. Instead, using such command, the position can be 388 | determined via an angle, by entering a number between -360 and 360. The 389 | origin (0) is the y axis. A positive number change the position counter 390 | clockwise, while a negative number make changes clockwise. 391 | 392 | - ``node_label_distance`` : distance between the node and the label. 393 | 394 | - ``node_label_color`` : color of the label. 395 | 396 | - ``node_label_size`` : font size of the label. 397 | 398 | - ``node_shape`` : shape of the vertices. Possibilities are: 399 | 'circle', 'rectangle', 'triangle', and any other Tikz shape 400 | 401 | - ``node_style`` : Any other Tikz style option or command can be entered via 402 | the option style. Most of these commands can be found in the "TikZ and 403 | PGF Manual". Contain the commands additional options (e.g. shading = 404 | ball), then the argument for the style has to be between { } brackets. 405 | 406 | - ``node_layer`` : the node can be assigned to a specific layer. 407 | 408 | - ``node_label_off`` : is Boolean option which suppress all labels. 409 | 410 | - ``node_label_as_id`` : is a Boolean option which assigns the node id as label. 411 | 412 | - ``node_math_mode`` : is a Boolean option which transforms the labels into 413 | mathematical expressions without using the $ $ environment. 414 | 415 | - ``node_pseudo`` : is a Boolean option which creates a pseudo node, where only 416 | the node name and the node coordinate will be provided. 417 | 418 | ### Keyword arguments for edge styles 419 | 420 | - ``edge_width`` : width of the edges. The default unit is point (pt). 421 | 422 | - ``edge_color`` : color of the edges. The default is gray. Colors can 423 | be specified either by common color names, or by 3-tuples of floats 424 | (ranging between 0 and 255 for the R, G and B components). 425 | 426 | - ``edge_opacity`` : opacity of the edges. The default is 1. The range of the 427 | number lies between 0 and 1. Where 0 represents a fully transparent fill 428 | and 1 a solid fill. 429 | 430 | - ``edge_curved`` : whether the edges should be curved. Positive numbers 431 | correspond to edges curved in a counter-clockwise direction, negative 432 | numbers correspond to edges curved in a clockwise direction. Zero 433 | represents straight edges. 434 | 435 | - ``edge_label`` : labels drawn next to the edges. 436 | 437 | - ``edge_label_position`` : Per default the label is positioned in between 438 | both nodes in the center of the line. Classical Tikz commands can be used to 439 | change the position of the label. 440 | 441 | - ``edge_label_distance`` : The label position between the nodes can be 442 | modified with the distance option. Per default the label is centered 443 | between both nodes. The position is expressed as the percentage of the 444 | length between the nodes, e.g. of distance = 0.7, the label is placed at 445 | 70% of the edge length away of Vertex i. 446 | 447 | - ``edge_label_color`` : color of the label. 448 | 449 | - ``edge_label_size`` : font size of the label. 450 | 451 | - ``edge_style`` : Any other Tikz style option or command can be entered via 452 | the option style. Most of these commands can be found in the "TikZ and 453 | PGF Manual". Contain the commands additional options (e.g. shading = 454 | ball), then the argument for the style has to be between { } brackets. 455 | 456 | - ``edge_arrow_size`` : arrow size of the edges. 457 | 458 | - ``edge_arrow_width`` : width of the arrowhead on the edge. 459 | 460 | - ``edge_loop_size`` : modifies the length of the edge. The measure value has 461 | to be insert together with its units. Per default the loop size is 1 cm. 462 | 463 | - ``edge_loop_position`` : The position of the self-loop is defined via the 464 | rotation angle around the node. The origin (0) is the y axis. A positive 465 | number change the loop position counter clockwise, while a negative 466 | number make changes clockwise. 467 | 468 | - ``edge_loop_shape`` : The shape of the self-loop is defined by the enclosing 469 | angle. The shape can be changed by decreasing or increasing the argument 470 | value of the loop shape option. 471 | 472 | - ``edge_directed`` : is a Boolean option which transform edges to directed 473 | arrows. If the network is already defined as directed network this option 474 | is not needed, except to turn off the direction for one or more edges. 475 | 476 | - ``edge_math_mode`` : is a Boolean option which transforms the labels into 477 | mathematical expressions without using the $ $ environment. 478 | 479 | - ``edge_not_in_bg`` : Per default, the edge is drawn on the background layer 480 | of the tikz picture. I.e. objects which are created after the edges 481 | appear also on top of them. To turn this off, the option edge_not_in_bg 482 | has to be enabled. 483 | 484 | ### Keyword arguments for layout styles 485 | 486 | NOTE: All layout arguments can be entered with or without 'layout_' at the 487 | beginning, e.g. 'layout_iterations' is equal to 'iterations' 488 | 489 | - ``layout`` : dict or string , optional (default = None) 490 | A dictionary with the node positions on a 2-dimensional plane. The 491 | key value of the dict represents the node id while the value 492 | represents a tuple of coordinates (e.g. n = (x,y)). The initial 493 | layout can be placed anywhere on the 2-dimensional plane. 494 | 495 | Instead of a dictionary, the algorithm used for the layout can be defined 496 | via a string value. Currently, supported are: 497 | 498 | * Random layout, where the nodes are uniformly at random placed in the 499 | unit square. This algorithm can be enabled with the keywords: 'Random', 500 | 'random', 'rand', or None 501 | 502 | * Fruchterman-Reingold force-directed algorithm. In this algorithm, the 503 | nodes are represented by steel rings and the edges are springs between 504 | them. The attractive force is analogous to the spring force and the 505 | repulsive force is analogous to the electrical force. The basic idea is 506 | to minimize the energy of the system by moving the nodes and changing 507 | the forces between them. This algorithm can be enabled with the 508 | keywords: 'Fruchterman-Reingold', 'fruchterman_reingold', 'fr', 509 | 'spring_layout', 'spring layout', 'FR' 510 | 511 | | Algorithms | Keywords | 512 | |----------------------|------------------------------------------------| 513 | | Random | Random, random, rand, None | 514 | | Fruchterman-Reingold | Fruchterman-Reingold, fruchterman_reingold, fr | 515 | | | spring_layout, spring layout, FR | 516 | 517 | - ``force`` : float, optional (default = None) 518 | Optimal distance between nodes. If None the distance is set to 519 | 1/sqrt(n) where n is the number of nodes. Increase this value to move 520 | nodes farther apart. 521 | 522 | - ``positions`` : dict or None optional (default = None) 523 | Initial positions for nodes as a dictionary with node as keys and values 524 | as a coordinate list or tuple. If None, then use random initial 525 | positions. 526 | 527 | - ``fixed`` : list or None, optional (default = None) 528 | Nodes to keep fixed at initial position. 529 | 530 | - ``iterations`` : int, optional (default = 50) 531 | Maximum number of iterations taken 532 | 533 | - ``threshold``: float, optional (default = 1e-4) 534 | Threshold for relative error in node position changes. The iteration 535 | stops if the error is below this threshold. 536 | 537 | - ``weight`` : string or None, optional (default = None) 538 | The edge attribute that holds the numerical value used for the edge 539 | weight. If None, then all edge weights are 1. 540 | 541 | - ``dimension`` : int, optional (default = 2) 542 | Dimension of layout. Currently, only plots in 2 dimension are supported. 543 | 544 | - ``seed`` : int or None, optional (default = None) 545 | Set the random state for deterministic node layouts. If int, `seed` is 546 | the seed used by the random number generator, if None, the a random seed 547 | by created by the numpy random number generator is used. 548 | 549 | 550 | ### Keyword arguments for general options 551 | 552 | - ``units`` : string or tuple of strings, optional (default = ('cm','pt')) 553 | Per default, all size values are based on cm, and all line widths are 554 | defined in pt units. Whit this option the input units can be 555 | changed. Currently supported are: Pixel 'px', Points 'pt', 556 | Millimeters 'mm', and Centimeters 'cm'. If a single value is entered as 557 | unit all inputs have to be defined using this unit. If a tuple of units 558 | is given, the sizes are defined with the first entry the line widths with 559 | the second entry. 560 | 561 | - ``margins`` : None, int, float or dict, optional (default = None) 562 | The margins define the 'empty' space from the canvas border. If no 563 | margins are defined, the margin will be calculated based on the maximum 564 | node size, to avoid clipping of the nodes. If a single int or float is 565 | defined all margins using this distances. To define different the margin 566 | sizes for all size a dictionary with in the form of 567 | `{'top':2,'left':1,'bottom':2,'right':.5}` has to be used. 568 | 569 | - ``canvas`` : None, tuple of int or floats, optional (default = (6,6)) 570 | Canvas or figure_size defines the size of the plot. The values entered as 571 | a tuple of numbers where the first number is width of the figure and the 572 | second number is the height of the figure. If the option ``units`` is not 573 | used the size is specified in cm. Per default the canvas is 6cm x 6cm. 574 | 575 | - ``keep_aspect_ratio`` : bool, optional (default = True) 576 | Defines whether to keep the aspect ratio of the current layout. If 577 | ``False``, the layout will be rescaled to fit exactly into the 578 | available area in the canvas (i.e. removed margins). If ``True``, the 579 | original aspect ratio of the layout will be kept and it will be 580 | centered within the canvas. 581 | 582 | - ``standalone`` : bool, optional (default = True) 583 | If this option is true, a standalone latex file will be created. i.e. the 584 | figure can be compiled from this output file. If standalone is false, 585 | only the tikz environment is stored in the tex file, and can be imported 586 | in an existing tex file. 587 | 588 | - ``clean`` : bool, optional (default = True) 589 | Whether non-pdf files created that are created during compilation should 590 | be removed. 591 | 592 | - ``clean_tex`` : bool, optional (default = True) 593 | Also remove the generated tex file. 594 | 595 | - ``compiler`` : `str` or `None`, optional (default = None) 596 | The name of the LaTeX compiler to use. If it is None, cnet will choose a 597 | fitting one on its own. Starting with ``latexmk`` and then ``pdflatex``. 598 | 599 | - ``compiler_args`` : `list` or `None`, optional (default = None) 600 | Extra arguments that should be passed to the LaTeX compiler. If this is 601 | None it defaults to an empty list. 602 | 603 | - ``silent`` : bool, optional (default = True) 604 | Whether to hide compiler output or not. 605 | 606 | ### Keyword naming convention 607 | 608 | In the style dictionary multiple keywords can be used to address 609 | attributes. These keywords will be converted to an unique key word, 610 | used in the remaining code. This allows to keep the keywords used in 611 | `igraph`. 612 | 613 | 614 | | keys | other valid keys | 615 | |-----------|-----------------------------------| 616 | | node | vertex, v, n | 617 | | edge | link, l, e | 618 | | margins | margin | 619 | | canvas | bbox, figure_size | 620 | | units | unit | 621 | | fixed | fixed_nodes, fixed_vertices, | 622 | | | fixed_n, fixed_v | 623 | | positions | initial_positions, node_positions | 624 | | | vertex_positions, n_positions, | 625 | | | v_positions | 626 | 627 | 628 | ## TODO 629 | 630 | - [ ] Add multi-layer handler 631 | 632 | ## Changelog 633 | | Version | Date | Changes | 634 | |---------|------------|-------------------------------------------------| 635 | | 0.1.0 | 2018-05-21 | initial commit to github | 636 | | 0.1.1 | 2018-05-22 | initial commit to PyPI | 637 | | 0.1.2 | 2018-05-27 | fixed Windows compiling problem | 638 | | 0.1.3 | 2018-07-17 | fixed layout problem when coordinates are zero | 639 | | 0.1.4 | 2018-07-29 | added some layouts algorithms | 640 | | 0.1.5 | 2018-07-30 | allow to add multiple networks to the same plot | 641 | | 0.1.6 | 2018-08-07 | some smaller bug fixes | 642 | | 0.1.7 | 2018-11-05 | fixed error with pathpy and csv export | 643 | | 0.1.8 | 2018-11-07 | fixed cnet and pathpy dependencies | 644 | | | | | 645 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sample.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sample.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/sample" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sample" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # sample documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Apr 16 21:22:43 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('../')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | #extensions = [] 29 | # extensions = ['sphinx.ext.autodoc', 30 | # 'sphinx.ext.doctest', 31 | # 'sphinx.ext.todo', 32 | # 'sphinx.ext.coverage', 33 | # 'sphinx.ext.viewcode', 34 | # 'sphinx.ext.mathjax'] 35 | extensions = [ 36 | 'sphinx.ext.autosummary', 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.doctest', 40 | 'sphinx.ext.intersphinx', 41 | 'sphinx.ext.mathjax', 42 | 'sphinx.ext.napoleon', 43 | 'sphinx.ext.todo', 44 | 'sphinx.ext.viewcode', 45 | #'sphinx_gallery.gen_gallery', 46 | #'nb2plots', 47 | #'texext', 48 | ] 49 | 50 | 51 | sphinx_gallery_conf = { 52 | # path to your examples scripts 53 | # 'examples_dirs': '../examples', 54 | # 'subsection_order': ExplicitOrder(['../examples/basic', 55 | # '../examples/drawing', 56 | # '../examples/graph', 57 | # '../examples/algorithms', 58 | # '../examples/advanced', 59 | # '../examples/3d_drawing', 60 | # '../examples/pygraphviz', 61 | # '../examples/javascript', 62 | # '../examples/jit', 63 | # '../examples/subclass']), 64 | # # path where to save gallery generated examples 65 | # 'gallery_dirs': 'auto_examples', 66 | 'backreferences_dir': 'modules/generated', 67 | # 'expected_failing_examples': ['../examples/advanced/plot_parallel_betweenness.py'] 68 | } 69 | 70 | # generate autosummary pages 71 | autosummary_generate = True 72 | 73 | # Add any paths that contain templates here, relative to this directory. 74 | # templates_path = ['_templates'] 75 | 76 | # The suffix of source filenames. 77 | source_suffix = '.rst' 78 | 79 | # The encoding of source files. 80 | #source_encoding = 'utf-8-sig' 81 | 82 | # The master toctree document. 83 | master_doc = 'index' 84 | 85 | # General information about the project. 86 | project = u'network2tikz' 87 | copyright = u'2018, Juergen Hackl' 88 | 89 | # The version info for the project you're documenting, acts as replacement for 90 | # |version| and |release|, also used in various other places throughout the 91 | # built documents. 92 | # 93 | # The short X.Y version. 94 | version = 'v0.1.0' 95 | # The full version, including alpha/beta/rc tags. 96 | release = 'v0.1.0' 97 | 98 | # The language for content autogenerated by Sphinx. Refer to documentation 99 | # for a list of supported languages. 100 | #language = None 101 | 102 | # There are two options for replacing |today|: either, you set today to some 103 | # non-false value, then it is used: 104 | #today = '' 105 | # Else, today_fmt is used as the format for a strftime call. 106 | #today_fmt = '%B %d, %Y' 107 | 108 | # List of patterns, relative to source directory, that match files and 109 | # directories to ignore when looking for source files. 110 | exclude_patterns = ['_build'] 111 | 112 | # The reST default role (used for this markup: `text`) to use for all documents. 113 | #default_role = None 114 | 115 | # If true, '()' will be appended to :func: etc. cross-reference text. 116 | #add_function_parentheses = True 117 | 118 | # If true, the current module name will be prepended to all description 119 | # unit titles (such as .. function::). 120 | #add_module_names = True 121 | 122 | # If true, sectionauthor and moduleauthor directives will be shown in the 123 | # output. They are ignored by default. 124 | #show_authors = False 125 | 126 | # The name of the Pygments (syntax highlighting) style to use. 127 | pygments_style = 'sphinx' 128 | 129 | # A list of ignored prefixes for module index sorting. 130 | #modindex_common_prefix = [] 131 | 132 | 133 | # -- Options for HTML output --------------------------------------------------- 134 | 135 | # The theme to use for HTML and HTML Help pages. See the documentation for 136 | # a list of builtin themes. 137 | html_theme = 'sphinx_rtd_theme' 138 | 139 | # Theme options are theme-specific and customize the look and feel of a theme 140 | # further. For a list of options available for each theme, see the 141 | # documentation. 142 | #html_theme_options = {} 143 | 144 | # Add any paths that contain custom themes here, relative to this directory. 145 | #html_theme_path = [] 146 | 147 | # The name for this set of Sphinx documents. If None, it defaults to 148 | # " v documentation". 149 | #html_title = None 150 | 151 | # A shorter title for the navigation bar. Default is the same as html_title. 152 | #html_short_title = None 153 | 154 | # The name of an image file (relative to this directory) to place at the top 155 | # of the sidebar. 156 | #html_logo = None 157 | 158 | # The name of an image file (within the static path) to use as favicon of the 159 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 160 | # pixels large. 161 | #html_favicon = None 162 | 163 | # Add any paths that contain custom static files (such as style sheets) here, 164 | # relative to this directory. They are copied after the builtin static files, 165 | # so a file named "default.css" will overwrite the builtin "default.css". 166 | #html_static_path = ['_static'] 167 | 168 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 169 | # using the given strftime format. 170 | #html_last_updated_fmt = '%b %d, %Y' 171 | 172 | # If true, SmartyPants will be used to convert quotes and dashes to 173 | # typographically correct entities. 174 | #html_use_smartypants = True 175 | 176 | # Custom sidebar templates, maps document names to template names. 177 | #html_sidebars = {} 178 | 179 | # Additional templates that should be rendered to pages, maps page names to 180 | # template names. 181 | #html_additional_pages = {} 182 | 183 | # If false, no module index is generated. 184 | #html_domain_indices = True 185 | 186 | # If false, no index is generated. 187 | #html_use_index = True 188 | 189 | # If true, the index is split into individual pages for each letter. 190 | #html_split_index = False 191 | 192 | # If true, links to the reST sources are added to the pages. 193 | #html_show_sourcelink = True 194 | 195 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 196 | #html_show_sphinx = True 197 | 198 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 199 | #html_show_copyright = True 200 | 201 | # If true, an OpenSearch description file will be output, and all pages will 202 | # contain a tag referring to it. The value of this option must be the 203 | # base URL from which the finished HTML is served. 204 | #html_use_opensearch = '' 205 | 206 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 207 | #html_file_suffix = None 208 | 209 | # Output file base name for HTML help builder. 210 | htmlhelp_basename = 'network2tikz' 211 | 212 | 213 | # -- Options for LaTeX output -------------------------------------------------- 214 | 215 | latex_elements = { 216 | # The paper size ('letterpaper' or 'a4paper'). 217 | #'papersize': 'letterpaper', 218 | 219 | # The font size ('10pt', '11pt' or '12pt'). 220 | #'pointsize': '10pt', 221 | 222 | # Additional stuff for the LaTeX preamble. 223 | #'preamble': '', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, author, documentclass [howto/manual]). 228 | latex_documents = [ 229 | ('index', 'network2tikz.tex', u'network2tikz Documentation', 230 | u'Juergen Hackl', 'manual'), 231 | ] 232 | 233 | # The name of an image file (relative to this directory) to place at the top of 234 | # the title page. 235 | #latex_logo = None 236 | 237 | # For "manual" documents, if this is true, then toplevel headings are parts, 238 | # not chapters. 239 | #latex_use_parts = False 240 | 241 | # If true, show page references after internal links. 242 | #latex_show_pagerefs = False 243 | 244 | # If true, show URL addresses after external links. 245 | #latex_show_urls = False 246 | 247 | # Documents to append as an appendix to all manuals. 248 | #latex_appendices = [] 249 | 250 | # If false, no module index is generated. 251 | #latex_domain_indices = True 252 | 253 | 254 | # -- Options for manual page output -------------------------------------------- 255 | 256 | # One entry per manual page. List of tuples 257 | # (source start file, name, description, authors, manual section). 258 | man_pages = [ 259 | ('index', 'network2tikz', u'network2tikz Documentation', 260 | [u'Juergen Hackl'], 1) 261 | ] 262 | 263 | # If true, show URL addresses after external links. 264 | #man_show_urls = False 265 | 266 | 267 | # -- Options for Texinfo output ------------------------------------------------ 268 | 269 | # Grouping the document tree into Texinfo files. List of tuples 270 | # (source start file, target name, title, author, 271 | # dir menu entry, description, category) 272 | texinfo_documents = [ 273 | ('index', 'network2tikz', u'network2tikz Documentation', 274 | u'Juergen Hackl', 'network2tikz', 'One line description of project.', 275 | 'Miscellaneous'), 276 | ] 277 | 278 | # Documents to append as an appendix to all manuals. 279 | #texinfo_appendices = [] 280 | 281 | # If false, no module index is generated. 282 | #texinfo_domain_indices = True 283 | 284 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 285 | #texinfo_show_urls = 'footnote' 286 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. sample documentation master file, created by 2 | sphinx-quickstart on Mon Apr 16 21:22:43 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to network2tikz's documentation! 7 | ======================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | The plot method 15 | =============== 16 | 17 | .. currentmodule:: network2tikz 18 | .. autofunction:: plot 19 | 20 | The layout method 21 | ================= 22 | 23 | .. currentmodule:: network2tikz 24 | .. autofunction:: layout 25 | 26 | Indices and tables 27 | ================== 28 | 29 | * :ref:`genindex` 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | 33 | -------------------------------------------------------------------------------- /docs/plot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackl/network2tikz/fda072a1bc2b285f0768a7552de68c19ea4e36bc/docs/plot_01.png -------------------------------------------------------------------------------- /docs/plot_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackl/network2tikz/fda072a1bc2b285f0768a7552de68c19ea4e36bc/docs/plot_02.png -------------------------------------------------------------------------------- /docs/plot_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackl/network2tikz/fda072a1bc2b285f0768a7552de68c19ea4e36bc/docs/plot_03.png -------------------------------------------------------------------------------- /docs/plot_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackl/network2tikz/fda072a1bc2b285f0768a7552de68c19ea4e36bc/docs/plot_04.png -------------------------------------------------------------------------------- /examples/README.txt: -------------------------------------------------------------------------------- 1 | .. _examples_gallery: 2 | 3 | Examples 4 | ======== 5 | 6 | General-purpose and introductory examples for network2tikz. 7 | -------------------------------------------------------------------------------- /examples/data/chicken_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackl/network2tikz/fda072a1bc2b285f0768a7552de68c19ea4e36bc/examples/data/chicken_in.png -------------------------------------------------------------------------------- /examples/data/gentoo.txt: -------------------------------------------------------------------------------- 1 | seemant jakub 2 | seemant stefaan 3 | seemant kerberos 4 | seemant samba 5 | seemant damon 6 | seemant gad333kadosh 7 | vapier eradicator 8 | vapier plasmaroo 9 | vapier uberlord 10 | vapier weeve 11 | vapier tetromino 12 | vapier battousai 13 | vapier je111fro 14 | vapier karstenrbecker 15 | vapier Tiger683 16 | vapier gcc222porting 17 | vapier ppc 18 | vapier toolchain 19 | vapier crypto 20 | vapier hardened 21 | vapier base222system 22 | vapier embedded 23 | vapier amd64 24 | vapier x86 25 | vapier kernel 26 | vapier net222dialup 27 | vapier 8an 28 | vapier ftlofaro 29 | vapier smallone 30 | vapier paapaa125 31 | vapier joelinux 32 | vapier davec222gentoo 33 | vapier kcody 34 | jakub dragonheart 35 | jakub mholzer 36 | jakub solar 37 | jakub ticho 38 | jakub agriffis 39 | jakub mlspamcb 40 | jakub cardoe 41 | jakub bugzilla 42 | jakub uberlord 43 | jakub zzam 44 | jakub squinky86 45 | jakub hanno 46 | jakub weeve 47 | jakub andre 48 | jakub truedfx 49 | jakub lu111zero 50 | jakub askwar 51 | jakub ka0ttic 52 | jakub mail 53 | jakub chrb 54 | jakub allanonjl 55 | jakub strerror 56 | jakub latexer 57 | jakub lanius 58 | jakub rphillips 59 | jakub gentoo 60 | jakub humpback 61 | jakub caneko 62 | jakub gentoobugs 63 | jakub denilsonsa 64 | jakub aben 65 | jakub bugzilla222gentoo 66 | jakub markusle 67 | jakub pfeifer 68 | jakub ed 69 | jakub andrei333ivanov 70 | jakub alpeterson 71 | jakub xkr47 72 | jakub paolo333pedroni 73 | jakub david 74 | jakub arj 75 | jakub evan 76 | jakub thedude0001 77 | jakub mozilla 78 | jakub toralf333foerster 79 | jakub bugs333gentoo333org 80 | jakub nunoplopes 81 | jakub albertito 82 | jakub malverian 83 | jakub andy333dalton 84 | jakub alex 85 | jakub joey 86 | jakub spida 87 | jakub gcc222porting 88 | jakub ppc 89 | jakub toolchain 90 | jakub crypto 91 | jakub hardened 92 | jakub kde 93 | jakub base222system 94 | jakub embedded 95 | jakub x86 96 | jakub x11 97 | jakub gnome 98 | jakub kernel 99 | jakub net222mail 100 | jakub antivirus 101 | jakub x11222drivers 102 | jakub mobile 103 | jakub netmon 104 | jakub python 105 | jakub printing 106 | jakub qt 107 | jakub sound 108 | jakub qa 109 | jakub net222p2p 110 | jakub java 111 | jakub web222apps 112 | jakub net222im 113 | jakub ruby 114 | jakub net222ftp 115 | jakub selinux 116 | jakub pda 117 | jakub openoffice 118 | jakub sci 119 | jakub media222video 120 | jakub graphics 121 | jakub ppc222macos 122 | jakub www222servers 123 | jakub emacs 124 | jakub media222optical 125 | jakub media222tv 126 | jakub text222markup 127 | jakub lang222misc 128 | jakub qmail222bugs 129 | jakub tcltk 130 | jakub vim 131 | jakub net222fs 132 | jakub shell222tools 133 | jakub accessibility 134 | jakub webapps222request 135 | jakub cluster 136 | jakub lazy111bum 137 | jakub transacid 138 | jakub 4u 139 | jakub hrabe 140 | jakub app222dicts 141 | jakub amerei 142 | jakub taylor333jones 143 | jakub ced 144 | jakub lorenzo333milesi 145 | jakub tudor 146 | jakub BryanRJ 147 | jakub silinio 148 | jakub duchier 149 | jakub tiago333freire 150 | jakub dliana 151 | jakub andy 152 | jakub cbm 153 | jakub rickard333narstrom 154 | jakub grubba 155 | jakub morten 156 | jakub papercrane 157 | jakub blitz00 158 | jakub spamtrap 159 | jakub mynamewasgone 160 | jakub muczy 161 | jakub wsheets 162 | jakub ich 163 | jakub yuval333yaari 164 | jakub siryes 165 | jakub rich 166 | jakub markknecht 167 | jakub staralex 168 | jakub robert333golding 169 | jakub alanh 170 | jakub 111me 171 | jakub spiralvoice 172 | jakub equaeghe 173 | jakub zeksers 174 | jakub greenwaldjared 175 | jakub enrygabry 176 | jakub arno 177 | jakub simon333strandman 178 | jakub alephlg 179 | jakub neil 180 | jakub jouni333rinne 181 | jakub fist111187 182 | jakub qeldroma 183 | jakub kajan111linux 184 | jakub strowi 185 | jakub neclimdul 186 | jakub phrexianreaper 187 | jakub lema 188 | jakub bruno 189 | jakub mudrii 190 | jakub alexluhrman 191 | jakub matthew333garman 192 | jakub andreasgick 193 | jakub jonas 194 | jakub jormarus 195 | jakub whit 196 | jakub djfarid 197 | jakub desowin 198 | jakub jcwren 199 | jakub znmeb 200 | jakub marsclic 201 | jakub elliot333pahl 202 | jakub rossettigab 203 | jakub ippokratis 204 | jakub gmurray 205 | jakub VValdo 206 | jakub FelixWiemannBugs 207 | jakub lex82 208 | jakub fellows 209 | jakub peter333ebden 210 | jakub musty333elessar 211 | jakub gazman 212 | jakub m333labhard 213 | jakub jiri333baloun 214 | jakub stephane333bausseron 215 | jakub rodney333brown 216 | jakub foti 217 | jakub pekunz 218 | jakub nick333peters 219 | jakub r111schneid 220 | jakub virus 221 | jakub pierre 222 | jakub jwind222gentoo 223 | jakub newchief 224 | jakub r111welz 225 | jakub carsten333milkau 226 | jakub aries333huijzer 227 | jakub synergy6 228 | jakub jarrellmark 229 | jakub papp333zoltan 230 | jakub sebastian333held 231 | jakub fischer 232 | jakub rob333eyre 233 | jakub lars333schonert 234 | jakub james333youngquist 235 | jakub chtitux 236 | jakub jorgecis 237 | jakub gotaserena 238 | jakub benjamin333lamowski 239 | jakub mluschas 240 | jakub root111 241 | jakub robert333zhangle 242 | jakub phasma 243 | jakub skelter 244 | jakub harno 245 | jakub yawgmoth7 246 | jakub david333hinkes 247 | jakub stormchaseruk 248 | jakub ryangrange 249 | jakub kouzminv 250 | jakub huseyinkozan 251 | jakub hoadley 252 | jakub kdallasd 253 | jakub vekku2k 254 | jakub kai333wassermann 255 | jakub dreamer86 256 | jakub goric 257 | jakub pabloa 258 | jakub adelsberger 259 | jakub ded 260 | jakub wolfstar 261 | jakub mojito 262 | jakub richard333korinek 263 | jakub rb 264 | jakub kero552 265 | jakub ebpowell 266 | jakub jbaldassari 267 | jakub kenaparsons 268 | jakub wonder333sk 269 | jakub andreas222stangl 270 | jakub nicola333mondinelli 271 | flameeyes phajdan333jr 272 | flameeyes ferringb 273 | flameeyes base222system 274 | flameeyes kernel 275 | flameeyes bsd 276 | flameeyes media222video 277 | flameeyes e333liubarskij 278 | flameeyes psihozefir 279 | flameeyes lothalev 280 | robbat2 vapier 281 | robbat2 dragonheart 282 | robbat2 crypto 283 | robbat2 kernel 284 | robbat2 portersb 285 | eradicator x11 286 | dragonheart dhp111gentoo 287 | dragonheart toolchain 288 | dragonheart base222system 289 | dberkholz jforman 290 | dberkholz x11 291 | dberkholz java 292 | dberkholz spyderous 293 | dberkholz tiago333freire 294 | dberkholz penguin222nix 295 | dberkholz goldfish654 296 | nelchael desktop222wm 297 | nelchael x86 298 | dertobi123 ppc 299 | betelgeuse security 300 | carlo kde 301 | carlo amd64 302 | carlo gnome 303 | carlo netmon 304 | carlo sound 305 | carlo openoffice 306 | carlo media222optical 307 | carlo gstreamer 308 | carlo maciej333blizinski 309 | carlo ibarbu 310 | carlo vyzivus 311 | azarah toolchain 312 | solar plasmaroo 313 | solar johnm 314 | solar kumba 315 | solar dsd 316 | solar kernel 317 | solar ps333m 318 | solar spyderous 319 | jaervosz carlo 320 | jaervosz taviso 321 | jaervosz leonardop 322 | jaervosz exg 323 | jaervosz sh 324 | jaervosz ppc 325 | jaervosz hppa 326 | jaervosz ia64 327 | jaervosz arm 328 | jaervosz alpha 329 | jaervosz sparc 330 | jaervosz ppc64 331 | jaervosz kde 332 | jaervosz amd64 333 | jaervosz x86 334 | jaervosz gnome 335 | jaervosz net222mail 336 | jaervosz s390 337 | jaervosz mips 338 | jaervosz web222apps 339 | jaervosz sysadmin 340 | dercorny kloeri 341 | dercorny dertobi123 342 | dercorny halcy0n 343 | dercorny anarchy 344 | dercorny gustavoz 345 | dercorny tcort 346 | dercorny corsair 347 | dercorny tsunam 348 | dercorny spyderous 349 | dercorny security 350 | dercorny stepp 351 | mkennedy emacs 352 | foser gcc222porting 353 | foser x86 354 | foser fonts 355 | foser devrel 356 | foser der111eq 357 | vanquirius netmon 358 | beandog dsd 359 | beandog ppc 360 | beandog amd64 361 | beandog x86 362 | beandog media222video 363 | mrness ticho 364 | mrness net222proxy 365 | tove games 366 | plasmaroo chrb 367 | plasmaroo voxus 368 | plasmaroo games 369 | plasmaroo cluster 370 | plasmaroo kang 371 | plasmaroo gimli 372 | nerdboy ppc 373 | nerdboy hppa 374 | nerdboy ia64 375 | nerdboy alpha 376 | nerdboy sparc 377 | nerdboy ppc64 378 | nerdboy amd64 379 | nerdboy x86 380 | falco kloeri 381 | falco stuart 382 | falco ppc 383 | falco hppa 384 | falco ia64 385 | falco alpha 386 | falco sparc 387 | falco ppc64 388 | falco amd64 389 | falco x86 390 | falco mips 391 | falco ppc222macos 392 | kevquinn toolchain 393 | chainsaw hppa 394 | chainsaw alpha 395 | chainsaw sparc 396 | chainsaw amd64 397 | chainsaw x86 398 | cedk ced 399 | bugzilla lourdas111v 400 | henrik kugelfang 401 | henrik josejx 402 | henrik brix 403 | kugelfang toolchain 404 | kugelfang qa 405 | uberlord hn0rbgn0br 406 | blubb herbs 407 | blubb welp 408 | blubb amd64 409 | blubb mips 410 | blubb printing 411 | blubb mrfree 412 | blubb mog333johnny 413 | wolf31o2 pylon 414 | wolf31o2 ppc 415 | wolf31o2 hppa 416 | wolf31o2 hardened 417 | wolf31o2 ia64 418 | wolf31o2 alpha 419 | wolf31o2 sparc 420 | wolf31o2 ppc64 421 | wolf31o2 amd64 422 | wolf31o2 x86 423 | wolf31o2 kernel 424 | wolf31o2 mips 425 | wolf31o2 pub111br111gentoo333org 426 | wolf31o2 aaron 427 | joshuabaergen jakub 428 | joshuabaergen flameeyes 429 | joshuabaergen dsd 430 | joshuabaergen Martin333vGagern 431 | joshuabaergen sh 432 | joshuabaergen hardened 433 | squinky86 amd64 434 | squinky86 python 435 | squinky86 voip 436 | squinky86 web222apps 437 | squinky86 accessibility 438 | george nattfodd 439 | george spyderous 440 | george dominique333michel 441 | weeve sparc 442 | truedfx seemant 443 | truedfx exg 444 | gustavoz weeve 445 | gustavoz sparc 446 | phosphan flameeyes 447 | suka ppc 448 | suka sparc 449 | suka openoffice 450 | genstef perl 451 | genstef printing 452 | genstef emilbeinroth 453 | genstef gentooperson 454 | caleb tiago333freire 455 | caleb dd55 456 | caleb ns 457 | caleb jiri333baloun 458 | mail info 459 | mail elias333probst 460 | mail noup333net 461 | szarpaj lazy111bum 462 | chrb james333laver 463 | aross chrb 464 | allanonjl fvalenduc 465 | allanonjl nidoranz 466 | allanonjl bassul 467 | dev222zero gentoo 468 | hlieberman agriffis 469 | hlieberman ferdy 470 | hlieberman sysadmin 471 | dsd spock 472 | dsd kernel 473 | ian ppc 474 | ian hppa 475 | ian ia64 476 | ian alpha 477 | ian sparc 478 | ian ppc64 479 | ian amd64 480 | ian x86 481 | ian mips 482 | gentoo double 483 | gentoo kenzelma 484 | gentoo mwhitlock 485 | gentoo222bugzilla rickard333narstrom 486 | battousai base222system 487 | dang gnome 488 | rich0 rich 489 | ahf alex 490 | tcort alpha 491 | tcort amd64 492 | corsair toolchain 493 | nichoj karltk 494 | nichoj java 495 | maxxer lorenzo333milesi 496 | aben drahos 497 | bugs333gentoo333org1114 bugs333gentoo333org 498 | m+gentoo222bugs bugzilla222gentoo 499 | tjkirch+gentoobugzilla gentoo 500 | denys333duchier duchier 501 | news gentoo 502 | exg seemant 503 | exg dickey 504 | yuval yuval333yaari 505 | david pookey 506 | b33fc0d3 kosmikus 507 | b33fc0d3 base222system 508 | jason333b333phillips gentoo 509 | scott222sender22275f28f gentoo 510 | thedude0001 games 511 | l33tmmx jouni333rinne 512 | pageexec solar 513 | tsunam x86 514 | -------------------------------------------------------------------------------- /examples/ex_animation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_animation.py 5 | # Creation : 24 November 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example for converting a node/edge list to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | import numpy as np 31 | 32 | # sys.path.insert(0, os.path.abspath( 33 | # os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | from network2tikz import plot 36 | 37 | 38 | def main(): 39 | # Network 40 | # ------- 41 | nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] 42 | edges = [('a', 'b'), ('a', 'c'), ('b', 'c'), ('b', 'd'), ('d', 'e'), ('d', 'f'), 43 | ('d', 'g'), ('e', 'f'), ('f', 'g')] 44 | net = (nodes, edges) 45 | 46 | # Network attributes 47 | # ------------------ 48 | layout = {'a': (0, 0), 'b': (1, 1), 'c': (0, 2), 'd': (2, 1), 'e': (3, 2), 49 | 'f': (4, 1), 'g': (3, 0)} 50 | 51 | # Network transition matrix 52 | # ------------------------- 53 | T = np.array([[0, 1/2, 1/2, 0, 0, 0, 0], 54 | [1/3, 0, 1/3, 1/3, 0, 0, 0], 55 | [1/2, 1/2, 0, 0, 0, 0, 0], 56 | [0, 1/4, 0, 0, 1/4, 1/4, 1/4], 57 | [0, 0, 0, 1/2, 0, 1/2, 0], 58 | [0, 0, 0, 1/3, 1/3, 0, 1/3], 59 | [0, 0, 0, 1/2, 0, 1/2, 0]]) 60 | 61 | # Starting vector 62 | x = np.array([1, 0, 0, 0, 0, 0, 0]) 63 | 64 | # Visual style dict 65 | # ----------------- 66 | visual_style = {} 67 | 68 | # node styles 69 | # ----------- 70 | visual_style['node_size'] = .8 71 | visual_style['node_color'] = 'red' 72 | 73 | # edge styles 74 | # ----------- 75 | visual_style['edge_width'] = 2 76 | visual_style['edge_curved'] = 0.1 77 | 78 | # general options 79 | # --------------- 80 | visual_style['layout'] = layout 81 | visual_style["canvas"] = (10, 7) 82 | 83 | # create images 84 | # ------------- 85 | for step in range(10): 86 | # create file name for step n 87 | filename = '{num:02d}_network.pdf'.format(num=step) 88 | 89 | # get distribution for step n 90 | values = np.linalg.matrix_power(T, step).transpose().dot(x) 91 | 92 | # change node label 93 | visual_style['node_label'] = [str(n) for n in np.round(values, 3)] 94 | 95 | # change node oppacity 96 | visual_style['node_opacity'] = list(values) 97 | # Create a latex file 98 | 99 | plot(net, filename, **visual_style) 100 | 101 | 102 | if __name__ == '__main__': 103 | main() 104 | 105 | # ============================================================================= 106 | # eof 107 | # 108 | # Local Variables: 109 | # mode: python 110 | # mode: linum 111 | # mode: auto-fill 112 | # fill-column: 80 113 | # End: 114 | -------------------------------------------------------------------------------- /examples/ex_chicken.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_chicken.py -- Chicken image to network to LaTeX/TikZ 5 | # Author : Juergen Hackl 6 | # Creation : 2018-08-08 7 | # Time-stamp: 8 | # 9 | # The code is based on the blog post "Transforming images into networks" 10 | # from Vedran Sekara (https://vedransekara.github.io/) 11 | # 12 | # ============================================================================= 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from scipy.ndimage import imread 16 | from scipy.spatial import cKDTree 17 | import random 18 | 19 | # function to transform color image to grayscale 20 | 21 | 22 | def rgb2gray(rgb): 23 | return np.dot(rgb[..., :3], [0.299, 0.587, 0.114]) 24 | 25 | 26 | def rgb2hex(color): 27 | ''' 28 | Matplotlib scatter is not happy with rgb tuples so we need to transform them to hex 29 | ''' 30 | c = tuple([np.int(255 if c == 1.0 else c * 256.0) for c in color]) 31 | return "#%02x%02x%02x" % c 32 | 33 | 34 | # parameters 35 | p = 0.003 # propability of selecting a pixel/node 36 | k = 5 # number of connections pre per pixel/node 37 | # remove values above this value 0 (white) - 255 (black) OR 0 (black) - 1 (white) 38 | pix_threshold = 0.9 39 | 40 | # load image 41 | data = plt.imread('./data/chicken_in.png') 42 | y, x = np.where(rgb2gray(data[:, :, :3]) < pix_threshold) 43 | y_norm, x_norm = map(float, data[:, :, 0].shape) 44 | colors = data[:, :, :3] 45 | 46 | # if its a large image it might be a good idea to downsample 47 | # y,x = np.where(rgb2gray(data[::3,::3,:3]) 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example converting cnet networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | 31 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 32 | 33 | import cnet as cn 34 | from network2tikz import plot 35 | 36 | def main(): 37 | # Network 38 | # ------- 39 | net = cn.Network(name = 'my tikz test network',directed=True) 40 | net.add_edges_from([('ab','a','b'), ('ac','a','c'), ('cd','c','d'), 41 | ('de','d','e'), ('ec','e','c'), ('cf','c','f'), 42 | ('fa','f','a'), ('fg','f','g'), ('gd','g','d'), 43 | ('gg','g','g')]) 44 | 45 | # Network attributes 46 | # ------------------ 47 | net.nodes['name'] = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 48 | 'George'] 49 | net.nodes['age'] = [25, 31, 18, 47, 22, 23, 50] 50 | net.nodes['gender'] = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 51 | 52 | net.edges['is_formal'] = [False, False, True, True, True, False, True, 53 | False, False, False] 54 | 55 | # Network dicts 56 | # ------------- 57 | color_dict = {"m": "blue", "f": "red"} 58 | shape_dict = {"m": "circle", "f": "rectangle"} 59 | style_dict = {"m": "{shading=ball}", "f": None} 60 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 61 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 62 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 63 | 'g': (9.7608, -12.7)} 64 | 65 | # Visual style dict 66 | # ----------------- 67 | visual_style = {} 68 | 69 | # node styles 70 | # ----------- 71 | visual_style['node_size'] = 5 72 | visual_style['node_color'] = [color_dict[g] for g in net.nodes('gender')] 73 | visual_style['node_opacity'] = .7 74 | visual_style['node_label'] = net.nodes['name'] 75 | visual_style['node_label_position'] = 'below' 76 | visual_style['node_label_distance'] = 15 77 | visual_style['node_label_color'] = 'gray' 78 | visual_style['node_label_size'] = 3 79 | visual_style['node_shape'] = [shape_dict[g] for g in net.nodes('gender')] 80 | visual_style['node_style'] = [style_dict[g] for g in net.nodes('gender')] 81 | visual_style['node_label_off'] = {'e':True} 82 | visual_style['node_math_mode'] = [True] 83 | visual_style['node_label_as_id'] = {'f':True} 84 | visual_style['node_pseudo'] = {'d':True} 85 | 86 | # edge styles 87 | # ----------- 88 | visual_style['edge_width'] = [.3 + .3 * int(f) for f in net.edges('is_formal')] 89 | visual_style['edge_color'] = 'black' 90 | visual_style['edge_opacity'] = .8 91 | visual_style['edge_curved'] = 0.1 92 | visual_style['edge_label'] = [e for e in net.edges] 93 | visual_style['edge_label_position'] = 'above' 94 | visual_style['edge_label_distance'] = .6 95 | visual_style['edge_label_color'] = 'gray' 96 | visual_style['edge_label_size'] = {'ac':5} 97 | visual_style['edge_style'] = 'dashed' 98 | visual_style['edge_arrow_size'] = .2 99 | visual_style['edge_arrow_width'] = .2 100 | 101 | visual_style['edge_loop_size'] = 15 102 | visual_style['edge_loop_position'] = 90 103 | visual_style['edge_loop_shape'] = 45 104 | visual_style['edge_directed'] = [True,True,False,True,True,False,True, 105 | True,True,True] 106 | visual_style['edge_label'][1] = '\\frac{\\alpha}{\\beta}' 107 | visual_style['edge_math_mode'] = {'ac':True} 108 | visual_style['edge_not_in_bg'] = {'fa':True} 109 | 110 | # general options 111 | # --------------- 112 | visual_style['unit'] = 'mm' 113 | visual_style['layout'] = layout 114 | visual_style["margin"] = {'top':5,'bottom':8,'left':5,'right':5} 115 | visual_style["canvas"] = (100,60) 116 | visual_style['keep_aspect_ratio'] = False 117 | 118 | # Create a latex file 119 | plot(net,'network.tex',**visual_style) 120 | 121 | # Create a node and edge list used by tikz-network 122 | 123 | # plot(net,'network.csv',**visual_style) 124 | 125 | # Create pdf figure of the network 126 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 127 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 128 | 129 | # plot(net,'network.pdf',**visual_style) 130 | 131 | # Create temp pdf and show the output 132 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 133 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 134 | 135 | # plot(net,**visual_style) 136 | 137 | 138 | if __name__ == '__main__': 139 | main() 140 | 141 | # ============================================================================= 142 | # eof 143 | # 144 | # Local Variables: 145 | # mode: python 146 | # mode: linum 147 | # mode: auto-fill 148 | # fill-column: 80 149 | # End: 150 | -------------------------------------------------------------------------------- /examples/ex_gentoo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_igraph_gentoo.py -- Plotting a colaboration network as tikz 5 | # Author : Juergen Hackl 6 | # Creation : 2018-08-07 7 | # Time-stamp: 8 | # 9 | # Copyright (c) 2018 Juergen Hackl 10 | # ============================================================================= 11 | import os 12 | import sys 13 | 14 | import igraph as ig 15 | import networkx as nx 16 | 17 | 18 | # sys.path.insert(0, os.path.abspath( 19 | # os.path.join(os.path.dirname(__file__), '..'))) 20 | 21 | from network2tikz import plot 22 | 23 | # Load data into a graph 24 | # ====================== 25 | G = ig.Graph.Read_Ncol('./data/gentoo.txt', directed=False) 26 | #G = nx.read_edgelist('./data/gentoo.txt') 27 | 28 | # Layout setup 29 | # ============ 30 | visual_style = {} 31 | visual_style['node_size'] = .3 32 | visual_style['edge_width'] = 1.1 33 | visual_style['edge_curved'] = 0.1 34 | visual_style["layout"] = 'FR' 35 | visual_style['canvas'] = (20, 20) 36 | visual_style['layout_seed'] = 3 37 | # Plot the network as tex 38 | # ======================= 39 | plot(G, "gentoo.pdf", **visual_style) 40 | 41 | 42 | # ============================================================================= 43 | # eof 44 | # 45 | # Local Variables: 46 | # mode: python 47 | # mode: linum 48 | # mode: auto-fill 49 | # fill-column: 80 50 | # End: 51 | -------------------------------------------------------------------------------- /examples/ex_igraph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_igraph.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example for converting igraph networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | 31 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 32 | 33 | import igraph as ig 34 | from network2tikz import plot 35 | 36 | def main(): 37 | # Network 38 | # ------- 39 | net = ig.Graph([(0,1), (0,2), (2,3), (3,4), (4,2), (2,5), (5,0), (6,3), 40 | (5,6), (6,6)],directed=True) 41 | 42 | # Network attributes 43 | # ------------------ 44 | net.vs["name"] = ["Alice", "Bob", "Claire", "Dennis", "Esther", "Frank", "George"] 45 | net.vs["age"] = [25, 31, 18, 47, 22, 23, 50] 46 | net.vs["gender"] = ["f", "m", "f", "m", "f", "m", "m"] 47 | net.es["is_formal"] = [False, False, True, True, True, False, True, False, 48 | False, False] 49 | # Network dicts 50 | # ------------- 51 | color_dict = {"m": "blue", "f": "red"} 52 | shape_dict = {"m": "circle", "f": "rectangle"} 53 | style_dict = {"m": "{shading=ball}", "f": None} 54 | layout = {0: (4.3191, -3.5352), 1: (0.5292, -0.5292), 55 | 2: (8.6559, -3.8008), 3: (12.4117, -7.5239), 56 | 4: (12.7, -1.7069), 5: (6.0022, -9.0323), 57 | 6: (9.7608, -12.7)} 58 | 59 | # Visual style dict 60 | # ----------------- 61 | visual_style = {} 62 | 63 | # node styles 64 | # ----------- 65 | visual_style['vertex_size'] = 5 66 | visual_style['vertex_color'] = [color_dict[g] for g in net.vs['gender']] 67 | visual_style['vertex_opacity'] = .7 68 | visual_style['vertex_label'] = net.vs['name'] 69 | visual_style['vertex_label_position'] = 'below' 70 | visual_style['vertex_label_distance'] = 15 71 | visual_style['vertex_label_color'] = 'gray' 72 | visual_style['vertex_label_size'] = 3 73 | visual_style['vertex_shape'] = [shape_dict[g] for g in net.vs['gender']] 74 | visual_style['vertex_style'] = [style_dict[g] for g in net.vs['gender']] 75 | visual_style['vertex_label_off'] = {4:True} # vertex e 76 | visual_style['vertex_math_mode'] = [True] 77 | visual_style['vertex_label_as_id'] = {5:True} # vertex f 78 | visual_style['vertex_pseudo'] = {3:True} # vertex d 79 | 80 | # edge styles 81 | # ----------- 82 | visual_style['edge_width'] = [.3 + .3 * int(f) for f in net.es['is_formal']] 83 | visual_style['edge_color'] = 'black' 84 | visual_style['edge_opacity'] = .8 85 | visual_style['edge_curved'] = 0.1 86 | visual_style['edge_label'] = [i for i,e in enumerate(net.es)] 87 | visual_style['edge_label_position'] = 'above' 88 | visual_style['edge_label_distance'] = .6 89 | visual_style['edge_label_color'] = 'gray' 90 | visual_style['edge_label_size'] = {1:5} # edge ac 91 | visual_style['edge_style'] = 'dashed' 92 | visual_style['edge_arrow_size'] = .2 93 | visual_style['edge_arrow_width'] = .2 94 | visual_style['edge_loop_size'] = 15 95 | visual_style['edge_loop_position'] = 90 96 | visual_style['edge_loop_shape'] = 45 97 | visual_style['edge_directed'] = [True,True,False,True,True,False,True, 98 | True,True] 99 | visual_style['edge_label'][1] = '\\frac{\\alpha}{\\beta}' 100 | visual_style['edge_math_mode'] = {1:True} # edge ac 101 | visual_style['edge_not_in_bg'] = {6:True} # edge fa 102 | 103 | # general options 104 | # --------------- 105 | visual_style['unit'] = 'mm' 106 | visual_style['layout'] = layout 107 | visual_style["margin"] = {'top':5,'bottom':8,'left':5,'right':5} 108 | visual_style["canvas"] = (100,60) 109 | visual_style['keep_aspect_ratio'] = False 110 | 111 | # Create a latex file 112 | plot(net,'network.tex',**visual_style) 113 | 114 | # Create a node and edge list used by tikz-network 115 | plot(net,'network.csv',**visual_style) 116 | 117 | # Create pdf figure of the network 118 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 119 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 120 | plot(net,'network.pdf',**visual_style) 121 | 122 | # Create temp pdf and show the output 123 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 124 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 125 | plot(net,**visual_style) 126 | 127 | 128 | if __name__ == '__main__': 129 | main() 130 | 131 | # ============================================================================= 132 | # eof 133 | # 134 | # Local Variables: 135 | # mode: python 136 | # mode: linum 137 | # mode: auto-fill 138 | # fill-column: 80 139 | # End: 140 | -------------------------------------------------------------------------------- /examples/ex_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_list.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example for converting a node/edge list to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | 31 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 32 | 33 | from network2tikz import plot 34 | 35 | def main(): 36 | # Network 37 | # ------- 38 | nodes = ['a','b','c','d','e','f','g'] 39 | edges = [('a','b'), ('a','c'), ('c','d'),('d','e'), ('e','c'), ('c','f'), 40 | ('f','a'), ('f','g'), ('g','d'), ('g','g')] 41 | net = (nodes,edges) 42 | 43 | # Network attributes 44 | # ------------------ 45 | name = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 'George'] 46 | age = [25, 31, 18, 47, 22, 23, 50] 47 | gender = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 48 | is_formal = [False, False, True, True, True, False, True, False, False, False] 49 | 50 | # Network dicts 51 | # ------------- 52 | color_dict = {"m": "blue", "f": "red"} 53 | shape_dict = {"m": "circle", "f": "rectangle"} 54 | style_dict = {"m": "{shading=ball}", "f": None} 55 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 56 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 57 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 58 | 'g': (9.7608, -12.7)} 59 | 60 | # Visual style dict 61 | # ----------------- 62 | visual_style = {} 63 | 64 | # node styles 65 | # ----------- 66 | visual_style['node_size'] = 5 67 | visual_style['node_color'] = [color_dict[g] for g in gender] 68 | visual_style['node_opacity'] = .7 69 | visual_style['node_label'] = name 70 | visual_style['node_label_position'] = 'below' 71 | visual_style['node_label_distance'] = 15 72 | visual_style['node_label_color'] = 'gray' 73 | visual_style['node_label_size'] = 3 74 | visual_style['node_shape'] = [shape_dict[g] for g in gender] 75 | visual_style['node_style'] = [style_dict[g] for g in gender] 76 | visual_style['node_label_off'] = {'e':True} 77 | visual_style['node_math_mode'] = [True] 78 | visual_style['node_label_as_id'] = {'f':True} 79 | visual_style['node_pseudo'] = {'d':True} 80 | 81 | # edge styles 82 | # ----------- 83 | visual_style['edge_width'] = [.3 + .3 * int(f) for f in is_formal] 84 | visual_style['edge_color'] = 'black' 85 | visual_style['edge_opacity'] = .8 86 | visual_style['edge_curved'] = 0.1 87 | visual_style['edge_label'] = {e:e[0]+e[1] for e in net[1]} 88 | visual_style['edge_label_position'] = 'above' 89 | visual_style['edge_label_distance'] = .6 90 | visual_style['edge_label_color'] = 'gray' 91 | visual_style['edge_label_size'] = {('a','c'):5} 92 | visual_style['edge_style'] = 'dashed' 93 | visual_style['edge_arrow_size'] = .2 94 | visual_style['edge_arrow_width'] = .2 95 | 96 | visual_style['edge_loop_size'] = 15 97 | visual_style['edge_loop_position'] = 90 98 | visual_style['edge_loop_shape'] = 45 99 | visual_style['edge_directed'] = [True,True,False,True,True,False,True, 100 | True,True,True] 101 | visual_style['edge_label'][('a','c')] = '\\frac{\\alpha}{\\beta}' 102 | visual_style['edge_math_mode'] = {('a','c'):True} 103 | visual_style['edge_not_in_bg'] = {('f','a'):True} 104 | 105 | # general options 106 | # --------------- 107 | visual_style['unit'] = 'mm' 108 | visual_style['layout'] = layout 109 | visual_style["margin"] = {'top':5,'bottom':8,'left':5,'right':5} 110 | visual_style["canvas"] = (100,60) 111 | visual_style['keep_aspect_ratio'] = False 112 | 113 | # Create a latex file 114 | plot(net,'network.tex',**visual_style) 115 | 116 | # Create a node and edge list used by tikz-network 117 | 118 | # plot(net,'network.csv',**visual_style) 119 | 120 | # Create pdf figure of the network 121 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 122 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 123 | 124 | # plot(net,'network.pdf',**visual_style) 125 | 126 | # Create temp pdf and show the output 127 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 128 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 129 | 130 | # plot(net,**visual_style) 131 | 132 | 133 | if __name__ == '__main__': 134 | main() 135 | 136 | # ============================================================================= 137 | # eof 138 | # 139 | # Local Variables: 140 | # mode: python 141 | # mode: linum 142 | # mode: auto-fill 143 | # fill-column: 80 144 | # End: 145 | -------------------------------------------------------------------------------- /examples/ex_networkx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_networkx.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example for converting igraph networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | 31 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 32 | 33 | import networkx as nx 34 | from network2tikz import plot 35 | 36 | def main(): 37 | # Network and attributes 38 | # ---------------------- 39 | net = nx.DiGraph() 40 | net.add_node('a', name='Alice', age=25, gender='f') 41 | net.add_node('b', name='Bob', age=31, gender='m') 42 | net.add_node('c', name='Claire', age=18, gender='f') 43 | net.add_node('d', name='Dennis', age=47, gender='m') 44 | net.add_node('e', name='Esther', age=22, gender='f') 45 | net.add_node('f', name='Frank', age=23, gender='m') 46 | net.add_node('g', name='George', age=50, gender='m') 47 | 48 | net.add_edge('a','b',is_formal=False) 49 | net.add_edge('a','c',is_formal=False) 50 | net.add_edge('c','d',is_formal=True) 51 | net.add_edge('d','e',is_formal=True) 52 | net.add_edge('e','c',is_formal=True) 53 | net.add_edge('c','f',is_formal=False) 54 | net.add_edge('f','a',is_formal=True) 55 | net.add_edge('f','g',is_formal=False) 56 | net.add_edge('g','g',is_formal=False) 57 | net.add_edge('g','d',is_formal=False) 58 | 59 | # Network dicts 60 | # ------------- 61 | color_dict = {"m": "blue", "f": "red"} 62 | shape_dict = {"m": "circle", "f": "rectangle"} 63 | style_dict = {"m": "{shading=ball}", "f": None} 64 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 65 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 66 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 67 | 'g': (9.7608, -12.7)} 68 | 69 | # Visual style dict 70 | # ----------------- 71 | visual_style = {} 72 | 73 | # node styles 74 | # ----------- 75 | visual_style['vertex_size'] = 5 76 | visual_style['vertex_color'] = {n:color_dict[g] for n,g in nx.get_node_attributes(net,'gender').items()} 77 | visual_style['vertex_opacity'] = .7 78 | visual_style['vertex_label'] = nx.get_node_attributes(net,'name') 79 | visual_style['vertex_label_position'] = 'below' 80 | visual_style['vertex_label_distance'] = 15 81 | visual_style['vertex_label_color'] = 'gray' 82 | visual_style['vertex_label_size'] = 3 83 | visual_style['vertex_shape'] = {n:shape_dict[g] for n,g in nx.get_node_attributes(net,'gender').items()} 84 | visual_style['vertex_style'] = {n:style_dict[g] for n,g in nx.get_node_attributes(net,'gender').items()} 85 | visual_style['vertex_label_off'] = {'e':True} 86 | visual_style['vertex_math_mode'] = {'a':True} 87 | visual_style['vertex_label_as_id'] = {'f':True} 88 | visual_style['vertex_pseudo'] = {'d':True} 89 | 90 | # edge styles 91 | # ----------- 92 | visual_style['edge_width'] = {e:.3 + .3 * int(f) for e,f in nx.get_edge_attributes(net,'is_formal').items()} 93 | visual_style['edge_color'] = 'black' 94 | visual_style['edge_opacity'] = .8 95 | visual_style['edge_curved'] = 0.1 96 | visual_style['edge_label'] = {e:e[0]+e[1] for e in net.edges} 97 | visual_style['edge_label_position'] = 'above' 98 | visual_style['edge_label_distance'] = .6 99 | visual_style['edge_label_color'] = 'gray' 100 | visual_style['edge_label_size'] = {('a','c'):5} 101 | visual_style['edge_style'] = 'dashed' 102 | visual_style['edge_arrow_size'] = .2 103 | visual_style['edge_arrow_width'] = .2 104 | visual_style['edge_loop_size'] = 15 105 | visual_style['edge_loop_position'] = 90 106 | visual_style['edge_loop_shape'] = 45 107 | visual_style['edge_directed'] = {('a','b'):True, ('a','c'):True, 108 | ('c','d'):False, ('d','e'):True, 109 | ('e','c'):True, ('c','f'):False, 110 | ('f','a'):True, ('f','g'):True, 111 | ('g','g'):True} 112 | visual_style['edge_label'][('a','c')] = '\\frac{\\alpha}{\\beta}' 113 | visual_style['edge_math_mode'] = {('a','c'):True} 114 | visual_style['edge_not_in_bg'] = {('f','a'):True} 115 | 116 | # general options 117 | # --------------- 118 | visual_style['unit'] = 'mm' 119 | visual_style['layout'] = layout 120 | visual_style["margin"] = {'top':5,'bottom':8,'left':5,'right':5} 121 | visual_style["canvas"] = (100,60) 122 | visual_style['keep_aspect_ratio'] = False 123 | 124 | # Create a latex file 125 | plot(net,'network.tex',**visual_style) 126 | 127 | # Create a node and edge list used by tikz-network 128 | 129 | # plot(net,'network.csv',**visual_style) 130 | 131 | # Create pdf figure of the network 132 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 133 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 134 | 135 | # plot(net,'network.pdf',**visual_style) 136 | 137 | # Create temp pdf and show the output 138 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 139 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 140 | 141 | # plot(net,**visual_style) 142 | 143 | 144 | if __name__ == '__main__': 145 | main() 146 | 147 | # ============================================================================= 148 | # eof 149 | # 150 | # Local Variables: 151 | # mode: python 152 | # mode: linum 153 | # mode: auto-fill 154 | # fill-column: 80 155 | # End: 156 | -------------------------------------------------------------------------------- /examples/ex_pathpy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : ex_pathpy.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Example for converting pathpy networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import os 29 | import sys 30 | 31 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 32 | 33 | import pathpy as pp 34 | from network2tikz import plot 35 | 36 | def main(): 37 | # Network and attributes 38 | # ---------------------- 39 | net = pp.Network(directed=True) 40 | net.add_node('a', name='Alice', age=25, gender='f') 41 | net.add_node('b', name='Bob', age=31, gender='m') 42 | net.add_node('c', name='Claire', age=18, gender='f') 43 | net.add_node('d', name='Dennis', age=47, gender='m') 44 | net.add_node('e', name='Esther', age=22, gender='f') 45 | net.add_node('f', name='Frank', age=23, gender='m') 46 | net.add_node('g', name='George', age=50, gender='m') 47 | 48 | net.add_edge('a','b',is_formal=False) 49 | net.add_edge('a','c',is_formal=False) 50 | net.add_edge('c','d',is_formal=True) 51 | net.add_edge('d','e',is_formal=True) 52 | net.add_edge('e','c',is_formal=True) 53 | net.add_edge('c','f',is_formal=False) 54 | net.add_edge('f','a',is_formal=True) 55 | net.add_edge('f','g',is_formal=False) 56 | net.add_edge('g','g',is_formal=False) 57 | net.add_edge('g','d',is_formal=False) 58 | 59 | # Network dicts 60 | # ------------- 61 | color_dict = {"m": "blue", "f": "red"} 62 | shape_dict = {"m": "circle", "f": "rectangle"} 63 | style_dict = {"m": "{shading=ball}", "f": None} 64 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 65 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 66 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 67 | 'g': (9.7608, -12.7)} 68 | 69 | # Visual style dict 70 | # ----------------- 71 | visual_style = {} 72 | 73 | # node styles 74 | # ----------- 75 | visual_style['node_size'] = 5 76 | visual_style['node_color'] = {n:color_dict[a['gender']]for n,a in net.nodes.items()} 77 | visual_style['node_opacity'] = .7 78 | visual_style['node_label'] = {n:a['name'] for n,a in net.nodes.items()} 79 | visual_style['node_label_position'] = 'below' 80 | visual_style['node_label_distance'] = 15 81 | visual_style['node_label_color'] = 'gray' 82 | visual_style['node_label_size'] = 3 83 | visual_style['node_shape'] = {n:shape_dict[a['gender']]for n,a in net.nodes.items()} 84 | visual_style['node_style'] = {n:style_dict[a['gender']]for n,a in net.nodes.items()} 85 | visual_style['node_label_off'] = {'e':True} 86 | visual_style['node_math_mode'] = {'a':True} 87 | visual_style['node_label_as_id'] = {'f':True} 88 | visual_style['node_pseudo'] = {'d':True} 89 | 90 | # edge styles 91 | # ----------- 92 | visual_style['edge_width'] = {e:.3 + .3 * int(a['is_formal']) for e,a in net.edges.items()} 93 | visual_style['edge_color'] = 'black' 94 | visual_style['edge_opacity'] = .8 95 | visual_style['edge_curved'] = 0.1 96 | visual_style['edge_label'] = {e:e[0]+e[1] for e in net.edges} 97 | visual_style['edge_label_position'] = 'above' 98 | visual_style['edge_label_distance'] = .6 99 | visual_style['edge_label_color'] = 'gray' 100 | visual_style['edge_label_size'] = {('a','c'):5} 101 | visual_style['edge_style'] = 'dashed' 102 | visual_style['edge_arrow_size'] = .2 103 | visual_style['edge_arrow_width'] = .2 104 | visual_style['edge_loop_size'] = 15 105 | visual_style['edge_loop_position'] = 90 106 | visual_style['edge_loop_shape'] = 45 107 | visual_style['edge_directed'] = {('a','b'):True, ('a','c'):True, 108 | ('c','d'):False, ('d','e'):True, 109 | ('e','c'):True, ('c','f'):False, 110 | ('f','a'):True, ('f','g'):True, 111 | ('g','g'):True} 112 | visual_style['edge_label'][('a','c')] = '\\frac{\\alpha}{\\beta}' 113 | visual_style['edge_math_mode'] = {('a','c'):True} 114 | visual_style['edge_not_in_bg'] = {('f','a'):True} 115 | 116 | # general options 117 | # --------------- 118 | visual_style['unit'] = 'mm' 119 | visual_style['layout'] = layout 120 | visual_style["margin"] = {'top':5,'bottom':8,'left':5,'right':5} 121 | visual_style["canvas"] = (100,60) 122 | visual_style['keep_aspect_ratio'] = False 123 | 124 | # Create a latex file 125 | plot(net,'network.tex',**visual_style) 126 | 127 | # Create a node and edge list used by tikz-network 128 | 129 | # plot(net,'network.csv',**visual_style) 130 | 131 | # Create pdf figure of the network 132 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 133 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 134 | 135 | # plot(net,'network.pdf',**visual_style) 136 | 137 | # Create temp pdf and show the output 138 | # ONLY POSSIBLE IF tikz-network IS INSTALLED 139 | # AND (for Widows OS) COMPLETER HAS TO BE SET RIGHT 140 | 141 | # plot(net,**visual_style) 142 | 143 | 144 | if __name__ == '__main__': 145 | main() 146 | 147 | # ============================================================================= 148 | # eof 149 | # 150 | # Local Variables: 151 | # mode: python 152 | # mode: linum 153 | # mode: auto-fill 154 | # fill-column: 80 155 | # End: 156 | -------------------------------------------------------------------------------- /network2tikz/__about__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : __about__.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : some additional package information 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | __title__ = 'Network to TikZ' 29 | __version__ = '0.1.8' 30 | __author__ = u'Juergen Hackl' 31 | __email__ = 'hackl.j@gmx.at' 32 | __copyright__ = u'Copyright (c) 2018, {} <{}>'.format(__author__, __email__) 33 | __license__ = u'License :: OSI Approved :: GNU General Public License v3 (GPLv3)' 34 | __maintainer__ = u'Juergen Hackl' 35 | __status__ = 'Development Status :: 4 - Beta' 36 | __credits__ = [] 37 | 38 | # ============================================================================= 39 | # eof 40 | # 41 | # Local Variables: 42 | # mode: python 43 | # mode: linum 44 | # mode: auto-fill 45 | # fill-column: 80 46 | # End: 47 | -------------------------------------------------------------------------------- /network2tikz/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : __init__.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : init file for the package 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | from .__about__ import( 29 | __title__, 30 | __version__, 31 | __author__, 32 | __email__, 33 | __copyright__, 34 | __license__, 35 | __maintainer__, 36 | __status__, 37 | __credits__ 38 | ) 39 | 40 | import logging 41 | 42 | 43 | def logger(name, level='INFO'): 44 | """A function to generate logger for the modules.""" 45 | # initialize new logger 46 | logger = logging.getLogger(name) 47 | # set logger level 48 | logger.setLevel(logging._nameToLevel[level]) 49 | return logger 50 | 51 | 52 | from .plot import Plot 53 | from .layout import layout 54 | plot = Plot() 55 | 56 | # ============================================================================= 57 | # eof 58 | # 59 | # Local Variables: 60 | # mode: python 61 | # mode: linum 62 | # mode: auto-fill 63 | # fill-column: 80 64 | # End: 65 | -------------------------------------------------------------------------------- /network2tikz/canvas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : canvas.py 5 | # Creation : 19 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Module to create a canvas for plotting 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | from . import logger 28 | from .exceptions import CnetError 29 | log = logger(__name__) 30 | 31 | # TODO: move to config file 32 | CANVAS = (6, 6) 33 | 34 | 35 | class Canvas(object): 36 | """A canvas object defining the size of the plot. 37 | 38 | Parameters 39 | ---------- 40 | width : int or float, optional (default = 6) 41 | The parameter defines the width of the figure. The width is defined in 42 | cm units. If no width is defined the default value of 6 cm is used. The 43 | default can be changed in the config file. 44 | 45 | height : int or float, optional (default = 6) 46 | The parameter defines the height of the figure. The height is defined in 47 | cm units. If no height is defined the default value of 6 cm is used. The 48 | default can be changed in the config file. 49 | 50 | margins : None, int, float or dict, optional (default = None) 51 | The margins define the 'empty' space from the canvas border. If no 52 | margins are defined, the margin will be calculated based on the maximum 53 | node size, to avoid clipping of the nodes. If a single int or float is 54 | defined all margins using this distances. To define different the margin 55 | sizes for all size a dictionary with in the form of 56 | `{'top':2,'left':1,'bottom':2,'right':.5}` has to be used. 57 | 58 | node_sizes : dict, optional (default = None) 59 | If no specific margins are defined, the margins are based on the maximum 60 | node size, in order to avoid clipping at the boundary of the figure. If 61 | no node_sizes are defined, the default value of tikz-network will be 62 | used. 63 | 64 | Attributes 65 | ---------- 66 | width : int or float 67 | Width of the figure. This property can be called, set and modified. 68 | 69 | height : int or float 70 | Height of the figure. This property can be called, set and modified. 71 | 72 | """ 73 | 74 | def __init__(self, width=None, height=None, margins=None, node_sizes=None): 75 | """Initialize a canvas object. 76 | 77 | Parameters 78 | ---------- 79 | width : int or float, optional (default = 6) 80 | The parameter defines the width of the figure. The width is defined 81 | in cm units. If no width is defined the default value of 6 cm is 82 | used. The default can be changed in the config file. 83 | 84 | height : int or float, optional (default = 6) 85 | The parameter defines the height of the figure. The height is 86 | defined in cm units. If no height is defined the default value of 6 87 | cm is used. The default can be changed in the config file. 88 | 89 | margins : None, int, float or dict, optional (default = None) 90 | The margins define the 'empty' space from the canvas border. If no 91 | margins are defined, the margin will be calculated based on the 92 | maximum node size, to avoid clipping of the nodes. If a single int 93 | or float is defined all margins using this distances. To define 94 | different the margin sizes for all size a dictionary with in the 95 | form of `{'top':2,'left':1,'bottom':2,'right':.5}` has to be used. 96 | 97 | node_sizes : dict, optional (default = None) 98 | If no specific margins are defined, the margins are based on the 99 | maximum node size, in order to avoid clipping at the boundary of the 100 | figure. If no node_sizes are defined, the default value of 101 | tikz-network will be used. 102 | 103 | """ 104 | # apply default values if not defined 105 | if width is None: 106 | width = CANVAS[0] 107 | if height is None: 108 | height = CANVAS[1] 109 | 110 | # initialize variables 111 | self._width = width 112 | self._height = height 113 | self._margins = margins 114 | self._node_sizes = node_sizes 115 | 116 | @property 117 | def width(self): 118 | """Returns the width of the canvas.""" 119 | return self._width 120 | 121 | @width.setter 122 | def width(self, width): 123 | """Set the width of the canvas.""" 124 | self._width = width 125 | 126 | @property 127 | def height(self): 128 | """Returns the height of the canvas.""" 129 | return self._height 130 | 131 | @height.setter 132 | def height(self, height): 133 | """Set the height of the canvas.""" 134 | self._height = height 135 | 136 | def margins(self, margins=None, node_sizes=None): 137 | """Returns a dictionary of margins. 138 | 139 | Parameters 140 | ---------- 141 | margins : None, int, float or dict, optional (default = None) 142 | The margins define the 'empty' space from the canvas border. If no 143 | margins are defined, the margin will be calculated based on the 144 | maximum node size, to avoid clipping of the nodes. If a single int 145 | or float is defined all margins using this distances. To define 146 | different the margin sizes for all size a dictionary with in the 147 | form of `{'top':2,'left':1,'bottom':2,'right':.5}` has to be used. 148 | 149 | node_sizes : dict, optional (default = None) 150 | If no specific margins are defined, the margins are based on the 151 | maximum node size, in order to avoid clipping at the boundary of the 152 | figure. If no node_sizes are defined, the default value of 153 | tikz-network will be used. 154 | 155 | Returns 156 | ------- 157 | margins : dict 158 | Returns the margins of the :py:class:`Canvas` as a dictionary in 159 | from of `{'top':2,'left':1,'bottom':2,'right':.5}`. The values 160 | correspond to cm units. 161 | 162 | Examples 163 | -------- 164 | Default margins if nothing is defined. 165 | 166 | >>> canvas = cn.Canvas() 167 | >>> canvas.margins() 168 | {'top':.35,'left':.35,'bottom':.35,'right':.35} 169 | 170 | Margins defined with one value 171 | 172 | >>> canvas.margins(1) 173 | {'top':1,'left':1,'bottom':1,'right':1} 174 | 175 | Margins as dictionary. 176 | 177 | >>> canvas.margins({'top':2,'left':1,'bottom':2,'right':.5}) 178 | {'top':2,'left':1,'bottom':2,'right':.5} 179 | 180 | Margins defined via max node sizes: 181 | 182 | >>> canvas.margins(margins=None,node_sizes{'a':.3,'b':.5}) 183 | {'top':.3,'left':.3,'bottom':.3,'right':.3} 184 | 185 | """ 186 | 187 | # check if arguments are None 188 | if margins is None and self._margins is not None: 189 | margins = self._margins 190 | if node_sizes is None and self._node_sizes is not None: 191 | node_sizes = self._node_sizes 192 | 193 | # if margins are not defined use max node size to avoid clipping. 194 | if margins is None: 195 | if node_sizes is not None: 196 | _margin = max(node_sizes.values())/2+.05 197 | else: 198 | _margin = 0.35 199 | _margins = {'top': _margin, 'bottom': _margin, 200 | 'left': _margin, 'right': _margin} 201 | 202 | # if only one number is specified, this will be applied to all margins. 203 | elif isinstance(margins, int) or isinstance(margins, float): 204 | _m = margins 205 | _margins = {'top': _m, 'left': _m, 'bottom': _m, 'right': _m} 206 | 207 | # if margins defined as dict, the dict values are used 208 | elif isinstance(margins, dict): 209 | _margins = {'top': margins.get('top', 0), 210 | 'left': margins.get('left', 0), 211 | 'bottom': margins.get('bottom', 0), 212 | 'right': margins.get('right', 0)} 213 | else: 214 | log.error('Margins are not proper defined!') 215 | raise CnetError 216 | 217 | # check size of the margins 218 | if _margins['top'] + _margins['bottom'] >= self.height or \ 219 | _margins['left'] + _margins['right'] >= self.width: 220 | log.error('Margins horizontal {} or vertical {} are larger than the' 221 | ' canvas size ({},{})!' 222 | ''.format(_margins['left'] + _margins['right'], 223 | _margins['top'] + _margins['bottom'], 224 | self.width, self.height)) 225 | raise CnetError 226 | 227 | # update class variables 228 | self._margins = _margins 229 | self._node_sizes = node_sizes 230 | 231 | # return a dict of margins 232 | return _margins 233 | 234 | def fit(self, layout, keep_aspect_ratio=True): 235 | """Fit the node positions to the canvas. 236 | 237 | Parameters 238 | ---------- 239 | layout : dict 240 | A dictionary with the node positions on a 2-dimensional plane. The 241 | key value of the dict represents the node id while the value 242 | represents a tuple of coordinates (e.g. n = (x,y)). The initial 243 | layout can be placed anywhere on the 2-dimensional plane. 244 | 245 | keep_aspect_ratio : bool, optional (default = True) 246 | Defines whether to keep the aspect ratio of the current layout. If 247 | `False`, the layout will be rescaled to fit exactly into the 248 | available area in the canvas (i.e. removed margins). If `True`, the 249 | original aspect ratio of the layout will be kept and it will be 250 | centered within the canvas. 251 | 252 | Returns 253 | ------- 254 | layout : dict 255 | Returns a dictionary with the new node positions. Key values 256 | represents the node ids and the values are the new coordinates. The 257 | new coordinates are shifted and transformed from its origins. 258 | 259 | Examples 260 | -------- 261 | Create empty canvas with no margins and fit simple layout. 262 | 263 | >>> canvas = cn.Canvas(6,4,margins=0) 264 | >>> layout = {'a':(-1,-1),'b':(1,-1),'c':(1,1),'d':(-1,1)} 265 | >>> canvas.fit(layout) 266 | {'a':(1,0),'b':(5,0),'c':(5,4),'d':(1,4)} 267 | 268 | Without keeping the aspect ratio. 269 | 270 | >>> canvas = cn.Canvas(6,4,margins=0) 271 | >>> layout = {'a':(-1,-1),'b':(1,-1),'c':(1,1),'d':(-1,1)} 272 | >>> canvas.fit(layout, keep_aspect_ratio=False) 273 | {'a':(0,0),'b':(6,0),'c':(6,4),'d':(0,4)} 274 | 275 | """ 276 | # get canvas size and margins 277 | width = self.width 278 | height = self.height 279 | margins = self.margins() 280 | 281 | # find min and max values of the points 282 | min_x = min(layout.items(), key=lambda item: item[1][0])[1][0] 283 | max_x = max(layout.items(), key=lambda item: item[1][0])[1][0] 284 | min_y = min(layout.items(), key=lambda item: item[1][1])[1][1] 285 | max_y = max(layout.items(), key=lambda item: item[1][1])[1][1] 286 | 287 | # calculate the scaling ratio 288 | ratio_x = float('inf') 289 | ratio_y = float('inf') 290 | 291 | if max_x-min_x > 0: 292 | ratio_x = (width-margins['left']-margins['right']) / (max_x-min_x) 293 | if max_y-min_y > 0: 294 | ratio_y = (height-margins['top']-margins['bottom']) / (max_y-min_y) 295 | 296 | if keep_aspect_ratio: 297 | scaling = (min(ratio_x, ratio_y), min(ratio_x, ratio_y)) 298 | else: 299 | scaling = (ratio_x, ratio_y) 300 | 301 | if scaling[0] == float('inf'): 302 | scaling = (1, scaling[1]) 303 | if scaling[1] == float('inf'): 304 | scaling = (scaling[0], 1) 305 | 306 | # apply scaling to the points 307 | _layout = {} 308 | for n, (x, y) in layout.items(): 309 | _x = (x)*scaling[0] 310 | _y = (y)*scaling[1] 311 | _layout[n] = (_x, _y) 312 | 313 | # find min and max values of new the points 314 | min_x = min(_layout.items(), key=lambda item: item[1][0])[1][0] 315 | max_x = max(_layout.items(), key=lambda item: item[1][0])[1][0] 316 | min_y = min(_layout.items(), key=lambda item: item[1][1])[1][1] 317 | max_y = max(_layout.items(), key=lambda item: item[1][1])[1][1] 318 | 319 | # calculate the translation 320 | translation = (((width-margins['left']-margins['right'])/2 321 | + margins['left']) - ((max_x-min_x)/2 + min_x), 322 | ((height-margins['top']-margins['bottom'])/2 323 | + margins['bottom']) - ((max_y-min_y)/2 + min_y)) 324 | 325 | # apply translation to the points 326 | for n, (x, y) in _layout.items(): 327 | _x = (x)+translation[0] 328 | _y = (y)+translation[1] 329 | _layout[n] = (_x, _y) 330 | 331 | return _layout 332 | 333 | # ============================================================================= 334 | # eof 335 | # 336 | # Local Variables: 337 | # mode: python 338 | # mode: linum 339 | # mode: auto-fill 340 | # fill-column: 80 341 | # End: 342 | -------------------------------------------------------------------------------- /network2tikz/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : exceptions.py 5 | # Creation : 30 Apr 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Module with the base exceptions 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | 29 | class CnetException(Exception): 30 | """Base class for all cnet specific exceptions.""" 31 | 32 | 33 | class CnetError(CnetException): 34 | """Exception for a serious error in cnet""" 35 | 36 | 37 | class CnetNotImplemented(CnetException): 38 | """Exception for procedure not implemented in cnet.""" 39 | 40 | 41 | # ============================================================================= 42 | # eof 43 | # 44 | # Local Variables: 45 | # mode: python 46 | # mode: linum 47 | # mode: auto-fill 48 | # fill-column: 80 49 | # End: 50 | -------------------------------------------------------------------------------- /network2tikz/layout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : layout.py -- Module to layout the network 5 | # Author : Juergen Hackl 6 | # Creation : 2018-07-26 7 | # Time-stamp: 8 | # 9 | # Copyright (c) 2018 Juergen Hackl 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | # ============================================================================= 24 | import numpy as np 25 | from . import logger 26 | from .exceptions import CnetError, CnetNotImplemented 27 | log = logger(__name__) 28 | 29 | 30 | def layout(network, **kwds): 31 | """Function to generate a layout for the network. 32 | 33 | This function genearates a layout configuration for the nodes in the 34 | network. Thereby, different layouts and options can be chosen. The layout 35 | function is directly included in the plot function or can be separately 36 | called. 37 | 38 | The layout function supports different network types and layout algorithm. 39 | Currently supported networks are: 40 | 41 | * 'cnet', 42 | * 'networkx', 43 | * 'igraph', 44 | * 'pathpy' 45 | * node/edge list 46 | 47 | Currently supported algorithms are: 48 | 49 | * Fruchterman-Reingold force-directed algorithm 50 | * Uniformly at random node positions 51 | 52 | The appearance of the layout can be modified by keyword arguments which will 53 | be explained in more detail below. 54 | 55 | Parameters 56 | ---------- 57 | 58 | network : network object 59 | Network to be drawn. The network can be a 'cnet', 'networkx', 'igraph', 60 | 'pathpy' object, or a tuple of a node list and edge list. 61 | 62 | kwds : keyword arguments, optional (default= no attributes) 63 | Attributes used to modify the appearance of the layout. 64 | For details see below. 65 | 66 | 67 | Keyword arguments used for the layout: 68 | 69 | **Layout:** 70 | 71 | NOTE: All layout arguments can be entered with or without 'layout_' at the 72 | beginning, e.g. 'layout_iterations' is equal to 'iterations' 73 | 74 | - ``layout`` : dict or string , optional (default = None) 75 | A dictionary with the node positions on a 2-dimensional plane. The 76 | key value of the dict represents the node id while the value 77 | represents a tuple of coordinates (e.g. n = (x,y)). The initial 78 | layout can be placed anywhere on the 2-dimensional plane. 79 | 80 | Instead of a dictionary, the algorithm used for the layout can be defined 81 | via a string value. Currently, supported are: 82 | 83 | * Random layout, where the nodes are uniformly at random placed in the 84 | unit square. This algorithm can be enabled with the keywords: 'Random', 85 | 'random', 'rand', or None 86 | 87 | * Fruchterman-Reingold force-directed algorithm. In this algorithm, the 88 | nodes are represented by steel rings and the edges are springs between 89 | them. The attractive force is analogous to the spring force and the 90 | repulsive force is analogous to the electrical force. The basic idea is 91 | to minimize the energy of the system by moving the nodes and changing 92 | the forces between them. This algorithm can be enabled with the 93 | keywords: 'Fruchterman-Reingold', 'fruchterman_reingold', 'fr', 94 | 'spring_layout', 'spring layout', 'FR' 95 | 96 | ==================== ================================================== 97 | Algorithms Keywords 98 | ==================== ================================================== 99 | Random Random, random, rand, None 100 | Fruchterman-Reingold Fruchterman-Reingold, fruchterman_reingold, fr 101 | spring_layout, spring layout, FR 102 | ==================== ================================================== 103 | 104 | - ``force`` : float, optional (default = None) 105 | Optimal distance between nodes. If None the distance is set to 106 | 1/sqrt(n) where n is the number of nodes. Increase this value to move 107 | nodes farther apart. 108 | 109 | - ``positions`` : dict or None optional (default = None) 110 | Initial positions for nodes as a dictionary with node as keys and values 111 | as a coordinate list or tuple. If None, then use random initial 112 | positions. 113 | 114 | - ``fixed`` : list or None, optional (default = None) 115 | Nodes to keep fixed at initial position. 116 | 117 | - ``iterations`` : int, optional (default = 50) 118 | Maximum number of iterations taken 119 | 120 | - ``threshold``: float, optional (default = 1e-4) 121 | Threshold for relative error in node position changes. The iteration 122 | stops if the error is below this threshold. 123 | 124 | - ``weight`` : string or None, optional (default = None) 125 | The edge attribute that holds the numerical value used for the edge 126 | weight. If None, then all edge weights are 1. 127 | 128 | - ``dimension`` : int, optional (default = 2) 129 | Dimension of layout. Currently, only plots in 2 dimension are supported. 130 | 131 | - ``seed`` : int or None, optional (default = None) 132 | Set the random state for deterministic node layouts. If int, `seed` is 133 | the seed used by the random number generator, if None, the a random seed 134 | by created by the numpy random number generator is used. 135 | 136 | In the layout style dictionary multiple keywords can be used to address 137 | attributes. These keywords will be converted to an unique key word, 138 | used in the remaining code. 139 | 140 | ========= ================================= 141 | keys other valid keys 142 | ========= ================================= 143 | fixed fixed_nodes, fixed_vertices, 144 | fixed_n, fixed_v 145 | positions initial_positions, node_positions 146 | vertex_positions, n_positions, 147 | v_positions 148 | ========= ================================= 149 | 150 | Examples 151 | -------- 152 | 153 | For illustration purpose a similar network as in the python-igrap tutorial 154 | is used. Instead of igraph, the cnet module is used for creating the 155 | network. 156 | 157 | Create an empty network object, and add some edges. 158 | 159 | >>> net = Network(name = 'my tikz test network',directed=True) 160 | >>> net.add_edges_from([('ab','a','b'), ('ac','a','c'), ('cd','c','d'), 161 | >>> ('de','d','e'), ('ec','e','c'), ('cf','c','f'), 162 | >>> ('fa','f','a'), ('fg','f','g'),('gg','g','g'), 163 | >>> ('gd','g','d')]) 164 | 165 | Now a layout can be generated: 166 | 167 | >>> layout(net) 168 | {'b': array([0.88878309, 0.15685131]), 'd': array([0.4659341 , 0.79839535]), 169 | 'c': array([0.60386662, 0.40727962]), 'e': array([0.71073353, 0.65608203]), 170 | 'g': array([0.42663927, 0.47412449]), 'f': array([0.48759769, 0.86787594]), 171 | 'a': array([0.84154488, 0.1633732 ])} 172 | 173 | Per default, the node positions are assigned uniform random. In order to 174 | create a layout, the layout methods of the packages can be used, or the 175 | position of the nodes can be directly assigned, in form of a dictionary, 176 | where the key is the node id and the value is a tuple of the node position 177 | in x and y. 178 | 179 | Let us generate a force directed layout (e.g. Fruchterman-Reingold): 180 | 181 | >>> layout(net, layout='fr') 182 | {'g': array([-0.77646408, 1.71291126]), 'c': array([-0.18639655,0.96232326]), 183 | 'f': array([0.33394308, 0.93778681]), 'e': array([0.09740098, 1.28511973]), 184 | 'a': array([1.37933158, 0.23171857]), 'b': array([ 2.93561876,-0.46183461]), 185 | 'd': array([-0.29329793, 1.48971303])} 186 | 187 | Note, instead of the command ``fr`` also the command 188 | ``Fruchterman-Reingold`` or any other command mentioned above can be 189 | used. For more information see table above. 190 | 191 | In order to keep the properties of the layout for your network separate from 192 | the network itself, you can simply set up a Python dictionary containing the 193 | keyword arguments you would pass to :py:meth:`layout` and then use the 194 | double asterisk (**) operator to pass your specific layout attributes to 195 | :py:meth:`layout`: 196 | 197 | >>> layout_style = {} 198 | >>> layout_style['layout'] = 'Fruchterman-Reingold' 199 | >>> layout_style['seed'] = 1 200 | >>> layout_style['iterations'] = 100 201 | >>> layout(net,**layout_style) 202 | {'d': array([-0.31778276, 1.78246882]), 'f': array([-0.8603259, 0.82328291]), 203 | 'c': array([-0.4423771 , 1.21203895]), 'e': array([-0.79934355, 1.49000119]), 204 | 'g': array([0.43694799, 1.51428788]), 'a': array([-2.15517293, 0.23948823]), 205 | 'b': array([-3.84803812, -0.71628417])} 206 | 207 | """ 208 | # initialize variables 209 | _weight = kwds.get('weight', None) 210 | if _weight is None: 211 | _weight = kwds.get('layout_weight', None) 212 | 213 | # check type of network 214 | if 'cnet' in str(type(network)): 215 | # log.debug('The network is of type "cnet".') 216 | nodes = list(network.nodes) 217 | adjacency_matrix = network.adjacency_matrix(weight=_weight) 218 | 219 | elif 'networkx' in str(type(network)): 220 | # log.debug('The network is of type "networkx".') 221 | nodes = list(network.nodes()) 222 | import networkx as nx 223 | adjacency_matrix = nx.adjacency_matrix(network, weight=_weight) 224 | elif 'igraph' in str(type(network)): 225 | # log.debug('The network is of type "igraph".') 226 | nodes = list(range(len(network.vs))) 227 | from scipy.sparse import coo_matrix 228 | A = np.array(network.get_adjacency(attribute=_weight).data) 229 | adjacency_matrix = coo_matrix(A) 230 | elif 'pathpy' in str(type(network)): 231 | # log.debug('The network is of type "pathpy".') 232 | nodes = list(network.nodes) 233 | if _weight is not None: 234 | _w = True 235 | else: 236 | _w = False 237 | adjacency_matrix = network.adjacency_matrix(weighted=_w) 238 | # elif isinstance(network, tuple): 239 | # # log.debug('The network is of type "list".') 240 | # nodes = network[0] 241 | # from collections import OrderedDict 242 | # edges = OrderedDict() 243 | # for e in network[1]: 244 | # edges[e] = e 245 | 246 | else: 247 | log.error('Type of the network could not be determined.' 248 | ' Currently only "cnet", "networkx","igraph", "pathpy"' 249 | ' and "node/edge list" is supported!') 250 | raise CnetNotImplemented 251 | 252 | # create layout class 253 | layout = Layout(nodes, adjacency_matrix, **kwds) 254 | # return the layout 255 | return layout.generate_layout() 256 | 257 | 258 | class Layout(object): 259 | """Default class to create layouts 260 | 261 | The :py:class:`Layout` class is used to generate node a layout drawer and 262 | return the calculated node positions as a dictionary, where the keywords 263 | represents the node ids and the values represents a two dimensional tuple 264 | with the x and y coordinates for the associated nodes. 265 | 266 | Parameters 267 | ---------- 268 | nodes : list with node ids 269 | The list contain a list of unique node ids. 270 | 271 | attr : keyword arguments, optional (default = no attributes) 272 | Attributes to add to node as key=value pairs. 273 | See also :py:meth:`layout` 274 | 275 | See Also 276 | -------- 277 | layout 278 | 279 | """ 280 | 281 | def __init__(self, nodes, adjacency_matrix, **attr): 282 | """Initialize the Layout class 283 | 284 | The :py:class:`Layout` class is used to generate node a layout drawer 285 | and return the calculated node positions as a dictionary, where the 286 | keywords represents the node ids and the values represents a two 287 | dimensional tuple with the x and y coordinates for the associated nodes. 288 | 289 | Parameters 290 | ---------- 291 | nodes : list with node ids 292 | The list contain a list of unique node ids. 293 | 294 | attr : keyword arguments, optional (default = no attributes) 295 | Attributes to add to node as key=value pairs. 296 | See also :py:meth:`layout` 297 | 298 | """ 299 | 300 | # initialize variables 301 | self.nodes = nodes 302 | self.adjacency_matrix = adjacency_matrix 303 | 304 | # rename the attributes 305 | attr = self.rename_attributes(**attr) 306 | 307 | # options for the layouts 308 | self.layout_type = attr.get('layout', None) 309 | self.k = attr.get('force', None,) 310 | self.fixed = attr.get('fixed', None) 311 | self.iterations = attr.get('iterations', 50) 312 | self.threshold = attr.get('threshold', 1e-4) 313 | self.weight = attr.get('weight', None) 314 | self.dimension = attr.get('dimension', 2) 315 | self.seed = attr.get('seed', None) 316 | self.positions = attr.get('positions', None) 317 | 318 | # TODO: allow also higher dimensional layouts 319 | if self.dimension != 2: 320 | log.warning('Currently only plots with dimension 2 are supported!') 321 | self.dimension = 2 322 | 323 | @staticmethod 324 | def rename_attributes(**kwds): 325 | """Rename layout attributes. 326 | 327 | In the style dictionary multiple keywords can be used to address 328 | attributes. These keywords will be converted to an unique key word, 329 | used in the remaining code. 330 | 331 | ========= ================================= 332 | keys other valid keys 333 | ========= ================================= 334 | fixed fixed_nodes, fixed_vertices, 335 | fixed_n, fixed_v 336 | positions initial_positions, node_positions 337 | vertex_positions, n_positions, 338 | v_positions 339 | ========= ================================= 340 | 341 | """ 342 | names = {'fixed': ['fixed_nodes', 'fixed_vertices', 343 | 'fixed_v', 'fixed_n'], 344 | 'positions': ['initial_positions', 'node_positions', 345 | 'vertex_positions', 'n_positions', 346 | 'v_positions'], 347 | 'layout_': ['layout_'], 348 | } 349 | 350 | _kwds = {} 351 | del_keys = [] 352 | for key, value in kwds.items(): 353 | for attr, name_list in names.items(): 354 | for name in name_list: 355 | if name in key and name[0] == key[0]: 356 | _kwds[key.replace(name, attr).replace( 357 | 'layout_', '')] = value 358 | del_keys.append(key) 359 | break 360 | # remove the replaced keys from the dict 361 | for key in del_keys: 362 | del kwds[key] 363 | 364 | return {**_kwds, **kwds} 365 | 366 | def generate_layout(self): 367 | """Function to pick and generate the right layout.""" 368 | # method names 369 | names_rand = ['Random', 'random', 'rand', None] 370 | names_fr = ['Fruchterman-Reingold', 'fruchterman_reingold', 'fr', 371 | 'spring_layout', 'spring layout', 'FR'] 372 | # check which layout should be plotted 373 | if self.layout_type in names_rand: 374 | self.layout = self.random() 375 | elif self.layout_type in names_fr: 376 | self.layout = self.fruchterman_reingold() 377 | 378 | # print(self.layout) 379 | return self.layout 380 | 381 | def random(self): 382 | """Position nodes uniformly at random in the unit square. 383 | 384 | For every node, a position is generated by choosing each of dimension 385 | coordinates uniformly at random on the interval [0.0, 1.0). 386 | 387 | This algorithm can be enabled with the keywords: 'Random', 388 | 'random', 'rand', or None 389 | 390 | NumPy (http://scipy.org) is required for this function. 391 | 392 | **Keyword arguments used for the layout:** 393 | 394 | - ``dimension`` : int, optional (default = 2) 395 | Dimension of layout. Currently, only plots in 2 dimension are supported. 396 | 397 | - ``seed`` : int or None, optional (default = None) 398 | Set the random state for deterministic node layouts. If int, `seed` is 399 | the seed used by the random number generator, if None, the a random 400 | seed by created by the numpy random number generator is used. 401 | 402 | Returns 403 | ------- 404 | layout : dict 405 | A dictionary of positions keyed by node 406 | 407 | """ 408 | np.random.seed(self.seed) 409 | layout = np.random.rand(len(self.nodes), self.dimension) 410 | return dict(zip(self.nodes, layout)) 411 | 412 | def fruchterman_reingold(self): 413 | """Position nodes using Fruchterman-Reingold force-directed algorithm. 414 | 415 | In this algorithm, the nodes are represented by steel rings and the 416 | edges are springs between them. The attractive force is analogous to the 417 | spring force and the repulsive force is analogous to the electrical 418 | force. The basic idea is to minimize the energy of the system by moving 419 | the nodes and changing the forces between them. 420 | 421 | This algorithm can be enabled with the keywords: 'Fruchterman-Reingold', 422 | 'fruchterman_reingold', 'fr', 'spring_layout', 'spring layout', 'FR' 423 | 424 | **Keyword arguments used for the layout:** 425 | 426 | - ``force`` : float, optional (default = None) 427 | Optimal distance between nodes. If None the distance is set to 428 | 1/sqrt(n) where n is the number of nodes. Increase this value to move 429 | nodes farther apart. 430 | 431 | - ``positions`` : dict or None optional (default = None) 432 | Initial positions for nodes as a dictionary with node as keys and values 433 | as a coordinate list or tuple. If None, then use random initial 434 | positions. 435 | 436 | - ``fixed`` : list or None, optional (default = None) 437 | Nodes to keep fixed at initial position. 438 | 439 | - ``iterations`` : int, optional (default = 50) 440 | Maximum number of iterations taken 441 | 442 | - ``threshold``: float, optional (default = 1e-4) 443 | Threshold for relative error in node position changes. The iteration 444 | stops if the error is below this threshold. 445 | 446 | - ``weight`` : string or None, optional (default = None) 447 | The edge attribute that holds the numerical value used for the edge 448 | weight. If None, then all edge weights are 1. 449 | 450 | - ``dimension`` : int, optional (default = 2) 451 | Dimension of layout. Currently, only plots in 2 dimension are supported. 452 | 453 | - ``seed`` : int or None, optional (default = None) 454 | Set the random state for deterministic node layouts. If int, `seed` is 455 | the seed used by the random number generator, if None, the a random seed 456 | by created by the numpy random number generator is used. 457 | 458 | Returns 459 | ------- 460 | layout : dict 461 | A dictionary of positions keyed by node 462 | 463 | """ 464 | 465 | # convert adjacency matrix 466 | self.adjacency_matrix = self.adjacency_matrix.astype(float) 467 | 468 | if self.fixed is not None: 469 | self.fixed = np.asarray([self.nodes.index(v) for v in self.fixed]) 470 | 471 | if self.positions is not None: 472 | # Determine size of existing domain to adjust initial positions 473 | _size = max(coord for t in layout.values() for coord in t) 474 | if _size == 0: 475 | _size = 1 476 | np.random.seed(self.seed) 477 | self.layout = np.random.rand( 478 | len(self.nodes), self.dimension) * _size 479 | 480 | for i, n in enumerate(self.nodes): 481 | if n in self.positions: 482 | self.layout[i] = np.asarray(self.positions[n]) 483 | else: 484 | self.layout = None 485 | 486 | if self.k is None and self.fixed is not None: 487 | # We must adjust k by domain size for layouts not near 1x1 488 | self.k = _size / np.sqrt(len(self.nodes)) 489 | 490 | try: 491 | # Sparse matrix 492 | if len(self.nodes) < 500: # sparse solver for large graphs 493 | raise ValueError 494 | layout = self._sparse_fruchterman_reingold() 495 | except: 496 | layout = self._fruchterman_reingold() 497 | 498 | layout = dict(zip(self.nodes, layout)) 499 | 500 | return layout 501 | 502 | def _fruchterman_reingold(self): 503 | """Fruchterman-Reingold algorithm for dense matrices. 504 | 505 | This algorithm is based on the Fruchterman-Reingold algorithm provided 506 | by networkx. (Copyright (C) 2004-2018 by Aric Hagberg 507 | Dan Schult Pieter Swart Richard 508 | Penney All rights reserved. BSD 509 | license.) 510 | 511 | """ 512 | A = self.adjacency_matrix.todense() 513 | k = self.k 514 | try: 515 | _n, _ = A.shape 516 | except AttributeError: 517 | log.error('Fruchterman-Reingold algorithm needs an adjacency ' 518 | 'matrix as input') 519 | raise CnetError 520 | 521 | # make sure we have an array instead of a matrix 522 | A = np.asarray(A) 523 | 524 | if self.layout is None: 525 | # random initial positions 526 | np.random.seed(self.seed) 527 | layout = np.asarray(np.random.rand( 528 | _n, self.dimension), dtype=A.dtype) 529 | else: 530 | # make sure positions are of same type as matrix 531 | layout = self.layout.astype(A.dtype) 532 | 533 | # optimal distance between nodes 534 | if k is None: 535 | k = np.sqrt(1.0 / _n) 536 | # the initial "temperature" is about .1 of domain area (=1x1) 537 | # this is the largest step allowed in the dynamics. 538 | # We need to calculate this in case our fixed positions force our domain 539 | # to be much bigger than 1x1 540 | t = max(max(layout.T[0]) - min(layout.T[0]), 541 | max(layout.T[1]) - min(layout.T[1])) * 0.1 542 | # simple cooling scheme. 543 | # linearly step down by dt on each iteration so last iteration is size dt. 544 | dt = t / float(self.iterations + 1) 545 | delta = np.zeros( 546 | (layout.shape[0], layout.shape[0], layout.shape[1]), dtype=A.dtype) 547 | # the inscrutable (but fast) version 548 | # this is still O(V^2) 549 | # could use multilevel methods to speed this up significantly 550 | for iteration in range(self.iterations): 551 | # matrix of difference between points 552 | delta = layout[:, np.newaxis, :] - layout[np.newaxis, :, :] 553 | # distance between points 554 | distance = np.linalg.norm(delta, axis=-1) 555 | # enforce minimum distance of 0.01 556 | np.clip(distance, 0.01, None, out=distance) 557 | # displacement "force" 558 | displacement = np.einsum('ijk,ij->ik', 559 | delta, 560 | (k * k / distance**2 - A * distance / k)) 561 | # update layoutitions 562 | length = np.linalg.norm(displacement, axis=-1) 563 | length = np.where(length < 0.01, 0.1, length) 564 | delta_layout = np.einsum('ij,i->ij', displacement, t / length) 565 | if self.fixed is not None: 566 | # don't change positions of fixed nodes 567 | delta_layout[self.fixed] = 0.0 568 | layout += delta_layout 569 | # cool temperature 570 | t -= dt 571 | error = np.linalg.norm(delta_layout) / _n 572 | if error < self.threshold: 573 | break 574 | return layout 575 | 576 | def _sparse_fruchterman_reingold(self): 577 | """Fruchterman-Reingold algorithm for sparse matrices. 578 | 579 | This algorithm is based on the Fruchterman-Reingold algorithm provided 580 | by networkx. (Copyright (C) 2004-2018 by Aric Hagberg 581 | Dan Schult Pieter Swart Richard 582 | Penney All rights reserved. BSD 583 | license.) 584 | 585 | """ 586 | A = self.adjacency_matrix 587 | k = self.k 588 | try: 589 | _n, _ = A.shape 590 | except AttributeError: 591 | log.error('Fruchterman-Reingold algorithm needs an adjacency ' 592 | 'matrix as input') 593 | raise CnetError 594 | try: 595 | from scipy.sparse import spdiags, coo_matrix 596 | except ImportError: 597 | log.error('The sparse Fruchterman-Reingold algorithm needs the ' 598 | 'scipy package: http://scipy.org/') 599 | raise ImportError 600 | # make sure we have a LIst of Lists representation 601 | try: 602 | A = A.tolil() 603 | except: 604 | A = (coo_matrix(A)).tolil() 605 | 606 | if self.layout is None: 607 | # random initial positions 608 | np.random.seed(self.seed) 609 | layout = np.asarray(np.random.rand( 610 | _n, self.dimension), dtype=A.dtype) 611 | else: 612 | # make sure positions are of same type as matrix 613 | layout = layout.astype(A.dtype) 614 | 615 | # no fixed nodes 616 | if self.fixed is None: 617 | self.fixed = [] 618 | 619 | # optimal distance between nodes 620 | if k is None: 621 | k = np.sqrt(1.0 / _n) 622 | # the initial "temperature" is about .1 of domain area (=1x1) 623 | # this is the largest step allowed in the dynamics. 624 | t = max(max(layout.T[0]) - min(layout.T[0]), 625 | max(layout.T[1]) - min(layout.T[1])) * 0.1 626 | # simple cooling scheme. 627 | # linearly step down by dt on each iteration so last iteration is size dt. 628 | dt = t / float(self.iterations + 1) 629 | 630 | displacement = np.zeros((self.dimension, _n)) 631 | for iteration in range(self.iterations): 632 | displacement *= 0 633 | # loop over rows 634 | for i in range(A.shape[0]): 635 | if i in self.fixed: 636 | continue 637 | # difference between this row's node position and all others 638 | delta = (layout[i] - layout).T 639 | # distance between points 640 | distance = np.sqrt((delta**2).sum(axis=0)) 641 | # enforce minimum distance of 0.01 642 | distance = np.where(distance < 0.01, 0.01, distance) 643 | # the adjacency matrix row 644 | Ai = np.asarray(A.getrowview(i).toarray()) 645 | # displacement "force" 646 | displacement[:, i] +=\ 647 | (delta * (k * k / distance**2 - Ai * distance / k)).sum(axis=1) 648 | # update positions 649 | length = np.sqrt((displacement**2).sum(axis=0)) 650 | length = np.where(length < 0.01, 0.1, length) 651 | delta_layout = (displacement * t / length).T 652 | layout += delta_layout 653 | # cool temperature 654 | t -= dt 655 | err = np.linalg.norm(delta_layout) / _n 656 | if err < self.threshold: 657 | break 658 | return layout 659 | 660 | 661 | # ============================================================================= 662 | # eof 663 | # 664 | # Local Variables: 665 | # mode: python 666 | # mode: linum 667 | # mode: auto-fill 668 | # fill-column: 80 669 | # End: 670 | -------------------------------------------------------------------------------- /network2tikz/units.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : unit.py 5 | # Creation : 08 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Module to convert measurement units 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | from . import logger 29 | from .exceptions import CnetNotImplemented, CnetError 30 | log = logger(__name__) 31 | 32 | 33 | class UnitConverter(object): 34 | """Convert units. 35 | 36 | Parameters 37 | ---------- 38 | input_unit : str, optional (default = 'cm') 39 | Unit which should be converted. The abbreviation of the unit is entered 40 | as string value. Currently supported are: Pixel 'px', Points 'pt', 41 | Millimeters 'mm', and Centimeters 'cm'. 42 | 43 | output_unit : str, optional (default = 'cm') 44 | Unit to which should be converted. The abbreviation of the unit is 45 | entered as string value. Currently supported are: Pixel 'px', Points 46 | 'pt', Millimeters 'mm', and Centimeters 'cm'. 47 | 48 | digits : int, optional (default = 4) 49 | Number of digits to round the returning measure. Per default the 50 | measures are rounded to 4 digits. 51 | 52 | Examples 53 | -------- 54 | >>> mm2cm = cn.UnitConverter('mm','cm') 55 | >>> mm2cm(10) 56 | 1 57 | 58 | """ 59 | 60 | def __init__(self, input_unit='cm', output_unit='cm', digits=4): 61 | """Initialize the unit converter. 62 | 63 | Parameters 64 | ---------- 65 | input_unit : str, optional (default = 'cm') 66 | Unit which should be converted. The abbreviation of the unit is 67 | entered as string value. Currently supported are: Pixel 'px', Points 68 | 'pt', Millimeters 'mm', and Centimeters 'cm'. 69 | 70 | output_unit : str, optional (default = 'cm') 71 | Unit to which should be converted. The abbreviation of the unit is 72 | entered as string value. Currently supported are: Pixel 'px', Points 73 | 'pt', Millimeters 'mm', and Centimeters 'cm'. 74 | 75 | digits : int, optional (default = 4) 76 | Number of digits to round the returning measure. Per default the 77 | measures are rounded to 4 digits. 78 | """ 79 | self.input_unit = input_unit 80 | self.output_unit = output_unit 81 | self.digits = digits 82 | 83 | def __call__(self, value): 84 | """Returns the converted measure. 85 | 86 | Returns 87 | ------- 88 | measure : float 89 | Returns the converted measure. 90 | 91 | Examples 92 | -------- 93 | >>> mm2cm = cn.UnitConverter('mm','cm') 94 | >>> mm2cm(10) 95 | 1 96 | 97 | """ 98 | return self.convert(value) 99 | 100 | @staticmethod 101 | def px_to_mm(measure): 102 | """Convert pixel to millimeters.""" 103 | return measure * 0.26458333333719 104 | 105 | @staticmethod 106 | def px_to_pt(measure): 107 | """Convert pixel to points.""" 108 | return measure * 0.75 109 | 110 | @staticmethod 111 | def pt_to_mm(measure): 112 | """Convert points to millimeters.""" 113 | return measure * 0.352778 114 | 115 | @staticmethod 116 | def mm_to_px(measure): 117 | """Convert millimeters to pixel.""" 118 | return measure * 3.779527559 119 | 120 | @staticmethod 121 | def mm_to_pt(measure): 122 | """Convert millimeters to points.""" 123 | return measure * 2.83465 124 | 125 | def convert(self, value): 126 | """Returns the converted measure. 127 | 128 | Returns 129 | ------- 130 | measure : float 131 | Returns the converted measure. 132 | 133 | Examples 134 | -------- 135 | >>> mm2cm = cn.UnitConverter('mm','cm') 136 | >>> mm2cm.convert(10) 137 | 1 138 | 139 | """ 140 | try: 141 | measure = float(value) 142 | except: 143 | log.error('Value "{}" is not a number, and therefor can not' 144 | ' converted to an other unit!.'.format(value)) 145 | raise CnetError 146 | 147 | # to cm 148 | if self.input_unit == 'mm' and self.output_unit == 'cm': 149 | value = measure/10 150 | elif self.input_unit == 'pt' and self.output_unit == 'cm': 151 | value = self.pt_to_mm(measure)/10 152 | elif self.input_unit == 'px' and self.output_unit == 'cm': 153 | value = self.px_to_mm(measure)/10 154 | elif self.input_unit == 'cm' and self.output_unit == 'cm': 155 | value = measure 156 | # to pt 157 | elif self.input_unit == 'px' and self.output_unit == 'pt': 158 | value = self.px_to_pt(measure) 159 | elif self.input_unit == 'mm' and self.output_unit == 'pt': 160 | value = self.mm_to_pt(measure) 161 | elif self.input_unit == 'cm' and self.output_unit == 'pt': 162 | value = self.mm_to_pt(measure)*10 163 | elif self.input_unit == 'pt' and self.output_unit == 'pt': 164 | value = measure 165 | # to px 166 | elif self.input_unit == 'mm' and self.output_unit == 'px': 167 | value = self.mm_to_px(measure) 168 | elif self.input_unit == 'cm' and self.output_unit == 'px': 169 | value = self.mm_to_px(10*measure) 170 | elif self.input_unit == 'pt' and self.output_unit == 'px': 171 | value = measure*4/3 172 | elif self.input_unit == 'px' and self.output_unit == 'px': 173 | value = measure 174 | else: 175 | log.error('The conversion from "{}" to "{}" is currently not ' 176 | 'supported!'.format(self.input_unit, 177 | self.output_unit)) 178 | raise CnetNotImplemented 179 | 180 | # return the converted measure 181 | return round(value, self.digits) 182 | 183 | # ============================================================================= 184 | # eof 185 | # 186 | # Local Variables: 187 | # mode: python 188 | # mode: linum 189 | # mode: auto-fill 190 | # fill-column: 80 191 | # End: 192 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : setup.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Setup script for network2tikz 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | import os 28 | from setuptools import setup, find_packages 29 | 30 | about = {} 31 | base_dir = os.path.abspath(os.path.dirname(__file__)) 32 | with open(os.path.join(base_dir, 'network2tikz', '__about__.py'), 'rb') as f: 33 | exec(f.read(), about) 34 | 35 | with open('README.md') as f: 36 | readme = f.read() 37 | 38 | with open('LICENSE') as f: 39 | license = f.read() 40 | 41 | setup( 42 | name='network2tikz', 43 | version=about['__version__'], 44 | packages=find_packages(), 45 | url='https://github.com/hackl/network2tikz', 46 | download_url = 'https://pypi.org/project/network2tikz', 47 | author=about['__author__'], 48 | author_email=about['__email__'], 49 | install_requires=['numpy'], 50 | description='A converter that takes a network (cnet, igraph, networkx, pathpy, ...) and creates a tikz-network for smooth integration into LaTeX.', 51 | long_description = readme, 52 | long_description_content_type='text/markdown', 53 | license = about['__license__'], 54 | classifiers=[ 55 | about['__status__'], 56 | about['__license__'], 57 | 'Operating System :: OS Independent', 58 | 'Programming Language :: Python', 59 | 'Programming Language :: Python :: 3', 60 | 'Intended Audience :: Science/Research', 61 | 'Topic :: Multimedia :: Graphics :: Graphics Conversion', 62 | 'Topic :: Scientific/Engineering :: Visualization', 63 | ] 64 | ) 65 | 66 | # ============================================================================= 67 | # eof 68 | # 69 | # Local Variables: 70 | # mode: python 71 | # mode: linum 72 | # mode: auto-fill 73 | # fill-column: 80 74 | # End: 75 | -------------------------------------------------------------------------------- /tests/test_cnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_cnet.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting cnet networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | import cnet as cn 36 | from network2tikz import plot, layout 37 | 38 | 39 | @pytest.fixture 40 | def net(): 41 | net = cn.Network(name='my tikz test network', directed=True) 42 | net.add_edges_from([('ab', 'a', 'b'), ('ac', 'a', 'c'), ('cd', 'c', 'd'), 43 | ('de', 'd', 'e'), ('ec', 'e', 'c'), ('cf', 'c', 'f'), 44 | ('fa', 'f', 'a'), ('fg', 'f', 'g'), ('gd', 'g', 'd'), 45 | ('gg', 'g', 'g')]) 46 | 47 | net.nodes['name'] = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 48 | 'George'] 49 | net.nodes['age'] = [25, 31, 18, 47, 22, 23, 50] 50 | net.nodes['gender'] = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 51 | 52 | net.edges['is_formal'] = [False, False, True, True, True, False, True, 53 | False, False, False] 54 | return net 55 | 56 | 57 | @pytest.fixture 58 | def color_dict(): 59 | return {"m": "blue", "f": "red"} 60 | 61 | 62 | @pytest.fixture 63 | def shape_dict(): 64 | return {"m": "circle", "f": "rectangle"} 65 | 66 | 67 | @pytest.fixture 68 | def style_dict(): 69 | return {"m": "{shading=ball}", "f": None} 70 | 71 | 72 | @pytest.fixture 73 | def _layout(): 74 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 75 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 76 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 77 | 'g': (9.7608, -12.7)} 78 | return layout 79 | 80 | 81 | def test_plot(net, _layout, color_dict): 82 | 83 | # plot(net) # plot_01.png 84 | 85 | # plot(net,layout=layout) # plot_02.png 86 | 87 | # plot(net, layout=layout, canvas=(8,8), margin=1) # plot_03.png 88 | 89 | visual_style = {} 90 | visual_style['layout'] = _layout 91 | visual_style['node_size'] = .5 92 | visual_style['node_color'] = [color_dict[g] for g in net.nodes('gender')] 93 | visual_style['node_opacity'] = .7 94 | visual_style['node_label'] = net.nodes['name'] 95 | visual_style['node_label_position'] = 'below' 96 | visual_style['edge_width'] = [ 97 | 1 + 2 * int(f) for f in net.edges('is_formal')] 98 | visual_style['edge_curved'] = 0.1 99 | visual_style['canvas'] = (8, 8) 100 | visual_style['margin'] = 1 101 | 102 | plot(net, 'network.tex', **visual_style) 103 | 104 | plot(net, 'network.csv', **visual_style) 105 | 106 | plot(net, 'network.pdf', **visual_style) 107 | 108 | plot(net, **visual_style) 109 | 110 | 111 | def test_plot_all_options(net, _layout, color_dict, shape_dict, style_dict): 112 | 113 | visual_style = {} 114 | # node styles 115 | # ----------- 116 | visual_style['node_size'] = 5 117 | visual_style['node_color'] = [color_dict[g] for g in net.nodes('gender')] 118 | visual_style['node_opacity'] = .7 119 | visual_style['node_label'] = net.nodes['name'] 120 | visual_style['node_label_position'] = 'below' 121 | visual_style['node_label_distance'] = 15 122 | visual_style['node_label_color'] = 'gray' 123 | visual_style['node_label_size'] = 3 124 | visual_style['node_shape'] = [shape_dict[g] for g in net.nodes('gender')] 125 | visual_style['node_style'] = [style_dict[g] for g in net.nodes('gender')] 126 | visual_style['node_label_off'] = {'e': True} 127 | visual_style['node_math_mode'] = [True] 128 | visual_style['node_label_as_id'] = {'f': True} 129 | visual_style['node_pseudo'] = {'d': True} 130 | 131 | # edge styles 132 | # ----------- 133 | visual_style['edge_width'] = [.3 + .3 * 134 | int(f) for f in net.edges('is_formal')] 135 | visual_style['edge_color'] = 'black' 136 | visual_style['edge_opacity'] = .8 137 | visual_style['edge_curved'] = 0.1 138 | visual_style['edge_label'] = [e for e in net.edges] 139 | visual_style['edge_label_position'] = 'above' 140 | visual_style['edge_label_distance'] = .6 141 | visual_style['edge_label_color'] = 'gray' 142 | visual_style['edge_label_size'] = {'ac': 5} 143 | visual_style['edge_style'] = 'dashed' 144 | visual_style['edge_arrow_size'] = .2 145 | visual_style['edge_arrow_width'] = .2 146 | 147 | visual_style['edge_loop_size'] = 15 148 | visual_style['edge_loop_position'] = 90 149 | visual_style['edge_loop_shape'] = 45 150 | visual_style['edge_directed'] = [True, True, False, True, True, False, True, 151 | True, True, True] 152 | visual_style['edge_label'][1] = '\\frac{\\alpha}{\\beta}' 153 | visual_style['edge_math_mode'] = {'ac': True} 154 | visual_style['edge_not_in_bg'] = {'fa': True} 155 | 156 | # general options 157 | # --------------- 158 | visual_style['unit'] = 'mm' 159 | visual_style['layout'] = _layout 160 | visual_style["margin"] = {'top': 5, 'bottom': 8, 'left': 5, 'right': 5} 161 | visual_style["canvas"] = (100, 60) 162 | visual_style['keep_aspect_ratio'] = False 163 | 164 | plot(net, 'network.tex', **visual_style) 165 | 166 | plot(net, 'network.csv', **visual_style) 167 | 168 | plot(net, 'network.pdf', **visual_style) 169 | 170 | plot(net, **visual_style) 171 | 172 | 173 | def test_layout(net): 174 | 175 | layout_style = {} 176 | layout_style['layout'] = 'fr' 177 | layout_style['seed'] = 1 178 | _layout = layout(net, **layout_style) 179 | 180 | plot(net, layout=_layout) 181 | 182 | 183 | # ============================================================================= 184 | # eof 185 | # 186 | # Local Variables: 187 | # mode: python 188 | # mode: linum 189 | # mode: auto-fill 190 | # fill-column: 80 191 | # End: 192 | -------------------------------------------------------------------------------- /tests/test_igraph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_igraph.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting igraph networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | import igraph as ig 36 | from network2tikz import plot, layout 37 | 38 | 39 | @pytest.fixture 40 | def net(): 41 | net = ig.Graph([(0, 1), (0, 2), (2, 3), (3, 4), (4, 2), (2, 5), (5, 0), (6, 3), 42 | (5, 6), (6, 6)], directed=True) 43 | 44 | net.vs["name"] = ["Alice", "Bob", "Claire", 45 | "Dennis", "Esther", "Frank", "George"] 46 | net.vs["age"] = [25, 31, 18, 47, 22, 23, 50] 47 | net.vs["gender"] = ["f", "m", "f", "m", "f", "m", "m"] 48 | net.es["is_formal"] = [False, False, True, True, True, False, True, False, 49 | False, False] 50 | return net 51 | 52 | 53 | @pytest.fixture 54 | def color_dict(): 55 | return {"m": "blue", "f": "red"} 56 | 57 | 58 | @pytest.fixture 59 | def shape_dict(): 60 | return {"m": "circle", "f": "rectangle"} 61 | 62 | 63 | @pytest.fixture 64 | def style_dict(): 65 | return {"m": "{shading=ball}", "f": None} 66 | 67 | 68 | @pytest.fixture 69 | def _layout(): 70 | layout = {0: (4.3191, -3.5352), 1: (0.5292, -0.5292), 71 | 2: (8.6559, -3.8008), 3: (12.4117, -7.5239), 72 | 4: (12.7, -1.7069), 5: (6.0022, -9.0323), 73 | 6: (9.7608, -12.7)} 74 | return layout 75 | 76 | 77 | def test_plot(net, _layout, color_dict): 78 | 79 | # plot(net) # plot_01.png 80 | 81 | # plot(net,layout=layout) # plot_02.png 82 | 83 | # plot(net, layout=layout, canvas=(8,8), margin=1) # plot_03.png 84 | 85 | visual_style = {} 86 | visual_style['layout'] = _layout 87 | visual_style['vertex_size'] = .5 88 | visual_style['vertex_color'] = [color_dict[g] for g in net.vs['gender']] 89 | visual_style['vertex_opacity'] = .7 90 | visual_style['vertex_label'] = net.vs['name'] 91 | visual_style['vertex_label_position'] = 'below' 92 | visual_style['edge_width'] = [1 + 2 * int(f) for f in net.es['is_formal']] 93 | visual_style['edge_curved'] = 0.1 94 | visual_style['canvas'] = (8, 8) 95 | visual_style['margin'] = 1 96 | 97 | plot(net, 'network.tex', **visual_style) 98 | 99 | plot(net, 'network.csv', **visual_style) 100 | 101 | plot(net, 'network.pdf', **visual_style) 102 | 103 | plot(net, **visual_style) 104 | 105 | 106 | def test_plot_all_options(net, _layout, color_dict, shape_dict, style_dict): 107 | 108 | visual_style = {} 109 | # node styles 110 | # ----------- 111 | visual_style['vertex_size'] = 5 112 | visual_style['vertex_color'] = [color_dict[g] for g in net.vs['gender']] 113 | visual_style['vertex_opacity'] = .7 114 | visual_style['vertex_label'] = net.vs['name'] 115 | visual_style['vertex_label_position'] = 'below' 116 | visual_style['vertex_label_distance'] = 15 117 | visual_style['vertex_label_color'] = 'gray' 118 | visual_style['vertex_label_size'] = 3 119 | visual_style['vertex_shape'] = [shape_dict[g] for g in net.vs['gender']] 120 | visual_style['vertex_style'] = [style_dict[g] for g in net.vs['gender']] 121 | visual_style['vertex_label_off'] = {4: True} # vertex e 122 | visual_style['vertex_math_mode'] = [True] 123 | visual_style['vertex_label_as_id'] = {5: True} # vertex f 124 | visual_style['vertex_pseudo'] = {3: True} # vertex d 125 | 126 | # edge styles 127 | # ----------- 128 | visual_style['edge_width'] = [.3 + .3 * int(f) for f in net.es['is_formal']] 129 | visual_style['edge_color'] = 'black' 130 | visual_style['edge_opacity'] = .8 131 | visual_style['edge_curved'] = 0.1 132 | visual_style['edge_label'] = [i for i, e in enumerate(net.es)] 133 | visual_style['edge_label_position'] = 'above' 134 | visual_style['edge_label_distance'] = .6 135 | visual_style['edge_label_color'] = 'gray' 136 | visual_style['edge_label_size'] = {1: 5} # edge ac 137 | visual_style['edge_style'] = 'dashed' 138 | visual_style['edge_arrow_size'] = .2 139 | visual_style['edge_arrow_width'] = .2 140 | visual_style['edge_loop_size'] = 15 141 | visual_style['edge_loop_position'] = 90 142 | visual_style['edge_loop_shape'] = 45 143 | visual_style['edge_directed'] = [True, True, False, True, True, False, True, 144 | True, True] 145 | visual_style['edge_label'][1] = '\\frac{\\alpha}{\\beta}' 146 | visual_style['edge_math_mode'] = {1: True} # edge ac 147 | visual_style['edge_not_in_bg'] = {6: True} # edge fa 148 | 149 | # general options 150 | # --------------- 151 | visual_style['unit'] = 'mm' 152 | visual_style['layout'] = _layout 153 | visual_style["margin"] = {'top': 5, 'bottom': 8, 'left': 5, 'right': 5} 154 | visual_style["canvas"] = (100, 60) 155 | visual_style['keep_aspect_ratio'] = False 156 | 157 | plot(net, 'network.tex', **visual_style) 158 | 159 | plot(net, 'network.csv', **visual_style) 160 | 161 | plot(net, 'network.pdf', **visual_style) 162 | 163 | plot(net, **visual_style) 164 | 165 | 166 | def test_layout(net): 167 | 168 | layout_style = {} 169 | layout_style['layout'] = 'fr' 170 | layout_style['seed'] = 1 171 | _layout = layout(net, **layout_style) 172 | 173 | plot(net, layout=_layout) 174 | 175 | 176 | # ============================================================================= 177 | # eof 178 | # 179 | # Local Variables: 180 | # mode: python 181 | # mode: linum 182 | # mode: auto-fill 183 | # fill-column: 80 184 | # End: 185 | -------------------------------------------------------------------------------- /tests/test_layout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_layout.py 5 | # Creation : 17 July 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting cnet networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | from network2tikz import plot 36 | from network2tikz.layout import Layout, layout 37 | import cnet as cn 38 | 39 | 40 | @pytest.fixture 41 | def net(): 42 | net = cn.Network(name='my tikz test network', directed=True) 43 | net.add_edges_from([('ab', 'a', 'b'), ('ac', 'a', 'c'), ('cd', 'c', 'd'), 44 | ('de', 'd', 'e'), ('ec', 'e', 'c'), ('cf', 'c', 'f'), 45 | ('fa', 'f', 'a'), ('fg', 'f', 'g'), ('gd', 'g', 'd'), 46 | ('gg', 'g', 'g')]) 47 | net.edges['ab']['force'] = 3 48 | net.edges['ac']['force'] = 2 49 | net.edges['cd']['force'] = 1 50 | net.edges['de']['force'] = 1 51 | net.edges['ec']['force'] = 3 52 | net.edges['cf']['force'] = 1 53 | net.edges['fa']['force'] = 1 54 | net.edges['fg']['force'] = 1 55 | net.edges['gd']['force'] = 1 56 | net.edges['gg']['force'] = 1 57 | 58 | net.nodes['name'] = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 59 | 'George'] 60 | net.nodes['age'] = [25, 31, 18, 47, 22, 23, 50] 61 | net.nodes['gender'] = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 62 | 63 | net.edges['is_formal'] = [False, False, True, True, True, False, True, 64 | False, False, False] 65 | return net 66 | 67 | 68 | @pytest.fixture 69 | def net2(): 70 | nodes = ['a', 'b', 'c'] 71 | edges = [('a', 'b'), ('b', 'c')] 72 | return nodes, edges 73 | 74 | 75 | def test_visual_style(net2): 76 | 77 | visual_style = {} 78 | visual_style['layout'] = {'a': (0, 0), 'b': (0, 0), 'c': (0, 0)} 79 | visual_style['keep_aspect_ratio'] = False 80 | #plot(net, **visual_style) 81 | 82 | visual_style = {} 83 | visual_style['layout'] = {'a': (0, 0), 'b': (0, 0), 'c': (1, 0)} 84 | visual_style['keep_aspect_ratio'] = False 85 | #plot(net, **visual_style) 86 | 87 | visual_style = {} 88 | visual_style['layout'] = {'a': (0, 0), 'b': (0, 0), 'c': (0, 1)} 89 | visual_style['keep_aspect_ratio'] = False 90 | #plot(net, **visual_style) 91 | 92 | 93 | def test_fruchterman_reingold(net): 94 | net.summary() 95 | A = net.adjacency_matrix().todense() 96 | print(A.shape) 97 | #L = Layout(net) 98 | # layout = L._fruchterman_reingold(A) 99 | 100 | # print(layout) 101 | _layout = {'a': (0, 0), 'b': (1, 1), 'c': (2, 2), 102 | 'd': (3, 3), 'e': (4, 4), 'f': (5, 5), 'g': (6, 6)} 103 | 104 | layout_style = {} 105 | layout_style['layout'] = 'fr' 106 | layout_style['layout_seed'] = 1 107 | layout_style['layout_weight'] = 'force' 108 | _layout = layout(net, **layout_style) 109 | 110 | visual_style = {} 111 | visual_style['node_label_as_id'] = True 112 | visual_style['layout'] = _layout 113 | visual_style['canvas'] = (10, 10) 114 | visual_style['margin'] = 1 115 | plot(net, **visual_style) 116 | 117 | visual_style = {} 118 | visual_style['node_label_as_id'] = True 119 | visual_style['canvas'] = (10, 10) 120 | visual_style['margin'] = 1 121 | visual_style['layout'] = 'fr' # _layout 122 | visual_style['layout_seed'] = 1 123 | visual_style['layout_weight'] = 'force' 124 | plot(net, **visual_style) 125 | 126 | 127 | # ============================================================================= 128 | # eof 129 | # 130 | # Local Variables: 131 | # mode: python 132 | # mode: linum 133 | # mode: auto-fill 134 | # fill-column: 80 135 | # End: 136 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_list.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting a node/edge list to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | from network2tikz import plot 36 | 37 | 38 | @pytest.fixture 39 | def net(): 40 | nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] 41 | edges = [('a', 'b'), ('a', 'c'), ('c', 'd'), ('d', 'e'), ('e', 'c'), ('c', 'f'), 42 | ('f', 'a'), ('f', 'g'), ('g', 'd'), ('g', 'g')] 43 | return nodes, edges 44 | 45 | 46 | @pytest.fixture 47 | def color_dict(): 48 | return {"m": "blue", "f": "red"} 49 | 50 | 51 | @pytest.fixture 52 | def shape_dict(): 53 | return {"m": "circle", "f": "rectangle"} 54 | 55 | 56 | @pytest.fixture 57 | def style_dict(): 58 | return {"m": "{shading=ball}", "f": None} 59 | 60 | 61 | @pytest.fixture 62 | def layout(): 63 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 64 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 65 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 66 | 'g': (9.7608, -12.7)} 67 | return layout 68 | 69 | 70 | def test_plot(net, layout, color_dict): 71 | 72 | name = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 'George'] 73 | age = [25, 31, 18, 47, 22, 23, 50] 74 | gender = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 75 | is_formal = [False, False, True, True, 76 | True, False, True, False, False, False] 77 | 78 | # plot(net) # plot_01.png 79 | 80 | # plot(net,layout=layout) # plot_02.png 81 | 82 | # plot(net, layout=layout, canvas=(8,8), margin=1) # plot_03.png 83 | 84 | visual_style = {} 85 | visual_style['layout'] = layout 86 | visual_style['node_size'] = .5 87 | visual_style['node_color'] = [color_dict[g] for g in gender] 88 | visual_style['node_opacity'] = .7 89 | visual_style['node_label'] = name 90 | visual_style['node_label_position'] = 'below' 91 | visual_style['edge_directed'] = True 92 | visual_style['edge_width'] = [1 + 2 * int(f) for f in is_formal] 93 | visual_style['edge_curved'] = 0.1 94 | visual_style['edge_color'] = [(230, 12, 102), (26, 213, 56)] 95 | visual_style['canvas'] = (8, 8) 96 | visual_style['margin'] = 1 97 | 98 | plot(net, 'network.tex', **visual_style) 99 | 100 | plot(net, 'network.csv', **visual_style) 101 | 102 | plot(net, 'network.pdf', **visual_style) 103 | 104 | plot(net, **visual_style) 105 | 106 | 107 | def test_plot_all_options(net, layout, color_dict, shape_dict, style_dict): 108 | 109 | name = ['Alice', 'Bob', 'Claire', 'Dennis', 'Esther', 'Frank', 'George'] 110 | age = [25, 31, 18, 47, 22, 23, 50] 111 | gender = ['f', 'm', 'f', 'm', 'f', 'm', 'm'] 112 | is_formal = [False, False, True, True, 113 | True, False, True, False, False, False] 114 | 115 | visual_style = {} 116 | 117 | # node styles 118 | # ----------- 119 | visual_style['node_size'] = 5 120 | visual_style['node_color'] = [color_dict[g] for g in gender] 121 | visual_style['node_opacity'] = .7 122 | visual_style['node_label'] = name 123 | visual_style['node_label_position'] = 'below' 124 | visual_style['node_label_distance'] = 15 125 | visual_style['node_label_color'] = 'gray' 126 | visual_style['node_label_size'] = 3 127 | visual_style['node_shape'] = [shape_dict[g] for g in gender] 128 | visual_style['node_style'] = [style_dict[g] for g in gender] 129 | visual_style['node_label_off'] = {'e': True} 130 | visual_style['node_math_mode'] = [True] 131 | visual_style['node_label_as_id'] = {'f': True} 132 | visual_style['node_pseudo'] = {'d': True} 133 | 134 | # edge styles 135 | # ----------- 136 | visual_style['edge_width'] = [.3 + .3 * int(f) for f in is_formal] 137 | visual_style['edge_color'] = 'black' 138 | visual_style['edge_opacity'] = .8 139 | visual_style['edge_curved'] = 0.1 140 | visual_style['edge_label'] = {e: e[0]+e[1] for e in net[1]} 141 | visual_style['edge_label_position'] = 'above' 142 | visual_style['edge_label_distance'] = .6 143 | visual_style['edge_label_color'] = 'gray' 144 | visual_style['edge_label_size'] = {('a', 'c'): 5} 145 | visual_style['edge_style'] = 'dashed' 146 | visual_style['edge_arrow_size'] = .2 147 | visual_style['edge_arrow_width'] = .2 148 | 149 | visual_style['edge_loop_size'] = 15 150 | visual_style['edge_loop_position'] = 90 151 | visual_style['edge_loop_shape'] = 45 152 | visual_style['edge_directed'] = [True, True, False, True, True, False, True, 153 | True, True, True] 154 | visual_style['edge_label'][('a', 'c')] = '\\frac{\\alpha}{\\beta}' 155 | visual_style['edge_math_mode'] = {('a', 'c'): True} 156 | visual_style['edge_not_in_bg'] = {('f', 'a'): True} 157 | 158 | # general options 159 | # --------------- 160 | visual_style['unit'] = 'mm' 161 | visual_style['layout'] = layout 162 | visual_style["margin"] = {'top': 5, 'bottom': 8, 'left': 5, 'right': 5} 163 | visual_style["canvas"] = (100, 60) 164 | visual_style['keep_aspect_ratio'] = False 165 | 166 | plot(net, 'network.tex', **visual_style) 167 | 168 | plot(net, 'network.csv', **visual_style) 169 | 170 | plot(net, 'network.pdf', **visual_style) 171 | 172 | plot(net, **visual_style) 173 | # ============================================================================= 174 | # eof 175 | # 176 | # Local Variables: 177 | # mode: python 178 | # mode: linum 179 | # mode: auto-fill 180 | # fill-column: 80 181 | # End: 182 | -------------------------------------------------------------------------------- /tests/test_network2tikz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_network2tikz.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | #from cnet import Node, Edge, Network 35 | from network2tikz.canvas import Canvas 36 | from network2tikz.units import UnitConverter 37 | 38 | 39 | def test_canvas(): 40 | canvas = Canvas() 41 | 42 | assert canvas.width == 6 43 | assert canvas.height == 6 44 | 45 | canvas.width = 10 46 | canvas.height = 8 47 | 48 | assert canvas.width == 10 49 | assert canvas.height == 8 50 | 51 | canvas = Canvas(4, 3) 52 | 53 | assert canvas.width == 4 54 | assert canvas.height == 3 55 | 56 | canvas = Canvas() 57 | 58 | assert isinstance(canvas.margins(), dict) 59 | assert canvas.margins()['top'] == 0.35 60 | 61 | assert canvas.margins(1)['top'] == 1 62 | 63 | margins = canvas.margins({'top': 2, 'left': 1, 'bottom': 2, 'right': .5}) 64 | assert margins['top'] == 2 and margins['left'] == 1 and \ 65 | margins['bottom'] == 2 and margins['right'] == .5 66 | 67 | with pytest.raises(Exception): 68 | canvas.margins(3) 69 | 70 | canvas = Canvas(6, 4, margins=0) 71 | layout = {'a': (-1, -1), 'b': (1, -1), 'c': (1, 1), 'd': (-1, 1)} 72 | 73 | l = canvas.fit(layout) 74 | assert l['a'] == (1, 0) 75 | assert l['b'] == (5, 0) 76 | assert l['c'] == (5, 4) 77 | assert l['d'] == (1, 4) 78 | 79 | l = canvas.fit(layout, keep_aspect_ratio=False) 80 | assert l['a'] == (0, 0) 81 | assert l['b'] == (6, 0) 82 | assert l['c'] == (6, 4) 83 | assert l['d'] == (0, 4) 84 | 85 | 86 | def test_unit_converter(): 87 | mm2cm = UnitConverter('mm', 'cm') 88 | 89 | assert mm2cm(10) == 1 90 | assert mm2cm.convert(10) == 1 91 | 92 | with pytest.raises(Exception): 93 | mm2m = UnitConverter('mm', 'm') 94 | mm2m(100) 95 | 96 | # ============================================================================= 97 | # eof 98 | # 99 | # Local Variables: 100 | # mode: python 101 | # mode: linum 102 | # mode: auto-fill 103 | # fill-column: 80 104 | # End: 105 | -------------------------------------------------------------------------------- /tests/test_networkx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_igraph.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting igraph networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | import networkx as nx 36 | from network2tikz import plot, layout 37 | 38 | 39 | @pytest.fixture 40 | def net(): 41 | net = nx.DiGraph() 42 | net.add_node('a', name='Alice', age=25, gender='f') 43 | net.add_node('b', name='Bob', age=31, gender='m') 44 | net.add_node('c', name='Claire', age=18, gender='f') 45 | net.add_node('d', name='Dennis', age=47, gender='m') 46 | net.add_node('e', name='Esther', age=22, gender='f') 47 | net.add_node('f', name='Frank', age=23, gender='m') 48 | net.add_node('g', name='George', age=50, gender='m') 49 | 50 | net.add_edge('a', 'b', is_formal=False) 51 | net.add_edge('a', 'c', is_formal=False) 52 | net.add_edge('c', 'd', is_formal=True) 53 | net.add_edge('d', 'e', is_formal=True) 54 | net.add_edge('e', 'c', is_formal=True) 55 | net.add_edge('c', 'f', is_formal=False) 56 | net.add_edge('f', 'a', is_formal=True) 57 | net.add_edge('f', 'g', is_formal=False) 58 | net.add_edge('g', 'g', is_formal=False) 59 | net.add_edge('g', 'd', is_formal=False) 60 | return net 61 | 62 | 63 | @pytest.fixture 64 | def color_dict(): 65 | return {"m": "blue", "f": "red"} 66 | 67 | 68 | @pytest.fixture 69 | def shape_dict(): 70 | return {"m": "circle", "f": "rectangle"} 71 | 72 | 73 | @pytest.fixture 74 | def style_dict(): 75 | return {"m": "{shading=ball}", "f": None} 76 | 77 | 78 | @pytest.fixture 79 | def _layout(): 80 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 81 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 82 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 83 | 'g': (9.7608, -12.7)} 84 | return layout 85 | 86 | 87 | def test_plot(net, _layout, color_dict): 88 | 89 | # plot(net) # plot_01.png 90 | 91 | # plot(net,layout=layout) # plot_02.png 92 | 93 | # plot(net, layout=layout, canvas=(8,8), margin=1) # plot_03.png 94 | 95 | visual_style = {} 96 | visual_style['layout'] = _layout 97 | visual_style['vertex_size'] = .5 98 | visual_style['vertex_color'] = {n: color_dict[g] 99 | for n, g in nx.get_node_attributes(net, 'gender').items()} 100 | visual_style['vertex_opacity'] = .7 101 | visual_style['vertex_label'] = nx.get_node_attributes(net, 'name') 102 | visual_style['vertex_label_position'] = 'below' 103 | visual_style['edge_width'] = { 104 | e: 1 + 2 * int(f) for e, f in nx.get_edge_attributes(net, 'is_formal').items()} 105 | visual_style['edge_curved'] = 0.1 106 | visual_style['canvas'] = (8, 8) 107 | visual_style['margin'] = 1 108 | 109 | plot(net, 'network.tex', **visual_style) 110 | 111 | plot(net, 'network.csv', **visual_style) 112 | 113 | plot(net, 'network.pdf', **visual_style) 114 | 115 | plot(net, **visual_style) 116 | 117 | 118 | def test_plot_all_options(net, _layout, color_dict, shape_dict, style_dict): 119 | 120 | visual_style = {} 121 | # node styles 122 | # ----------- 123 | visual_style['vertex_size'] = 5 124 | visual_style['vertex_color'] = {n: color_dict[g] 125 | for n, g in nx.get_node_attributes(net, 'gender').items()} 126 | visual_style['vertex_opacity'] = .7 127 | visual_style['vertex_label'] = nx.get_node_attributes(net, 'name') 128 | visual_style['vertex_label_position'] = 'below' 129 | visual_style['vertex_label_distance'] = 15 130 | visual_style['vertex_label_color'] = 'gray' 131 | visual_style['vertex_label_size'] = 3 132 | visual_style['vertex_shape'] = {n: shape_dict[g] 133 | for n, g in nx.get_node_attributes(net, 'gender').items()} 134 | visual_style['vertex_style'] = {n: style_dict[g] 135 | for n, g in nx.get_node_attributes(net, 'gender').items()} 136 | visual_style['vertex_label_off'] = {'e': True} 137 | visual_style['vertex_math_mode'] = {'a': True} 138 | visual_style['vertex_label_as_id'] = {'f': True} 139 | visual_style['vertex_pseudo'] = {'d': True} 140 | 141 | # edge styles 142 | # ----------- 143 | visual_style['edge_width'] = { 144 | e: .3 + .3 * int(f) for e, f in nx.get_edge_attributes(net, 'is_formal').items()} 145 | visual_style['edge_color'] = 'black' 146 | visual_style['edge_opacity'] = .8 147 | visual_style['edge_curved'] = 0.1 148 | visual_style['edge_label'] = {e: e[0]+e[1] for e in net.edges} 149 | visual_style['edge_label_position'] = 'above' 150 | visual_style['edge_label_distance'] = .6 151 | visual_style['edge_label_color'] = 'gray' 152 | visual_style['edge_label_size'] = {('a', 'c'): 5} 153 | visual_style['edge_style'] = 'dashed' 154 | visual_style['edge_arrow_size'] = .2 155 | visual_style['edge_arrow_width'] = .2 156 | visual_style['edge_loop_size'] = 15 157 | visual_style['edge_loop_position'] = 90 158 | visual_style['edge_loop_shape'] = 45 159 | visual_style['edge_directed'] = {('a', 'b'): True, ('a', 'c'): True, 160 | ('c', 'd'): False, ('d', 'e'): True, 161 | ('e', 'c'): True, ('c', 'f'): False, 162 | ('f', 'a'): True, ('f', 'g'): True, 163 | ('g', 'g'): True} 164 | visual_style['edge_label'][('a', 'c')] = '\\frac{\\alpha}{\\beta}' 165 | visual_style['edge_math_mode'] = {('a', 'c'): True} 166 | visual_style['edge_not_in_bg'] = {('f', 'a'): True} 167 | 168 | # general options 169 | # --------------- 170 | visual_style['unit'] = 'mm' 171 | visual_style['layout'] = _layout 172 | visual_style["margin"] = {'top': 5, 'bottom': 8, 'left': 5, 'right': 5} 173 | visual_style["canvas"] = (100, 60) 174 | visual_style['keep_aspect_ratio'] = False 175 | 176 | plot(net, 'network.tex', **visual_style) 177 | 178 | plot(net, 'network.csv', **visual_style) 179 | 180 | plot(net, 'network.pdf', **visual_style) 181 | 182 | plot(net, **visual_style) 183 | 184 | 185 | def test_layout(net): 186 | 187 | layout_style = {} 188 | layout_style['layout'] = 'fr' 189 | layout_style['seed'] = 1 190 | _layout = layout(net, **layout_style) 191 | 192 | plot(net, layout=_layout) 193 | 194 | 195 | # ============================================================================= 196 | # eof 197 | # 198 | # Local Variables: 199 | # mode: python 200 | # mode: linum 201 | # mode: auto-fill 202 | # fill-column: 80 203 | # End: 204 | -------------------------------------------------------------------------------- /tests/test_overlay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_overlay.py -- Test environment for overlaying networks 5 | # Author : Juergen Hackl 6 | # Creation : 2018-07-30 7 | # Time-stamp: 8 | # 9 | # Copyright (c) 2018 Juergen Hackl 10 | # ============================================================================= 11 | 12 | import pytest 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath( 17 | os.path.join(os.path.dirname(__file__), '..'))) 18 | 19 | #from network2tikz import plot 20 | from network2tikz.layout import Layout, layout 21 | from network2tikz import plot 22 | import cnet as cn 23 | 24 | 25 | @pytest.fixture 26 | def net_1(): 27 | net = cn.Network(name='my tikz test network number 1', directed=True) 28 | net.add_edges_from([('ab', 'a', 'b'), ('bc', 'b', 'c')]) 29 | return net 30 | 31 | 32 | @pytest.fixture 33 | def net_2(): 34 | net = cn.Network(name='my tikz test network number 2', directed=True) 35 | net.add_edges_from([('uv', 'u', 'v'), ('vw', 'v', 'w')]) 36 | return net 37 | 38 | 39 | def test_overlay(net_1, net_2): 40 | 41 | visual_style_1 = {} 42 | visual_style_1['layout'] = {'a': (0, 0), 'b': (1, 0), 'c': (2, 0)} 43 | visual_style_1['node_color'] = 'green' 44 | visual_style_1["canvas"] = (10, 10) 45 | visual_style_1['yshift'] = -1 46 | # plot(net_1, **visual_style_1) 47 | 48 | visual_style_2 = {} 49 | visual_style_2['layout'] = {'u': (0, 1), 'v': (1, 1), 'w': (2, 1)} 50 | visual_style_2['node_color'] = 'red' 51 | visual_style_2["canvas"] = (10, 10) 52 | visual_style_2['yshift'] = 1 53 | # plot(net_2, **visual_style_2) 54 | 55 | plot.add(net_1, **visual_style_1) 56 | plot.add(net_2, **visual_style_2) 57 | plot.show() 58 | # plot.save('test.tex') 59 | 60 | 61 | # ============================================================================= 62 | # eof 63 | # 64 | # Local Variables: 65 | # mode: python 66 | # mode: linum 67 | # mode: auto-fill 68 | # fill-column: 80 69 | # End: 70 | -------------------------------------------------------------------------------- /tests/test_pathpy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # -*- coding: utf-8 -*- 3 | # ============================================================================= 4 | # File : test_pathpy.py 5 | # Creation : 21 May 2018 6 | # Time-stamp: 7 | # 8 | # Copyright (c) 2018 Jürgen Hackl 9 | # http://www.ibi.ethz.ch 10 | # $Id$ 11 | # 12 | # Description : Test functions for converting pathpy networks to tikz-networks 13 | # 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | # 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | # ============================================================================= 27 | 28 | import pytest 29 | import os 30 | import sys 31 | 32 | sys.path.insert(0, os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), '..'))) 34 | 35 | import pathpy as pp 36 | from network2tikz import plot, layout 37 | 38 | 39 | @pytest.fixture 40 | def net(): 41 | net = pp.Network(directed=True) 42 | net.add_node('a', name='Alice', age=25, gender='f') 43 | net.add_node('b', name='Bob', age=31, gender='m') 44 | net.add_node('c', name='Claire', age=18, gender='f') 45 | net.add_node('d', name='Dennis', age=47, gender='m') 46 | net.add_node('e', name='Esther', age=22, gender='f') 47 | net.add_node('f', name='Frank', age=23, gender='m') 48 | net.add_node('g', name='George', age=50, gender='m') 49 | 50 | net.add_edge('a', 'b', is_formal=False) 51 | net.add_edge('a', 'c', is_formal=False) 52 | net.add_edge('c', 'd', is_formal=True) 53 | net.add_edge('d', 'e', is_formal=True) 54 | net.add_edge('e', 'c', is_formal=True) 55 | net.add_edge('c', 'f', is_formal=False) 56 | net.add_edge('f', 'a', is_formal=True) 57 | net.add_edge('f', 'g', is_formal=False) 58 | net.add_edge('g', 'g', is_formal=False) 59 | net.add_edge('g', 'd', is_formal=False) 60 | return net 61 | 62 | 63 | @pytest.fixture 64 | def color_dict(): 65 | return {"m": "blue", "f": "red"} 66 | 67 | 68 | @pytest.fixture 69 | def shape_dict(): 70 | return {"m": "circle", "f": "rectangle"} 71 | 72 | 73 | @pytest.fixture 74 | def style_dict(): 75 | return {"m": "{shading=ball}", "f": None} 76 | 77 | 78 | @pytest.fixture 79 | def _layout(): 80 | layout = {'a': (4.3191, -3.5352), 'b': (0.5292, -0.5292), 81 | 'c': (8.6559, -3.8008), 'd': (12.4117, -7.5239), 82 | 'e': (12.7, -1.7069), 'f': (6.0022, -9.0323), 83 | 'g': (9.7608, -12.7)} 84 | return layout 85 | 86 | 87 | def test_plot(net, _layout, color_dict): 88 | 89 | # plot(net) # plot_01.png 90 | 91 | # plot(net,layout=layout) # plot_02.png 92 | 93 | # plot(net, layout=layout, canvas=(8,8), margin=1) # plot_03.png 94 | 95 | visual_style = {} 96 | visual_style['layout'] = _layout 97 | visual_style['node_size'] = .5 98 | visual_style['node_color'] = { 99 | n: color_dict[a['gender']]for n, a in net.nodes.items()} 100 | visual_style['node_opacity'] = .7 101 | visual_style['node_label'] = {n: a['name'] for n, a in net.nodes.items()} 102 | visual_style['node_label_position'] = 'below' 103 | visual_style['edge_width'] = { 104 | e: 1 + 2 * int(a['is_formal']) for e, a in net.edges.items()} 105 | visual_style['edge_curved'] = 0.1 106 | visual_style['canvas'] = (8, 8) 107 | visual_style['margin'] = 1 108 | 109 | plot(net, 'network.tex', **visual_style) 110 | 111 | plot(net, 'network.csv', **visual_style) 112 | 113 | plot(net, 'network.pdf', **visual_style) 114 | 115 | plot(net, **visual_style) 116 | 117 | 118 | def test_plot_all_options(net, _layout, color_dict, shape_dict, style_dict): 119 | 120 | visual_style = {} 121 | # node styles 122 | # ----------- 123 | visual_style['node_size'] = 5 124 | visual_style['node_color'] = { 125 | n: color_dict[a['gender']]for n, a in net.nodes.items()} 126 | visual_style['node_opacity'] = .7 127 | visual_style['node_label'] = {n: a['name'] for n, a in net.nodes.items()} 128 | visual_style['node_label_position'] = 'below' 129 | visual_style['node_label_distance'] = 15 130 | visual_style['node_label_color'] = 'gray' 131 | visual_style['node_label_size'] = 3 132 | visual_style['node_shape'] = { 133 | n: shape_dict[a['gender']]for n, a in net.nodes.items()} 134 | visual_style['node_style'] = { 135 | n: style_dict[a['gender']]for n, a in net.nodes.items()} 136 | visual_style['node_label_off'] = {'e': True} 137 | visual_style['node_math_mode'] = {'a': True} 138 | visual_style['node_label_as_id'] = {'f': True} 139 | visual_style['node_pseudo'] = {'d': True} 140 | 141 | # edge styles 142 | # ----------- 143 | visual_style['edge_width'] = { 144 | e: .3 + .3 * int(a['is_formal']) for e, a in net.edges.items()} 145 | visual_style['edge_color'] = 'black' 146 | visual_style['edge_opacity'] = .8 147 | visual_style['edge_curved'] = 0.1 148 | visual_style['edge_label'] = {e: e[0]+e[1] for e in net.edges} 149 | visual_style['edge_label_position'] = 'above' 150 | visual_style['edge_label_distance'] = .6 151 | visual_style['edge_label_color'] = 'gray' 152 | visual_style['edge_label_size'] = {('a', 'c'): 5} 153 | visual_style['edge_style'] = 'dashed' 154 | visual_style['edge_arrow_size'] = .2 155 | visual_style['edge_arrow_width'] = .2 156 | visual_style['edge_loop_size'] = 15 157 | visual_style['edge_loop_position'] = 90 158 | visual_style['edge_loop_shape'] = 45 159 | visual_style['edge_directed'] = {('a', 'b'): True, ('a', 'c'): True, 160 | ('c', 'd'): False, ('d', 'e'): True, 161 | ('e', 'c'): True, ('c', 'f'): False, 162 | ('f', 'a'): True, ('f', 'g'): True, 163 | ('g', 'g'): True} 164 | visual_style['edge_label'][('a', 'c')] = '\\frac{\\alpha}{\\beta}' 165 | visual_style['edge_math_mode'] = {('a', 'c'): True} 166 | visual_style['edge_not_in_bg'] = {('f', 'a'): True} 167 | 168 | # general options 169 | # --------------- 170 | visual_style['unit'] = 'mm' 171 | visual_style['layout'] = _layout 172 | visual_style["margin"] = {'top': 5, 'bottom': 8, 'left': 5, 'right': 5} 173 | visual_style["canvas"] = (100, 60) 174 | visual_style['keep_aspect_ratio'] = False 175 | 176 | plot(net, 'network.tex', **visual_style) 177 | 178 | plot(net, 'network.csv', **visual_style) 179 | 180 | plot(net, 'network.pdf', **visual_style) 181 | 182 | plot(net, **visual_style) 183 | 184 | 185 | def test_layout(net): 186 | 187 | layout_style = {} 188 | layout_style['layout'] = 'fr' 189 | layout_style['seed'] = 1 190 | _layout = layout(net, **layout_style) 191 | 192 | plot(net, layout=_layout) 193 | 194 | 195 | def test_simple(): 196 | g = pp.Network() 197 | g.add_node('a') 198 | g.add_node('b') 199 | plot(g) 200 | 201 | 202 | test_simple() 203 | # ============================================================================= 204 | # eof 205 | # 206 | # Local Variables: 207 | # mode: python 208 | # mode: linum 209 | # mode: auto-fill 210 | # fill-column: 80 211 | # End: 212 | --------------------------------------------------------------------------------