Graphinate 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphinate-0.4.0 → graphinate-0.5.0}/PKG-INFO +9 -5
- {graphinate-0.4.0 → graphinate-0.5.0}/README.md +7 -3
- graphinate-0.5.0/examples/math/graph_atlas.py +69 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/math/graphs.py +74 -20
- graphinate-0.5.0/examples/math/gui.py +108 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/web/page_links.py +12 -8
- {graphinate-0.4.0 → graphinate-0.5.0}/pyproject.toml +2 -2
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/builders.py +5 -5
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/modeling.py +14 -15
- graphinate-0.5.0/src/graphinate/server/web/static/images/logo.svg +50 -0
- graphinate-0.5.0/src/graphinate/server/web/viewer/index.html +162 -0
- graphinate-0.4.0/examples/math/graph_atlas.py +0 -52
- graphinate-0.4.0/examples/math/gui.py +0 -133
- graphinate-0.4.0/src/graphinate/server/web/static/js/murmurhash3_gc.js +0 -64
- graphinate-0.4.0/src/graphinate/server/web/viewer/index.html +0 -66
- {graphinate-0.4.0 → graphinate-0.5.0}/.coveragerc +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/dependabot.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/workflows/codeql.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/workflows/publish-docs.yaml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/workflows/publish.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/workflows/test-beta.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.github/workflows/test.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.gitignore +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/.sonarcloud.properties +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/LICENSE +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/STATS.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/acknowledge.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/assets/images/logo-128.png +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/assets/images/network_graph.png +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/assets/stylesheets/extra.css +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/dev.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/examples/code.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/examples/github.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/examples/math.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/examples/system.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/examples/web.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/gen_ref_pages.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/index.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/intro.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/start.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/usage/cli.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/docs/usage/lib.md +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/code/python_ast.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/code/python_dependencies.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/code/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/_client.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/commits_visibilty_graph.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/followers.graphql +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/followers.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/graphql.config.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/repositories.graphql +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/repositories.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/github/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/math/polygonal_graph.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/math/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/system/processes.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/system/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/examples/web/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/mkdocs.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/ethernet/traceroute.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/genric_graph.graphql +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/graphql.config.yml +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/house_of_graphs.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/social/albums.json +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/social/musicisians.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/text/nlp_graph.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/text/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/time_series/requirements.txt +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/playground/time_series/visibility_graph.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/sonar-project.properties +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/__main__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/cli.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/color.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/materializers/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/materializers/matplotlib.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/starlette/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/starlette/views.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/elements/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/elements/index.html +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/graphiql/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/graphiql/index.html +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/rapidoc/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/rapidoc/index.html +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/static/images/logo-128.png +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/static/images/network_graph.png +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/viewer/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/voyager/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/server/web/voyager/index.html +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/tools/__init__.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/tools/converters.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/tools/mutators.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/src/graphinate/typing.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/conftest.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_builders.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_cli.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_color.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_materializers.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_modeling.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_server.py +0 -0
- {graphinate-0.4.0 → graphinate-0.5.0}/tests/graphinate/test_tools.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Graphinate
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: 𝔾raphinate. Data to Graphs.
|
|
5
5
|
Project-URL: Homepage, https://erivlis.github.io/graphinate
|
|
6
6
|
Project-URL: Documentation, https://erivlis.github.io/graphinate
|
|
7
7
|
Project-URL: Bug Tracker, https://github.com/erivlis/graphinate/issues
|
|
@@ -58,7 +58,7 @@ Requires-Dist: pytest-randomly; extra == 'test'
|
|
|
58
58
|
Requires-Dist: pytest-xdist; extra == 'test'
|
|
59
59
|
Description-Content-Type: text/markdown
|
|
60
60
|
|
|
61
|
-
# [
|
|
61
|
+
# [𝔾raphinate. Data to Graphs.](https://erivlis.github.io/graphinate/)
|
|
62
62
|
|
|
63
63
|
<img height="360" src="https://github.com/erivlis/graphinate/assets/9897520/dae41f9f-69e5-4eb5-a488-87ce7f51fa32" alt="Graphinate. Data to Graphs.">
|
|
64
64
|
|
|
@@ -120,10 +120,14 @@ Description-Content-Type: text/markdown
|
|
|
120
120
|
<a href="https://snyk.io/test/github/erivlis/graphinate"><img alt="Snyk" src="https://snyk.io/test/github/erivlis/Graphinate/badge.svg"></a>
|
|
121
121
|
</td>
|
|
122
122
|
</tr>
|
|
123
|
+
<tr>
|
|
124
|
+
<td>Badge</td>
|
|
125
|
+
<td>
|
|
126
|
+
<a href="https://img.shields.io/badge/%F0%9D%94%BE%3D%7B%F0%9D%95%8D%2C%F0%9D%94%BC%7D-Graphinate-darkviolet"><img alt="Graphinate" src="https://img.shields.io/badge/%F0%9D%94%BE%3D%7B%F0%9D%95%8D%2C%F0%9D%94%BC%7D-Graphinate-darkviolet"></a>
|
|
127
|
+
</td>
|
|
128
|
+
</tr>
|
|
123
129
|
</table>
|
|
124
130
|
|
|
125
|
-
---------------------
|
|
126
|
-
|
|
127
131
|
## Introduction
|
|
128
132
|
|
|
129
133
|
### What is Graphinate?
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# [
|
|
1
|
+
# [𝔾raphinate. Data to Graphs.](https://erivlis.github.io/graphinate/)
|
|
2
2
|
|
|
3
3
|
<img height="360" src="https://github.com/erivlis/graphinate/assets/9897520/dae41f9f-69e5-4eb5-a488-87ce7f51fa32" alt="Graphinate. Data to Graphs.">
|
|
4
4
|
|
|
@@ -60,10 +60,14 @@
|
|
|
60
60
|
<a href="https://snyk.io/test/github/erivlis/graphinate"><img alt="Snyk" src="https://snyk.io/test/github/erivlis/Graphinate/badge.svg"></a>
|
|
61
61
|
</td>
|
|
62
62
|
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td>Badge</td>
|
|
65
|
+
<td>
|
|
66
|
+
<a href="https://img.shields.io/badge/%F0%9D%94%BE%3D%7B%F0%9D%95%8D%2C%F0%9D%94%BC%7D-Graphinate-darkviolet"><img alt="Graphinate" src="https://img.shields.io/badge/%F0%9D%94%BE%3D%7B%F0%9D%95%8D%2C%F0%9D%94%BC%7D-Graphinate-darkviolet"></a>
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
63
69
|
</table>
|
|
64
70
|
|
|
65
|
-
---------------------
|
|
66
|
-
|
|
67
71
|
## Introduction
|
|
68
72
|
|
|
69
73
|
### What is Graphinate?
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
|
|
3
|
+
import graphs
|
|
4
|
+
import networkx as nx
|
|
5
|
+
|
|
6
|
+
import graphinate
|
|
7
|
+
from graphinate.materializers import Materializers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def model(items: list[tuple[str, nx.Graph]]) -> graphinate.GraphModel:
|
|
11
|
+
"""
|
|
12
|
+
Generate a graph model based on the provided iterable of graphs.
|
|
13
|
+
The function creates a graph model named 'Graph Atlas' using the 'graphinate' library.
|
|
14
|
+
It then combines all the graphs from the input iterable into a single disjoint union graph using NetworkX library.
|
|
15
|
+
The function defines edges for the combined graph by iterating over all edges in the disjoint union graph and
|
|
16
|
+
yielding dictionaries with 'source' and 'target' keys representing the edge connections.
|
|
17
|
+
Finally, the function yields the created graph model containing the combined graph with defined edges.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
items: A list containing graphs to be combined into a single graph model.
|
|
21
|
+
|
|
22
|
+
Yields:
|
|
23
|
+
GraphModel: A graph model containing the combined graph with defined edges.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def items_iter(recs):
|
|
27
|
+
for name, g in recs:
|
|
28
|
+
print(name)
|
|
29
|
+
yield g
|
|
30
|
+
|
|
31
|
+
g = nx.disjoint_union_all(items_iter(items)) if len(items) > 1 else items[0][1]
|
|
32
|
+
|
|
33
|
+
graph_model = graphinate.model('Graph Atlas')
|
|
34
|
+
|
|
35
|
+
@graph_model.node(operator.itemgetter(1),
|
|
36
|
+
key=operator.itemgetter(0),
|
|
37
|
+
value=operator.itemgetter(0))
|
|
38
|
+
def nodes():
|
|
39
|
+
yield from g.nodes(data='type')
|
|
40
|
+
|
|
41
|
+
@graph_model.edge(operator.itemgetter(2),
|
|
42
|
+
source=operator.itemgetter(0),
|
|
43
|
+
target=operator.itemgetter(1),
|
|
44
|
+
label=operator.itemgetter(0, 1),
|
|
45
|
+
value=operator.itemgetter(0, 1))
|
|
46
|
+
def edge():
|
|
47
|
+
yield from g.edges.data('type')
|
|
48
|
+
|
|
49
|
+
return graph_model
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == '__main__':
|
|
53
|
+
from gui import ListboxChooser, RadiobuttonChooser
|
|
54
|
+
|
|
55
|
+
graph_atlas = graphs.atlas()
|
|
56
|
+
|
|
57
|
+
listbox_chooser = ListboxChooser('Choose Graph', graph_atlas)
|
|
58
|
+
choices = list(listbox_chooser.get_choices())
|
|
59
|
+
model = model(choices)
|
|
60
|
+
|
|
61
|
+
# or
|
|
62
|
+
# model(graph_atlas.values())
|
|
63
|
+
|
|
64
|
+
radiobutton_chooser = RadiobuttonChooser('Choose Materializer',
|
|
65
|
+
options={m.name: m.value for m in Materializers},
|
|
66
|
+
default=(None, None))
|
|
67
|
+
result = radiobutton_chooser.get_choice()
|
|
68
|
+
builder, handler = result[1]
|
|
69
|
+
graphinate.materialize(model, builder=builder, builder_output_handler=handler)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import itertools
|
|
2
|
+
import re
|
|
2
3
|
from collections.abc import Iterable
|
|
3
4
|
from typing import NewType
|
|
4
5
|
|
|
@@ -692,18 +693,20 @@ SPECIAL_GRAPHS_ADJACENCY_LISTS = {
|
|
|
692
693
|
AdjacencyList = NewType('AdjacencyList', dict[int, list[int]])
|
|
693
694
|
|
|
694
695
|
|
|
695
|
-
def ladder_ring_graph(size: int):
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
696
|
+
def ladder_ring_graph(size: int) -> nx.Graph:
|
|
697
|
+
g: nx.Graph = nx.ladder_graph(size)
|
|
698
|
+
g.add_edge(size, size * 2 - 1)
|
|
699
|
+
g.add_edge(0, size - 1)
|
|
700
|
+
g.name = f'Ladder Ring[{size}]'
|
|
701
|
+
return g
|
|
700
702
|
|
|
701
703
|
|
|
702
|
-
def ladder_mobius_graph(size: int):
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
704
|
+
def ladder_mobius_graph(size: int) -> nx.Graph:
|
|
705
|
+
g = nx.ladder_graph(size)
|
|
706
|
+
g.add_edge(size, size - 1)
|
|
707
|
+
g.add_edge(0, size * 2 - 1)
|
|
708
|
+
g.name = f'Ladder Möbius Ring[{size}]'
|
|
709
|
+
return g
|
|
707
710
|
|
|
708
711
|
|
|
709
712
|
def _cylinder_edges(circumference: int, length: int) -> Iterable[tuple[int, int]]:
|
|
@@ -721,7 +724,9 @@ def _cylinder_edges(circumference: int, length: int) -> Iterable[tuple[int, int]
|
|
|
721
724
|
|
|
722
725
|
|
|
723
726
|
def cylinder_graph(circumference: int, length: int) -> nx.Graph:
|
|
724
|
-
|
|
727
|
+
g = nx.Graph(_cylinder_edges(circumference, length))
|
|
728
|
+
g.name = f'Cylinder[circumference={circumference},length={length}]'
|
|
729
|
+
return g
|
|
725
730
|
|
|
726
731
|
|
|
727
732
|
def _spiral_edges(n, k) -> Iterable[tuple[int, int]]:
|
|
@@ -733,7 +738,9 @@ def _spiral_edges(n, k) -> Iterable[tuple[int, int]]:
|
|
|
733
738
|
|
|
734
739
|
|
|
735
740
|
def spiral_graph(n, k) -> nx.Graph:
|
|
736
|
-
|
|
741
|
+
g = nx.Graph(_spiral_edges(n, k))
|
|
742
|
+
g.name = f'Spiral[n={n},k={k}]'
|
|
743
|
+
return g
|
|
737
744
|
|
|
738
745
|
|
|
739
746
|
def _spiral_torus_edges(n, k) -> Iterable[tuple[int, int]]:
|
|
@@ -744,7 +751,20 @@ def _spiral_torus_edges(n, k) -> Iterable[tuple[int, int]]:
|
|
|
744
751
|
|
|
745
752
|
|
|
746
753
|
def spiral_torus_graph(n, k) -> nx.Graph:
|
|
747
|
-
|
|
754
|
+
g = nx.Graph(_spiral_torus_edges(n, k))
|
|
755
|
+
g.name = f'Spiral Torus[n={n},k={k}]'
|
|
756
|
+
return g
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def k_regular_edges(n, k) -> Iterable[tuple[int, int]]:
|
|
760
|
+
yield from itertools.chain.from_iterable(
|
|
761
|
+
((i, j) for j in range(i + 1, i + k + 1))
|
|
762
|
+
for i in range(n-k))
|
|
763
|
+
|
|
764
|
+
def k_regular_graph(n, k) -> nx.Graph:
|
|
765
|
+
g = nx.Graph(k_regular_edges(n, k))
|
|
766
|
+
g.name = f'Generalized Buckyball[n={n},k={k}]'
|
|
767
|
+
return g
|
|
748
768
|
|
|
749
769
|
|
|
750
770
|
def adjacency_edges(adjacency_list: AdjacencyList) -> Iterable[tuple[int, int]]:
|
|
@@ -786,23 +806,57 @@ def atlas():
|
|
|
786
806
|
"""
|
|
787
807
|
|
|
788
808
|
graph_atlas = {
|
|
809
|
+
'Triangle': nx.cycle_graph(3),
|
|
810
|
+
'Square': nx.cycle_graph(4),
|
|
811
|
+
'Square Lattice[3,3]': nx.grid_2d_graph(3, 3),
|
|
812
|
+
'Pentagon': nx.cycle_graph(5),
|
|
813
|
+
'Hexagon': nx.cycle_graph(6),
|
|
814
|
+
'Heptagon': nx.cycle_graph(7),
|
|
815
|
+
'Octagon': nx.cycle_graph(8),
|
|
789
816
|
'Tetrahedron': nx.tetrahedral_graph(),
|
|
790
817
|
'Cube': nx.hypercube_graph(3),
|
|
791
818
|
'Octahedron': nx.octahedral_graph(),
|
|
792
819
|
'Dodecahedron': nx.dodecahedral_graph(),
|
|
793
820
|
'Icosahedron': nx.icosahedral_graph(),
|
|
794
821
|
'Tesseract': nx.hypercube_graph(4),
|
|
822
|
+
'Hypercube[5]': nx.hypercube_graph(5),
|
|
795
823
|
'Truncated Cube': nx.truncated_cube_graph(),
|
|
796
824
|
'Truncated Tetrahedron': nx.truncated_tetrahedron_graph(),
|
|
797
|
-
'Ladder': nx.ladder_graph(16),
|
|
798
|
-
'Ring': ladder_ring_graph(16),
|
|
799
|
-
'Möbius': ladder_mobius_graph(16),
|
|
800
|
-
'Cylinder': cylinder_graph(6, 8),
|
|
801
|
-
'Spiral': spiral_graph(
|
|
802
|
-
'Spiral Torus': spiral_torus_graph(128, 8),
|
|
803
|
-
'
|
|
825
|
+
'Ladder[16]': nx.ladder_graph(16),
|
|
826
|
+
'Ladder Ring[16]': ladder_ring_graph(16),
|
|
827
|
+
'Ladder Möbius Ring[16]': ladder_mobius_graph(16),
|
|
828
|
+
'Cylinder[6,8]': cylinder_graph(6, 8),
|
|
829
|
+
'Spiral[128,8]': spiral_graph(128, 8),
|
|
830
|
+
'Spiral Torus[128,8]': spiral_torus_graph(128, 8),
|
|
831
|
+
'Chvátal': nx.chvatal_graph(),
|
|
832
|
+
'Circulant[10,[2]]': nx.circulant_graph(10, [2]),
|
|
833
|
+
'Desargues': nx.desargues_graph(),
|
|
834
|
+
'Dorogovtsev-Goltsev-Mendes[4]': nx.dorogovtsev_goltsev_mendes_graph(4),
|
|
835
|
+
'Frucht': nx.frucht_graph(),
|
|
836
|
+
'Heawood': nx.heawood_graph(),
|
|
837
|
+
'Hoffman-Singleton': nx.hoffman_singleton_graph(),
|
|
838
|
+
# 'Margulis-Gabber-Galil[8]': nx.margulis_gabber_galil_graph(8),
|
|
839
|
+
'Papus': nx.pappus_graph(),
|
|
840
|
+
'Petersen': nx.petersen_graph(),
|
|
841
|
+
'Sedgewick Maze': nx.sedgewick_maze_graph(),
|
|
842
|
+
'Tutte': nx.tutte_graph(),
|
|
804
843
|
}
|
|
805
844
|
|
|
806
845
|
graph_atlas.update(special_graphs())
|
|
807
846
|
|
|
847
|
+
def clean(s: str):
|
|
848
|
+
# Remove invalid characters
|
|
849
|
+
s = re.sub('[^0-9a-zA-Z_]', '_', s)
|
|
850
|
+
|
|
851
|
+
# Remove leading characters until we find a letter or underscore
|
|
852
|
+
s = re.sub('^[^a-zA-Z_]+', '', s)
|
|
853
|
+
|
|
854
|
+
return s
|
|
855
|
+
|
|
856
|
+
for name, g in graph_atlas.items():
|
|
857
|
+
_type = clean(name).capitalize()
|
|
858
|
+
nx.set_node_attributes(g, _type, 'type')
|
|
859
|
+
nx.set_edge_attributes(g, _type, 'type')
|
|
860
|
+
|
|
808
861
|
return graph_atlas
|
|
862
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModalWindow(tk.Tk):
|
|
7
|
+
def __init__(self, title: str):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.title(title)
|
|
10
|
+
self.configure_window()
|
|
11
|
+
|
|
12
|
+
def configure_window(self):
|
|
13
|
+
if platform.system().lower() == 'windows':
|
|
14
|
+
self.wm_attributes('-toolwindow', 'True')
|
|
15
|
+
self.wm_attributes('-topmost', 'True')
|
|
16
|
+
self.resizable(False, False)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RadiobuttonChooser(ModalWindow):
|
|
20
|
+
"""
|
|
21
|
+
Usage Example:
|
|
22
|
+
```python
|
|
23
|
+
radiobutton_chooser = RadiobuttonChooser("Choose an option", {"Option 1": 1, "Option 2": 2})
|
|
24
|
+
choice, value = radiobutton_chooser.get_choice()
|
|
25
|
+
print(choice, value)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, window_title: str, options: dict, default=None):
|
|
30
|
+
super().__init__(window_title)
|
|
31
|
+
self.exit_button = None
|
|
32
|
+
self.choice_var = tk.StringVar(self, None)
|
|
33
|
+
self.default = default
|
|
34
|
+
self.options = options
|
|
35
|
+
self.create_widgets()
|
|
36
|
+
self.mainloop()
|
|
37
|
+
|
|
38
|
+
def create_widgets(self):
|
|
39
|
+
frame = ttk.Frame(self, borderwidth=5, relief='solid')
|
|
40
|
+
frame.pack(padx=4, pady=4)
|
|
41
|
+
|
|
42
|
+
ttk.Label(frame, text="Output Mode:").pack()
|
|
43
|
+
|
|
44
|
+
for option in self.options:
|
|
45
|
+
ttk.Radiobutton(
|
|
46
|
+
frame,
|
|
47
|
+
text=option,
|
|
48
|
+
variable=self.choice_var,
|
|
49
|
+
value=option,
|
|
50
|
+
command=self.enable_exit_button,
|
|
51
|
+
padding=4
|
|
52
|
+
).pack(side=tk.TOP, anchor="w")
|
|
53
|
+
|
|
54
|
+
self.exit_button = ttk.Button(self, text="OK", command=self.destroy, state=tk.DISABLED)
|
|
55
|
+
self.exit_button.pack(pady=4, side=tk.BOTTOM)
|
|
56
|
+
|
|
57
|
+
def enable_exit_button(self):
|
|
58
|
+
self.exit_button['state'] = tk.NORMAL
|
|
59
|
+
|
|
60
|
+
def get_choice(self):
|
|
61
|
+
return self.choice_var.get(), self.options.get(self.choice_var.get(), self.default)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ListboxChooser(ModalWindow):
|
|
65
|
+
"""
|
|
66
|
+
Usage Example:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
listbox_chooser = ListboxChooser("Choose options", {"Option 1": 1, "Option 2": 2, "Option 3": 3})
|
|
70
|
+
for choice, value in listbox_chooser.get_choices():
|
|
71
|
+
print(choice, value)
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, window_title: str, options: dict, default=None):
|
|
76
|
+
super().__init__(window_title)
|
|
77
|
+
self.exit_button = None
|
|
78
|
+
self.choices = list(options.keys())
|
|
79
|
+
self.options = options
|
|
80
|
+
self.default = default
|
|
81
|
+
self.selection_var = tk.Variable(self)
|
|
82
|
+
self.create_widgets()
|
|
83
|
+
self.mainloop()
|
|
84
|
+
|
|
85
|
+
def create_widgets(self):
|
|
86
|
+
frame = ttk.Frame(self)
|
|
87
|
+
frame.pack(padx=2, pady=2)
|
|
88
|
+
|
|
89
|
+
listbox = tk.Listbox(frame, listvariable=tk.Variable(self, value=self.choices), selectmode="multiple",
|
|
90
|
+
height=min(len(self.choices), 50), width=max(len(c) for c in self.choices))
|
|
91
|
+
listbox.pack(side=tk.LEFT, fill=tk.BOTH)
|
|
92
|
+
|
|
93
|
+
scrollbar = ttk.Scrollbar(frame, command=listbox.yview)
|
|
94
|
+
scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH)
|
|
95
|
+
listbox.config(yscrollcommand=scrollbar.set)
|
|
96
|
+
|
|
97
|
+
listbox.bind("<<ListboxSelect>>", self.on_select)
|
|
98
|
+
|
|
99
|
+
self.exit_button = ttk.Button(self, text="OK", command=self.destroy, state=tk.DISABLED)
|
|
100
|
+
self.exit_button.pack(pady=4, side=tk.BOTTOM)
|
|
101
|
+
|
|
102
|
+
def on_select(self, event):
|
|
103
|
+
self.exit_button['state'] = tk.NORMAL
|
|
104
|
+
self.selection_var.set(event.widget.curselection())
|
|
105
|
+
|
|
106
|
+
def get_choices(self):
|
|
107
|
+
for choice in self.selection_var.get():
|
|
108
|
+
yield self.choices[choice], self.options.get(self.choices[choice], self.default)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from urllib.parse import
|
|
1
|
+
from urllib.parse import urlparse
|
|
2
2
|
|
|
3
3
|
import requests
|
|
4
4
|
from bs4 import BeautifulSoup
|
|
@@ -28,13 +28,17 @@ def page_links_graph_model(max_depth: int = DEFAULT_MAX_DEPTH):
|
|
|
28
28
|
for link in soup.find_all('a', href=True):
|
|
29
29
|
child_url = link.get('href')
|
|
30
30
|
|
|
31
|
-
if child_url.startswith('javascript:'):
|
|
31
|
+
if child_url.startswith('javascript:'): # Skip JavaScript links
|
|
32
32
|
continue
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
if child_url.startswith('//'): # Handle protocol-relative URLs
|
|
34
35
|
child_url = f"https:{child_url}"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
|
|
37
|
+
if not bool(urlparse(child_url).netloc): # Skip relative URLs
|
|
38
|
+
# child_url = urljoin(url, child_url)
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
if not child_url.startswith('http'): # Skip non-HTTP URLs
|
|
38
42
|
continue
|
|
39
43
|
|
|
40
44
|
yield {'source': url, 'target': child_url}
|
|
@@ -62,7 +66,7 @@ if __name__ == '__main__':
|
|
|
62
66
|
model=model,
|
|
63
67
|
graph_type=graphinate.GraphType.DiGraph,
|
|
64
68
|
default_node_attributes={'type': 'url'},
|
|
65
|
-
builder=graphinate.builders.
|
|
66
|
-
builder_output_handler=graphinate.
|
|
69
|
+
builder=graphinate.builders.GraphQLBuilder,
|
|
70
|
+
builder_output_handler=graphinate.graphql,
|
|
67
71
|
**params
|
|
68
72
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Graphinate"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name = "Eran Rivlis", email = "eran@rivlis.info" },
|
|
6
6
|
]
|
|
7
|
-
description = "
|
|
7
|
+
description = "𝔾raphinate. Data to Graphs."
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
classifiers = [
|
|
@@ -203,7 +203,7 @@ class NetworkxBuilder(Builder):
|
|
|
203
203
|
if node_type == 'tuple':
|
|
204
204
|
node_type = node_model.type.lower()
|
|
205
205
|
|
|
206
|
-
logger.debug("Adding node: '{}'",
|
|
206
|
+
logger.debug("Adding node: '{}'", node.value)
|
|
207
207
|
|
|
208
208
|
if node_id in self._graph:
|
|
209
209
|
self._graph.nodes[node_id]['value'].append(node.value)
|
|
@@ -231,16 +231,16 @@ class NetworkxBuilder(Builder):
|
|
|
231
231
|
self._populate_node_type(node_model.type, **new_kwargs)
|
|
232
232
|
|
|
233
233
|
def _populate_edges(self, **kwargs):
|
|
234
|
-
for
|
|
234
|
+
for edge_model, edge_generators in self.model.edge_generators.items():
|
|
235
235
|
for edge_generator in edge_generators:
|
|
236
236
|
for edge in edge_generator(**kwargs):
|
|
237
237
|
edge_id = ((edge.source,), (edge.target,))
|
|
238
238
|
edge_weight = edge.weight or 1.0
|
|
239
|
-
|
|
239
|
+
edge_type = edge.type.lower()
|
|
240
|
+
logger.debug("Adding edge from: '{}' to: '{}'", *edge_id)
|
|
240
241
|
|
|
241
242
|
if isinstance(self._graph, nx.MultiGraph) or edge_id not in self._graph.edges:
|
|
242
|
-
self._graph.add_edge(
|
|
243
|
-
(edge.target,),
|
|
243
|
+
self._graph.add_edge(*edge_id,
|
|
244
244
|
label=edge.label,
|
|
245
245
|
type=edge_type,
|
|
246
246
|
value=[edge.value],
|
|
@@ -4,7 +4,7 @@ from collections.abc import Callable, Iterable, Mapping
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import Any, Optional, Union
|
|
6
6
|
|
|
7
|
-
from .typing import Edge,
|
|
7
|
+
from .typing import Edge, Element, Extractor, Items, Node, NodeTypeAbsoluteId, UniverseNode
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class GraphModelError(Exception):
|
|
@@ -59,17 +59,14 @@ def elements(iterable: Iterable[Any],
|
|
|
59
59
|
Returns:
|
|
60
60
|
Iterable of Elements.
|
|
61
61
|
"""
|
|
62
|
+
for item in iterable:
|
|
63
|
+
_type = element_type(item) if element_type and callable(element_type) else element_type
|
|
64
|
+
if not _type.isidentifier():
|
|
65
|
+
raise ValueError(f"Invalid Type: {_type}. Must be a valid Python identifier.")
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
kwargs = {k: extractor(item, v) for k, v in getters.items()}
|
|
67
|
-
yield create_element(**kwargs)
|
|
68
|
-
else:
|
|
69
|
-
create_element = element(element_type, getters.keys())
|
|
70
|
-
for item in iterable:
|
|
71
|
-
kwargs = {k: extractor(item, v) for k, v in getters.items()}
|
|
72
|
-
yield create_element(**kwargs)
|
|
67
|
+
create_element = element(_type, getters.keys())
|
|
68
|
+
kwargs = {k: extractor(item, v) for k, v in getters.items()}
|
|
69
|
+
yield create_element(**kwargs)
|
|
73
70
|
|
|
74
71
|
|
|
75
72
|
@dataclass
|
|
@@ -92,7 +89,7 @@ class NodeModel:
|
|
|
92
89
|
parent_type: Optional[str] = UniverseNode
|
|
93
90
|
uniqueness: bool = False
|
|
94
91
|
parameters: set[str] | None = None
|
|
95
|
-
generator:
|
|
92
|
+
generator: Callable[[], Iterable[Node]] | None = None
|
|
96
93
|
label: Callable[[Any], str | None] = None
|
|
97
94
|
|
|
98
95
|
@property
|
|
@@ -114,7 +111,7 @@ class GraphModel:
|
|
|
114
111
|
self.name: str = name
|
|
115
112
|
self._node_models: dict[NodeTypeAbsoluteId, NodeModel] = {}
|
|
116
113
|
self._node_children: dict[str, list[str]] = defaultdict(list)
|
|
117
|
-
self._edge_generators: dict[str, list[
|
|
114
|
+
self._edge_generators: dict[str, list[Callable[[], Iterable[Edge]]]] = defaultdict(list)
|
|
118
115
|
self._networkx_graph = None
|
|
119
116
|
|
|
120
117
|
def __add__(self, other: 'GraphModel'):
|
|
@@ -225,7 +222,7 @@ class GraphModel:
|
|
|
225
222
|
return register_node
|
|
226
223
|
|
|
227
224
|
def edge(self,
|
|
228
|
-
_type: Optional[
|
|
225
|
+
_type: Optional[Extractor] = None,
|
|
229
226
|
source: Extractor = 'source',
|
|
230
227
|
target: Extractor = 'target',
|
|
231
228
|
label: Optional[Extractor] = str,
|
|
@@ -252,6 +249,8 @@ class GraphModel:
|
|
|
252
249
|
edge_type = _type or f.__name__
|
|
253
250
|
self._validate_type(edge_type)
|
|
254
251
|
|
|
252
|
+
model_type = f.__name__ if callable(edge_type) else edge_type
|
|
253
|
+
|
|
255
254
|
getters = {
|
|
256
255
|
'source': source,
|
|
257
256
|
'target': target,
|
|
@@ -264,7 +263,7 @@ class GraphModel:
|
|
|
264
263
|
def edge_generator(**kwargs) -> Iterable[Edge]:
|
|
265
264
|
yield from elements(f(**kwargs), edge_type, **getters)
|
|
266
265
|
|
|
267
|
-
self._edge_generators[
|
|
266
|
+
self._edge_generators[model_type].append(edge_generator)
|
|
268
267
|
|
|
269
268
|
return register_edge
|
|
270
269
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Filters for shadow -->
|
|
3
|
+
<defs>
|
|
4
|
+
<filter id="circle-shadow" x="-50%" y="-50%" width="200%" height="500%">
|
|
5
|
+
<feDropShadow dx="6" dy="6" stdDeviation="1" flood-color="rgba(0, 0, 0, 0.8)"/>
|
|
6
|
+
</filter>
|
|
7
|
+
<filter id="text-shadow" x="-50%" y="-50%" width="200%" height="500%">
|
|
8
|
+
<feDropShadow dx="5" dy="5" stdDeviation="1" flood-color="rgba(0, 0, 0, 0.6)"/>
|
|
9
|
+
</filter>
|
|
10
|
+
</defs>
|
|
11
|
+
|
|
12
|
+
<text x="10" y="30"
|
|
13
|
+
font-family="serif"
|
|
14
|
+
font-size="40"
|
|
15
|
+
font-weight="500"
|
|
16
|
+
fill="darkviolet"
|
|
17
|
+
stroke="indigo"
|
|
18
|
+
stroke-width="0.5"
|
|
19
|
+
filter="url(#text-shadow)">𝔾raphinate
|
|
20
|
+
</text>
|
|
21
|
+
<!-- Background -->
|
|
22
|
+
<!--rect width="100%" height="100%" fill="#2E004F" /-->
|
|
23
|
+
|
|
24
|
+
<!-- Circle with shadow -->
|
|
25
|
+
<circle cx="100" cy="100" r="60" fill="indigo" stroke="rebeccapurple" stroke-width="0.5"
|
|
26
|
+
filter="url(#circle-shadow)"/>
|
|
27
|
+
<!-- Double-struck G with shadow -->
|
|
28
|
+
<text x="99" y="113"
|
|
29
|
+
font-family="serif"
|
|
30
|
+
font-size="108"
|
|
31
|
+
fill="darkviolet"
|
|
32
|
+
text-anchor="middle"
|
|
33
|
+
stroke="indigo"
|
|
34
|
+
stroke-width="0.5"
|
|
35
|
+
alignment-baseline="middle"
|
|
36
|
+
filter="url(#text-shadow)">
|
|
37
|
+
𝔾
|
|
38
|
+
</text>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
<text x="10" y="190"
|
|
42
|
+
font-family="serif"
|
|
43
|
+
font-size="40"
|
|
44
|
+
font-weight="500"
|
|
45
|
+
fill="darkviolet"
|
|
46
|
+
stroke="indigo"
|
|
47
|
+
stroke-width="0.5"
|
|
48
|
+
filter="url(#text-shadow)">𝔾raphinate
|
|
49
|
+
</text>
|
|
50
|
+
</svg>
|