├── .gitignore
├── .meta_version
├── .travis.yml
├── LICENSE
├── README.md
├── README.rst
├── graphviz_artist
├── __init__.py
├── attr.py
├── graph.py
└── test
│ ├── __init__.py
│ ├── test_1.py
│ ├── test_2.py
│ ├── test_3.py
│ ├── test_4.py
│ └── test_5.py
├── imgs
├── test_1.png
├── test_2.png
└── test_3.png
├── runtests.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # dot
2 | **Digraph.gv
3 |
4 | # emacs
5 | **~
6 |
7 | # editors
8 | .idea/
9 | .vscode/
10 |
11 | # Byte-compiled / optimized / DLL files
12 | __pycache__/
13 | *.py[cod]
14 | *$py.class
15 | .coverage
16 |
17 | # C extensions
18 | *.so
19 |
20 | # Distribution / packaging
21 | .Python
22 | build/
23 | develop-eggs/
24 | dist/
25 | downloads/
26 | eggs/
27 | .eggs/
28 | lib/
29 | lib64/
30 | parts/
31 | sdist/
32 | var/
33 | wheels/
34 | *.egg-info/
35 | .installed.cfg
36 | *.egg
37 | MANIFEST
38 |
39 | # PyInstaller
40 | # Usually these files are written by a python script from a template
41 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
42 | *.manifest
43 | *.spec
44 |
45 | # Installer logs
46 | pip-log.txt
47 | pip-delete-this-directory.txt
48 |
49 | # Unit test / coverage reports
50 | htmlcov/
51 | .tox/
52 | .coverage
53 | .coverage.*
54 | .cache
55 | nosetests.xml
56 | coverage.xml
57 | *.cover
58 | .hypothesis/
59 | .pytest_cache/
60 |
61 | # Translations
62 | *.mo
63 | *.pot
64 |
65 | # Django stuff:
66 | *.log
67 | local_settings.py
68 | db.sqlite3
69 |
70 | # Flask stuff:
71 | instance/
72 | .webassets-cache
73 |
74 | # Scrapy stuff:
75 | .scrapy
76 |
77 | # Sphinx documentation
78 | docs/_build/
79 |
80 | # PyBuilder
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # celery beat schedule file
90 | celerybeat-schedule
91 |
92 | # SageMath parsed files
93 | *.sage.py
94 |
95 | # Environments
96 | .env
97 | .venv
98 | env/
99 | venv/
100 | ENV/
101 | env.bak/
102 | venv.bak/
103 |
104 | # Spyder project settings
105 | .spyderproject
106 | .spyproject
107 |
108 | # Rope project settings
109 | .ropeproject
110 |
111 | # mkdocs documentation
112 | /site
113 |
114 | # mypy
115 | .mypy_cache/
116 |
--------------------------------------------------------------------------------
/.meta_version:
--------------------------------------------------------------------------------
1 | method: autoinc
2 | current: 0.0.1
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.4"
5 | - "3.5"
6 | - "3.6"
7 | - "3.6-dev"
8 | - "3.7-dev"
9 | install:
10 | - pip install codecov coverage graphviz typing
11 |
12 | script:
13 | - coverage run runtests.py
14 | - coverage report
15 |
16 | after_success:
17 | - codecov
18 | - coveralls
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 | Copyright (c) 2019 thautwarm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
21 | OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Graphviz-Artist
2 |
3 | [](https://codecov.io/gh/thautwarm/graphviz-artist)
4 | [](https://pypi.python.org/pypi/graphviz-artist)
5 | [](https://travis-ci.org/thautwarm/graphviz-artist)
6 | [](https://pypi.org/project/graphviz-artist/)
7 | [](https://github.com/thautwarm/graphviz-artist/blob/master/LICENSE)
8 |
9 | You just become an artist in graph drawing once you start using `Graphviz-Artist`.
10 |
11 | If you don't have the demand of fine-grained controling upon generated graphs, the learning curve of graphviz-artist is actually horizontal.
12 |
13 | This package provides a higher-level encapsulation for the python package [`graphviz`](https://github.com/xflr6/graphviz), and you might
14 | want to have a try at the latter.
15 |
16 |
17 | ## Installation
18 |
19 | - Install [Graphviz](https://www.graphviz.org/download/).
20 | - `pip install graphviz-artist`
21 |
22 |
23 | ## Quickstart
24 |
25 | ### Undirected Graph
26 |
27 | ```python
28 | import graphviz_artist as ga
29 |
30 | # make a graph
31 | g = ga.Graph()
32 |
33 | # make nodes
34 | n1 = g.new()
35 | n2 = g.new()
36 | n3 = g.new()
37 |
38 | # (>), (==) and (<) could create edges for graphs.
39 | # n2 > n3 / n3 < n2 : there is an edge n2 -> n3
40 | # (==) will be introduced later, as it's only meaningful to directed edges.
41 | _ = n1 > n2 > n3 > n1
42 |
43 | g.view()
44 | ```
45 |
46 |
47 |

48 |
49 |
50 |
51 | ### Directed Graph
52 |
53 | ```python
54 | import graphviz_artist as ga
55 |
56 | # use attr module to see which Graphviz Attributes
57 | # could be auto-completed.
58 | import graphviz_artist.attr as attr
59 |
60 | # use HorizontalGraph
61 | g = ga.Graph(attr.HorizontalGraph)
62 |
63 | # `attr.Shape("")` to specify the shape of nodes.
64 | n1 = g.new(attr.Label('hey'), attr.Shape.diamond)
65 | n2 = g.new(attr.Label('hey'), attr.Shape.hexagon)
66 | n3 = g.new(attr.Label('you'), attr.Shape.star)
67 |
68 | # `attr.Directed()` makes a directed edge.
69 | directed = attr.Directed()
70 |
71 | # `attr.Label` to specify the text that edges display
72 | edge_label = attr.Label("passed_here")
73 |
74 | # `attr.Penwidth` to decide the width of edge glyph.
75 | edge_size = attr.Penwidth(2.)
76 |
77 | # in `a < b[b_to_c_attrs...] > c`, the edge `b -> c` will have attribute `b_to_c_attrs`.
78 | _ = n3[directed, edge_label, edge_size] > n1[directed] == n2 > n3
79 |
80 | g.view()
81 | ```
82 |
83 |
84 |

85 |
86 |
87 |
88 | ### Expression Tree
89 |
90 | ```python
91 | import graphviz_artist as ga
92 | import graphviz_artist.attr as attr
93 |
94 | # make a graph
95 | g = ga.Graph(directed=True)
96 |
97 | new = g.new
98 |
99 | # decl nodes
100 | false = new(attr.Label("False"))
101 | true = new(attr.Label("True"))
102 | not_ = new(attr.Label("not"))
103 | and_ = new(attr.Label("and"))
104 |
105 | unary1 = new(attr.Label("unary"))
106 | unary2 = new(attr.Label("unary"))
107 | binary = new(attr.Label("binary"), attr.Width(2), attr.Shape.box)
108 | expr = new(attr.Label("expr"))
109 |
110 | # build graph
111 | _ = false > unary1 < not_
112 | _ = true > unary2
113 |
114 | _ = and_[attr.Label('Op')] > binary
115 |
116 | # XLabel: For edges, the label will be placed near the center of the edge.
117 | _ = unary1[attr.XLabel("Left operand")] > binary
118 | _ = unary2[attr.XLabel('Right operand')] > binary
119 | _ = binary > expr
120 |
121 | g.view()
122 | ```
123 |
124 |

125 |
126 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 |
2 | .. image:: https://travis-ci.org/thautwarm/graphviz-artist.svg?branch=master
3 | :target: https://travis-ci.org/thautwarm/graphviz-artist
4 |
5 | .. image:: https://img.shields.io/pypi/v/graphviz-artist.svg
6 | :target: https://pypi.python.org/pypi/graphviz-artist
7 |
8 | .. image:: https://codecov.io/gh/thautwarm/graphviz-artist/branch/master/graph/badge.svg
9 | :target: https://codecov.io/gh/thautwarm/graphviz-artist
10 |
11 | .. image:: https://img.shields.io/pypi/pyversions/graphviz-artist.svg
12 | :target: https://pypi.org/project/graphviz-artist/
13 |
14 | .. image:: https://img.shields.io/badge/license-mit-teal.svg
15 | :target: https://pypi.org/project/graphviz-artist/
16 |
17 | Graphviz-Artist
18 | ===============
19 |
20 |
21 | You just become an artist in graph drawing once you start using
22 | ``Graphviz-Artist``.
23 |
24 | If you don't have the demand of fine-grained controling upon generated
25 | graphs, the learning curve of graphviz-artist is actually horizontal.
26 |
27 | This package provides a higher-level encapsulation for the python
28 | package `graphviz `__, and you
29 | might want to have a try at the latter.
30 |
31 | Installation
32 | ------------
33 |
34 | - Install `Graphviz `__.
35 | - ``pip install graphviz-artist``
36 |
37 | Quickstart
38 | ----------
39 |
40 |
41 | Undirected Graph
42 | ~~~~~~~~~~~~~~~~
43 |
44 | .. code:: python
45 |
46 | import graphviz_artist as ga
47 |
48 | # make a graph
49 | g = ga.Graph()
50 |
51 | # make nodes
52 | n1 = g.new()
53 | n2 = g.new()
54 | n3 = g.new()
55 |
56 | # (>), (==) and (<) could create edges for graphs.
57 | # n2 > n3 / n3 < n2 : there is an edge n2 -> n3
58 | # (==) will be introduced later, as it's only meaningful to directed edges.
59 | _ = n1 > n2 > n3 > n1
60 |
61 | g.view()
62 |
63 | .. image:: https://raw.githubusercontent.com/thautwarm/graphviz-artist/master/imgs/test_1.png
64 | :width: 500px
65 | :align: center
66 |
67 | Directed Graph
68 | ~~~~~~~~~~~~~~
69 |
70 | .. code:: python
71 |
72 | import graphviz_artist as ga
73 |
74 | # use attr module to see which Graphviz Attributes
75 | # could be auto-completed.
76 | import graphviz_artist.attr as attr
77 |
78 | # use HorizontalGraph
79 | g = ga.Graph(attr.HorizontalGraph)
80 |
81 | # `attr.Shape("")` to specify the shape of nodes.
82 | n1 = g.new(attr.Label('hey'), attr.Shape.diamond)
83 | n2 = g.new(attr.Label('hey'), attr.Shape.hexagon)
84 | n3 = g.new(attr.Label('you'), attr.Shape.star)
85 |
86 | # `attr.Directed()` makes a directed edge.
87 | directed = attr.Directed()
88 |
89 | # `attr.Label` to specify the text that edges display
90 | edge_label = attr.Label("passed_here")
91 |
92 | # `attr.Penwidth` to decide the width of edge glyph.
93 | edge_size = attr.Penwidth(2.)
94 |
95 | # in `a < b[b_to_c_attrs...] > c`, the edge `b -> c` will have attribute `b_to_c_attrs`.
96 | _ = n3[directed, edge_label, edge_size] > n1[directed] == n2 > n3
97 |
98 | g.view()
99 |
100 | .. image:: https://raw.githubusercontent.com/thautwarm/graphviz-artist/master/imgs/test_2.png
101 | :width: 500px
102 | :align: center
103 |
104 |
105 |
106 | Expression Tree
107 | ~~~~~~~~~~~~~~~
108 |
109 | .. code:: python
110 |
111 | import graphviz_artist as ga
112 | import graphviz_artist.attr as attr
113 |
114 | # make a graph
115 | g = ga.Graph(directed=True)
116 |
117 | new = g.new
118 |
119 | # decl nodes
120 | false = new(attr.Label("False"))
121 | true = new(attr.Label("True"))
122 | not_ = new(attr.Label("not"))
123 | and_ = new(attr.Label("and"))
124 |
125 | unary1 = new(attr.Label("unary"))
126 | unary2 = new(attr.Label("unary"))
127 | binary = new(attr.Label("binary"), attr.Width(2), attr.Shape.box)
128 | expr = new(attr.Label("expr"))
129 |
130 | # build graph
131 | _ = false > unary1 < not_
132 | _ = true > unary2
133 |
134 | _ = and_[attr.Label('Op')] > binary
135 |
136 | # XLabel: For edges, the label will be placed near the center of the edge.
137 | _ = unary1[attr.XLabel("Left operand")] > binary
138 | _ = unary2[attr.XLabel('Right operand')] > binary
139 | _ = binary > expr
140 |
141 | g.view()
142 |
143 |
144 | .. image:: https://raw.githubusercontent.com/thautwarm/graphviz-artist/master/imgs/test_3.png
145 | :width: 500px
146 | :align: center
147 |
--------------------------------------------------------------------------------
/graphviz_artist/__init__.py:
--------------------------------------------------------------------------------
1 | from . import attr
2 | from .graph import *
3 |
--------------------------------------------------------------------------------
/graphviz_artist/attr.py:
--------------------------------------------------------------------------------
1 | import numbers
2 |
3 |
4 | class _ClassProperty(object):
5 | def __init__(self, method):
6 | self.method = method
7 |
8 | def __get__(self, _, instance_cls):
9 | return self.method(instance_cls)
10 |
11 |
12 | def thunk_class_property(f):
13 | return _ClassProperty(lambda cls: f())
14 |
15 |
16 | class Attr(object):
17 | __slots__ = ('value', )
18 |
19 | def __init__(self, value): # type: (str) -> None
20 | self.value = value
21 |
22 | def dump_(self, config):
23 | """
24 | default configuration dumping method.
25 | """
26 | config[self.__class__.__name__.lower()] = self.value
27 | return config
28 |
29 | def __repr__(self):
30 | return "<%s %s>" % (self.__class__.__name__, str(self.value))
31 |
32 |
33 | class Label(Attr):
34 | """
35 | The text to be put on the surface of a node.
36 | """
37 | pass
38 |
39 |
40 | class Ratio(Attr):
41 | pass
42 |
43 |
44 | class Width(Attr):
45 | def __init__(self, num): # type: (numbers.Number) -> None
46 | Attr.__init__(self, str(num))
47 |
48 |
49 | class Height(Attr):
50 | def __init__(self, num): # type: (numbers.Number) -> None
51 | Attr.__init__(self, str(num))
52 |
53 |
54 | class FixedSize(Attr):
55 | def __init__(self):
56 | Attr.__init__(self, 'true')
57 |
58 |
59 | class Penwidth(Attr):
60 | def __init__(self, num): # type: (float) -> None
61 | Attr.__init__(self, str(num))
62 |
63 |
64 | class Directed(Attr):
65 | """
66 | check https://www.graphviz.org/doc/info/attrs.html#k:dirType
67 | to see available options
68 | """
69 |
70 | def __init__(self, is_directed=True):
71 | Attr.__init__(self, 'forward' if is_directed else 'none')
72 |
73 | def dump_(self, config):
74 | config['dir'] = self.value
75 | return config
76 |
77 |
78 | class Rankdir(Attr):
79 | """
80 | Sets direction of graph layout.
81 | """
82 | LR = thunk_class_property(lambda: Rankdir('LR'))
83 |
84 |
85 | HorizontalGraph = Rankdir.LR
86 |
87 |
88 | class Shape(Attr):
89 | """
90 | check following link to see available shape options.
91 | https://www.graphviz.org/doc/info/shapes.html
92 | """
93 |
94 | box = thunk_class_property(lambda: Shape('box')) # type: Shape
95 | polygon = thunk_class_property(lambda: Shape('polygon')) # type: Shape
96 | ellipse = thunk_class_property(lambda: Shape('ellipse')) # type: Shape
97 |
98 | oval = thunk_class_property(lambda: Shape("oval")) # type: Shape
99 | circle = thunk_class_property(lambda: Shape("circle")) # type: Shape
100 | point = thunk_class_property(lambda: Shape("point")) # type: Shape
101 | egg = thunk_class_property(lambda: Shape("egg")) # type: Shape
102 | triangle = thunk_class_property(lambda: Shape("triangle")) # type: Shape
103 | diamond = thunk_class_property(lambda: Shape("diamond")) # type: Shape
104 | trapezium = thunk_class_property(lambda: Shape("trapezium")) # type: Shape
105 | parallelogram = thunk_class_property(lambda: Shape("parallelogram")
106 | ) # type: Shape
107 | star = thunk_class_property(lambda: Shape("star")) # type: Shape
108 | pentagon = thunk_class_property(lambda: Shape("pentagon")) # type: Shape
109 | hexagon = thunk_class_property(lambda: Shape("hexagon")) # type: Shape
110 | cds = thunk_class_property(lambda: Shape("cds")) # type: Shape
111 | box3d = thunk_class_property(lambda: Shape("box3d")) # type: Shape
112 |
113 |
114 | class MinLen(Attr):
115 | def __init__(self, num): # type: (float) -> None
116 | Attr.__init__(self, str(num))
117 |
118 |
119 | class LabelFloat(Attr):
120 | def __init__(self):
121 | Attr.__init__(self, 'true')
122 |
123 |
124 | class LabelFontSize(Attr):
125 | def __init__(self, num): # type: (numbers.Number) -> None
126 | Attr.__init__(self, str(num))
127 |
128 |
129 | class XLabel(Attr):
130 | pass
131 |
132 |
133 | class Nodesep(Attr):
134 | def __init__(self, num): # type: (float) -> None
135 | Attr.__init__(self, str(num))
136 |
137 |
138 | class Color(Attr):
139 | def __init__(self, color): # type: (str) -> None
140 | Attr.__init__(self, color)
141 |
142 |
143 | class Style(Attr):
144 | def __init__(self, style): # type: (str) -> None
145 | Attr.__init__(self, style)
146 |
147 |
148 | class Fillcolor(Attr):
149 | def __init__(self, fillcolor): # type: (str) -> None
150 | Attr.__init__(self, fillcolor)
151 |
--------------------------------------------------------------------------------
/graphviz_artist/graph.py:
--------------------------------------------------------------------------------
1 | from graphviz import Digraph as _Digraph
2 | from graphviz.dot import Dot as _Dot
3 | from .attr import *
4 | from typing import *
5 |
6 | __all__ = ['Graph', 'By', 'Node']
7 |
8 |
9 | class Graph(object):
10 | def __init__(self, *attrs, **kwargs): # type: (*Attr, **object) -> None
11 | """
12 | e.g.:
13 | # all edges are default to be directed
14 | >> g = Graph(directed=True)
15 |
16 | # set custom dot graph, should be typed `graphviz.Digraph`
17 | >> g = Graph(dot=graphviz.Digraph(...)
18 | """
19 | directed = kwargs.get('directed', False)
20 | g = kwargs.get('dot')
21 |
22 | g = g or _Digraph() # type: _Dot
23 | self.g = g # type: _Dot
24 |
25 | self.count = {}
26 | self.attrs = attrs
27 |
28 | self.default_directed = Directed(directed) # type: Attr
29 |
30 | def update(self, attrs=()): # type: (*Attr) -> Graph
31 | if attrs:
32 | self.attrs = _merge_attr(self.attrs, attrs)
33 | config = {}
34 | for each in self.attrs:
35 | each.dump_(config)
36 | self.g.graph_attr.update(config)
37 | return self
38 |
39 | def new(self, *attrs, **kwds): # type: (*Attr, **Attr) -> Node
40 | attrs = attrs + tuple(kwds.values())
41 | node = Node(self, attrs)
42 | count = self.count[node.token] = len(self.count)
43 | node.name = str(count)
44 | node.update()
45 | return node
46 |
47 | def view(self, *args, **kwargs):
48 | self.update()
49 | return self.g.view(*args, **kwargs)
50 |
51 | def save(self, filename=None, directory=None):
52 | self.update()
53 | return self.g.save(filename, directory)
54 |
55 |
56 | class By:
57 | def __init__(self, node, attrs): # type: (Node, Tuple[Attr, ...]) -> None
58 | self.node = node
59 | self.attrs = attrs
60 |
61 | def _op(self, op, other):
62 | if isinstance(other, By):
63 | other = other.node
64 | return op(self.node, other, self.attrs)
65 |
66 | def __eq__(self, other): # type: (Union[Node, By]) -> bool
67 |
68 | return self._op(lambda a, b, attrs: a.__eq__(b, attrs), other)
69 |
70 | def __gt__(self, other): # type: (Union[Node, By]) -> bool
71 | return self._op(lambda a, b, attrs: a.__gt__(b, attrs), other)
72 |
73 | def __lt__(self, other): # type: (Union[Node, By]) -> bool
74 | return self._op(lambda a, b, attrs: a.__lt__(b, attrs), other)
75 |
76 |
77 | class Node(object):
78 | def __init__(self, g, attrs):
79 | # type: (Graph, Tuple[Attr, ...]) -> None
80 | self.attrs = attrs
81 | self.name = None
82 | self.g = g
83 | self.token = object()
84 |
85 | def update(self, *attrs): # type: (*Attr) -> Node
86 |
87 | if attrs:
88 | self.attrs = _merge_attr(self.attrs, attrs)
89 |
90 | config = {}
91 | for each in self.attrs:
92 | each.dump_(config)
93 | self.g.g.node(self.name, **config)
94 | return self
95 |
96 | def __getitem__(self,
97 | attrs): # type: (Union[Attr, Tuple[Attr, ...]]) -> By
98 | if not isinstance(attrs, tuple):
99 | attrs = (attrs, )
100 |
101 | return By(self, attrs)
102 |
103 | def __eq__(
104 | self, other,
105 | attrs=()): # type: (Union['Node', By], Tuple[Attr, ...]) -> bool
106 |
107 | if isinstance(other, By):
108 | other = other.node
109 |
110 | self.__gt__(other, attrs)
111 | other.__gt__(self, attrs)
112 | return True
113 |
114 | def __gt__(self, other,
115 | attrs=()): # type: (Union[Node, By], Tuple[Attr, ...]) -> bool
116 | if isinstance(other, By):
117 | other = other.node
118 |
119 | left = self.name
120 | right = other.name
121 |
122 | config = {}
123 | self.g.default_directed.dump_(config)
124 |
125 | for each in attrs:
126 | each.dump_(config)
127 | self.g.g.edge(left, right, **config)
128 | return True
129 |
130 | def __lt__(
131 | self, other,
132 | attrs=()): # type: (Union['Node', By], Tuple[Attr, ...]) -> bool
133 | if isinstance(other, By):
134 | other = other.node
135 |
136 | return other.__gt__(self, attrs)
137 |
138 |
139 | def _merge_attr(
140 | attrs1, attrs2
141 | ): # type: (Tuple[Attr, ...], Tuple[Attr, ...]) -> Tuple[Attr, ...]
142 | if not attrs2:
143 | return attrs1
144 | if not attrs1:
145 | return attrs2
146 | t = type
147 | new_attrs = {t(attr): attr for attr in attrs1}
148 | for new_attr in attrs2:
149 | new_attrs[t(new_attr)] = new_attr
150 | return tuple(new_attrs.values())
151 |
--------------------------------------------------------------------------------
/graphviz_artist/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thautwarm/graphviz-artist/442a9351d497f4423f13b9b3129dbbd6e74f33a9/graphviz_artist/test/__init__.py
--------------------------------------------------------------------------------
/graphviz_artist/test/test_1.py:
--------------------------------------------------------------------------------
1 | import graphviz_artist as ga
2 |
3 | # make a graph
4 | g = ga.Graph()
5 |
6 | # make nodes
7 | n1 = g.new()
8 | n2 = g.new()
9 | n3 = g.new()
10 |
11 | # (>), (==) and (<) could edges for graphs.
12 | # n2 > n3 / n3 < n2 : there is an edge n2 -> n3
13 | # (==) will be introduced later, as it's only meaningful to directed edges.
14 | _ = n1 > n2 > n3 > n1
15 |
16 | g.g.save()
17 |
18 | assert """digraph {
19 | 0
20 | 1
21 | 2
22 | 0 -> 1 [dir=none]
23 | 1 -> 2 [dir=none]
24 | 2 -> 0 [dir=none]
25 | }""" == str(g.g)
26 |
--------------------------------------------------------------------------------
/graphviz_artist/test/test_2.py:
--------------------------------------------------------------------------------
1 | import graphviz_artist as ga
2 |
3 | # use attr module to see which Graphviz Attributes
4 | # could be auto-completed.
5 | import graphviz_artist.attr as attr
6 |
7 | # for some reason, the name of keyword arg has no actual semantics,
8 | # but the value does:
9 | # ga.Graph(kw=attr.XXX) == ga.Graph(_=XXX) == ga.Graph(x=XXX)
10 | g = ga.Graph(attr.HorizontalGraph)
11 |
12 | # `attr.Shape("")` to specify the shape of nodes.
13 | n1 = g.new(attr.Label('hey'), attr.Shape.diamond)
14 | n2 = g.new(attr.Label('hey'), attr.Shape.hexagon)
15 | n3 = g.new(attr.Label('you'), attr.Shape.star)
16 |
17 | # `attr.Directed()` makes a directed edge.
18 | directed = attr.Directed()
19 |
20 | # `attr.Label` to specify the text that edges display
21 | edge_label = attr.Label("passed_here")
22 |
23 | # `attr.Penwidth` to decide the width of edge glyph.
24 | edge_size = attr.Penwidth(2.)
25 |
26 | # in `a < b[b_to_c_attrs...] > c`, the edge `b -> c` will have attribute `b_to_c_attrs`.
27 | _ = n3[directed, edge_label, edge_size] > n1[directed] == n2 > n3
28 |
29 | g.save()
30 |
31 | assert """digraph {
32 | graph [rankdir=LR]
33 | 0 [label=hey shape=diamond]
34 | 1 [label=hey shape=hexagon]
35 | 2 [label=you shape=star]
36 | 2 -> 0 [label=passed_here dir=forward penwidth=2.0]
37 | 0 -> 1 [dir=forward]
38 | 1 -> 0 [dir=forward]
39 | 1 -> 2 [dir=none]
40 | }""" == str(g.g)
--------------------------------------------------------------------------------
/graphviz_artist/test/test_3.py:
--------------------------------------------------------------------------------
1 | import graphviz_artist as ga
2 | import graphviz_artist.attr as attr
3 |
4 | # make a graph
5 | g = ga.Graph(directed=True)
6 |
7 | new = g.new
8 |
9 | # decl nodes
10 | false = new(attr.Label("False"))
11 | true = new(attr.Label("True"))
12 | not_ = new(attr.Label("not"))
13 | and_ = new(attr.Label("and"))
14 |
15 | unary1 = new(attr.Label("unary"))
16 | unary2 = new(attr.Label("unary"))
17 | binary = new(attr.Label("binary"), attr.Width(2), attr.Shape.box)
18 | expr = new(attr.Label("expr"))
19 |
20 | # build graph
21 | _ = false > unary1 < not_
22 | _ = true > unary2
23 |
24 | _ = and_[attr.Label('Op')] > binary
25 |
26 | # XLabel: For edges, the label will be placed near the center of the edge.
27 | _ = unary1[attr.XLabel("Left operand")] > binary
28 | _ = unary2[attr.XLabel('Right operand')] > binary
29 | _ = binary > expr
30 |
31 | g.save()
32 |
33 | assert """digraph {
34 | 0 [label=False]
35 | 1 [label=True]
36 | 2 [label=not]
37 | 3 [label=and]
38 | 4 [label=unary]
39 | 5 [label=unary]
40 | 6 [label=binary shape=box width=2]
41 | 7 [label=expr]
42 | 0 -> 4 [dir=forward]
43 | 2 -> 4 [dir=forward]
44 | 1 -> 5 [dir=forward]
45 | 3 -> 6 [label=Op dir=forward]
46 | 4 -> 6 [dir=forward xlabel="Left operand"]
47 | 5 -> 6 [dir=forward xlabel="Right operand"]
48 | 6 -> 7 [dir=forward]
49 | }""" == str(g.g)
--------------------------------------------------------------------------------
/graphviz_artist/test/test_4.py:
--------------------------------------------------------------------------------
1 | import graphviz_artist as ga
2 |
3 | import graphviz_artist.attr as attr
4 |
5 | g = ga.Graph()
6 | n1 = g.new()
7 | n2 = g.new()
8 | n3 = g.new()
9 |
10 | _ = n1 > n2[attr.Label("woundn't work")]
11 | _ = n1 < n2[attr.Label("woundn't work")]
12 | _ = n3 == n2[attr.Label("woundn't work")]
13 |
14 | g.update(attrs=(attr.HorizontalGraph, ))
15 |
16 | g.save()
17 |
18 |
19 | assert """digraph {
20 | graph [rankdir=LR]
21 | 0
22 | 1
23 | 2
24 | 0 -> 1 [dir=none]
25 | 1 -> 0 [dir=none]
26 | 2 -> 1 [dir=none]
27 | 1 -> 2 [dir=none]
28 | }""" == str(g.g)
--------------------------------------------------------------------------------
/graphviz_artist/test/test_5.py:
--------------------------------------------------------------------------------
1 | import graphviz_artist as ga
2 |
3 | import graphviz_artist.attr as attr
4 |
5 | g = ga.Graph()
6 | n1 = g.new()
7 | n2 = g.new()
8 | n1.update(attr.Label("Hey"))
9 | n1.update(attr.Label("Holla"))
10 | _ = n1[attr.Label("ok")] == n2
11 |
12 | g.save()
13 |
14 | assert """digraph {
15 | 0
16 | 1
17 | 0 [label=Hey]
18 | 0 [label=Holla]
19 | 0 -> 1 [label=ok dir=none]
20 | 1 -> 0 [label=ok dir=none]
21 | }""" == str(g.g)
--------------------------------------------------------------------------------
/imgs/test_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thautwarm/graphviz-artist/442a9351d497f4423f13b9b3129dbbd6e74f33a9/imgs/test_1.png
--------------------------------------------------------------------------------
/imgs/test_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thautwarm/graphviz-artist/442a9351d497f4423f13b9b3129dbbd6e74f33a9/imgs/test_2.png
--------------------------------------------------------------------------------
/imgs/test_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thautwarm/graphviz-artist/442a9351d497f4423f13b9b3129dbbd6e74f33a9/imgs/test_3.png
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | from graphviz_artist.test import test_1
2 | from graphviz_artist.test import test_2
3 | from graphviz_artist.test import test_3
4 | from graphviz_artist.test import test_4
5 | from graphviz_artist.test import test_5
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | with open('README.rst', 'r') as readme:
4 | readme = readme.read()
5 |
6 |
7 | setup(
8 | name='graphviz-artist',
9 | version='0.2.1',
10 | keywords="graphviz, graph-drawing, dsl", # keywords of your project that separated by comma ","
11 | description="A chance to focus on graph drawing itself, forget APIs and other stuffs.", # a conceise introduction of your project
12 | long_description=readme,
13 | license='mit',
14 | url='https://github.com/thautwarm/graphviz-artist',
15 | author='thautwarm',
16 | author_email='twshere@outlook.com',
17 | packages=['graphviz_artist'],
18 | entry_points={"console_scripts": []},
19 | # above option specifies commands to be installed,
20 | # e.g: entry_points={"console_scripts": ["yapypy=yapypy.cmd.compiler"]}
21 | install_requires=["graphviz", "typing"],
22 | platforms="any",
23 | classifiers=[
24 | "Programming Language :: Python :: 2.7",
25 | "Programming Language :: Python :: 3.4",
26 | "Programming Language :: Python :: 3.5",
27 | "Programming Language :: Python :: 3.6",
28 | "Programming Language :: Python :: 3.7",
29 | "Programming Language :: Python :: 3.8",
30 | "Programming Language :: Python :: 3.9",
31 | "Programming Language :: Python :: Implementation :: CPython",
32 | ],
33 | zip_safe=False,
34 | )
35 |
--------------------------------------------------------------------------------