├── .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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------