{"id":13929385,"url":"https://github.com/bdcht/grandalf","last_synced_at":"2025-09-04T15:46:41.739Z","repository":{"id":56825637,"uuid":"1792493","full_name":"bdcht/grandalf","owner":"bdcht","description":"graph and drawing algorithms framework","archived":false,"fork":false,"pushed_at":"2024-08-10T10:13:35.000Z","size":1144,"stargazers_count":250,"open_issues_count":12,"forks_count":45,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-08-29T21:25:22.317Z","etag":null,"topics":["drawings","graphs","layout-engine"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bdcht.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-05-24T08:51:59.000Z","updated_at":"2025-08-24T20:44:49.000Z","dependencies_parsed_at":"2025-01-15T13:10:25.429Z","dependency_job_id":"87118d69-0d80-48e9-a64b-8c2b26583d57","html_url":"https://github.com/bdcht/grandalf","commit_stats":{"total_commits":159,"total_committers":7,"mean_commits":"22.714285714285715","dds":"0.16981132075471694","last_synced_commit":"ee80dec38a801c78a9cd72e2d4cbe56809fbf60e"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/bdcht/grandalf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdcht%2Fgrandalf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdcht%2Fgrandalf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdcht%2Fgrandalf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdcht%2Fgrandalf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bdcht","download_url":"https://codeload.github.com/bdcht/grandalf/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdcht%2Fgrandalf/sbom","scorecard":{"id":229010,"data":{"date":"2025-08-11","repo":{"name":"github.com/bdcht/grandalf","commit":"ee80dec38a801c78a9cd72e2d4cbe56809fbf60e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T04:28:07.392Z","repository_id":56825637,"created_at":"2025-08-17T04:28:07.393Z","updated_at":"2025-08-17T04:28:07.393Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273633103,"owners_count":25140772,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drawings","graphs","layout-engine"],"created_at":"2024-08-07T18:02:18.761Z","updated_at":"2025-09-04T15:46:41.709Z","avatar_url":"https://github.com/bdcht.png","language":"Python","funding_links":[],"categories":["others","Python"],"sub_categories":[],"readme":"========\nGrandalf\n========\n\n--------------------------------------\nGraph and drawing algorithms framework\n--------------------------------------\n\n.. image:: https://travis-ci.org/bdcht/grandalf.svg?branch=master\n    :target: https://travis-ci.org/bdcht/grandalf\n\n.. image:: https://img.shields.io/lgtm/grade/python/g/bdcht/grandalf.svg?logo=lgtm\u0026logoWidth=18\n    :target: https://lgtm.com/projects/g/bdcht/grandalf/context:python\n    :alt: Code Quality\n\n.. image:: https://img.shields.io/pypi/dm/grandalf.svg\n    :target: https://pypi.python.org/pypi/grandalf\n\n\n+-----------+--------------------------------------+\n| Status:   | Under Development                    |\n+-----------+--------------------------------------+\n| Location: | https://github.com/bdcht/grandalf    |\n+-----------+--------------------------------------+\n| Version:  | 0.8                                  |\n+-----------+--------------------------------------+\n\nDescription\n===========\n\nGrandalf is a python package made for experimentations with graphs drawing\nalgorithms. It is written in pure python, and currently implements two layouts:\nthe Sugiyama hierarchical layout and the force-driven or energy minimization approach.\nWhile not as fast or featured as graphviz_ or other libraries like OGDF_ (C++),\nit provides a way to *walk* and *draw* graphs\nno larger than thousands of nodes, while keeping the source code simple enough\nto make it possible to easily tweak and hack any part of it for experimental purpose.\nWith a total of about 1500 lines of python, the code involved in\ndrawing the Sugiyama (dot) layout fits in less than 600 lines.\nThe energy minimization approach is comprised of only 250 lines!\n\nGrandalf does only 2 not-so-simple things:\n\n- computing the nodes (x,y) coordinates\n  (based on provided nodes dimensions, and a chosen layout)\n- routing the edges with lines or nurbs\n\nIt doesn't depend on any GTK/Qt/whatever graphics toolkit.\nThis means that it will help you find *where* to\ndraw things like nodes and edges, but it's up to you to actually draw things with\nyour favorite graphics toolkit.\n\nScreenshots and Videos\n======================\n\nSee examples listed in the Wiki_.\n\nHere is a screenshot showing the result of rendering the control flow graph of\na function:\n\n.. image:: https://raw.github.com/bdcht/grandalf/master/doc/screenshot-1.png\n\n\nInstall\n=======\n\nGrandalf suggests the following python packages:\n\n- http://pypi.python.org/pypi/numpy, for the directed-constrained layout\n- http://www.dabeaz.com/ply, for importing graphs from graphviz_ *dot* files.\n\nQuickstart\n==========\n\nLook for examples in ``tests/``. Here is a very simple example:\n\n.. code-block:: python\n\n \u003e\u003e\u003e from grandalf.graphs import Vertex,Edge,Graph,graph_core\n \u003e\u003e\u003e V = [Vertex(data) for data in range(10)]\n \u003e\u003e\u003e X = [(0,1),(0,2),(1,3),(2,3),(4,0),(1,4),(4,5),(5,6),(3,6),(3,7),(6,8),\n  ... (7,8),(8,9),(5,9)]\n \u003e\u003e\u003e E = [Edge(V[v],V[w]) for (v,w) in X]\n \u003e\u003e\u003e g = Graph(V,E)\n \u003e\u003e\u003e g.C\n [\u003cgrandalf.graphs.graph_core at 0x7fb23a95e4c0\u003e]\n \u003e\u003e\u003e print([v.data for v in g.path(V[1],V[9])])\n [1, 4, 5, 9]\n \u003e\u003e\u003e g.add_edge(Edge(V[9],Vertex(10)))\n \u003cgrandalf.graphs.Edge object at 0x7fb23a95e3a0\u003e\n \u003e\u003e\u003e g.remove_edge(V[5].e_to(V[9]))\n \u003cgrandalf.graphs.Edge object at 0x7fb23a95e0a0\u003e\n \u003e\u003e\u003e print([v.data for v in g.path(V[1],V[9])])\n [1, 3, 6, 8, 9]\n \u003e\u003e\u003e g.remove_vertex(V[8])\n \u003cgrandalf.graphs.Vertex object at 0x7fb23a933dc0\u003e\n \u003e\u003e\u003e len(g.C)\n 2\n \u003e\u003e\u003e print(g.path(V[1],V[9]))\n None\n \u003e\u003e\u003e for e in g.C[1].E(): print(\"%s -\u003e %s\"%(e.v[0].data,e.v[1].data))\n ...\n 9 -\u003e 10\n \u003e\u003e\u003e from grandalf.layouts import SugiyamaLayout\n \u003e\u003e\u003e class defaultview(object):\n ...   w,h = 10,10\n ...\n \u003e\u003e\u003e for v in V: v.view = defaultview()\n ...\n \u003e\u003e\u003e sug = SugiyamaLayout(g.C[0])\n \u003e\u003e\u003e sug.init_all(roots=[V[0]],inverted_edges=[V[4].e_to(V[0])])\n \u003e\u003e\u003e sug.draw()\n \u003e\u003e\u003e for v in g.C[0].sV: print(\"%s: (%d,%d)\"%(v.data,v.view.xy[0],v.view.xy[1]))\n ...\n 4: (30,65)\n 5: (30,95)\n 0: (0,5)\n 2: (-30,35)\n 1: (15,35)\n 3: (-15,65)\n 7: (-30,95)\n 6: (15,125)\n \u003e\u003e\u003e for l in sug.layers:\n ...   for n in l: print(n.view.xy,end='')\n ...   print('')\n ...\n (0.0, 5.0)\n (-30.0, 35.0)(15.0, 35.0)(45.0, 35.0)\n (-15.0, 65.0)(30.0, 65.0)\n (-30.0, 95.0)(0.0, 95.0)(30.0, 95.0)\n (15.0, 125.0)\n \u003e\u003e\u003e for e,d in sug.ctrls.items():\n ...   print('long edge %s --\u003e %s points:'%(e.v[0].data,e.v[1].data))\n ...   for r,v in d.items(): print(\"%s %s %s\"%(v.view.xy,'at rank',r))\n ...\n long edge 3 --\u003e 6 points:\n (-15.0, 65.0) at rank 2\n (15.0, 125.0) at rank 4\n (0.0, 95.0) at rank 3\n long edge 4 --\u003e 0 points:\n (0.0, 5.0) at rank 0\n (30.0, 65.0) at rank 2\n (45.0, 35.0) at rank 1\n\nOverview\n========\n\n*graph.py*\n----------\nContains the \"mathematical\" methods related to graphs.\nThis module defines the classes:\n\n- Vertex (and vertex_core)\n- Edge (and edge_core)\n- Graph (and graph_core)\n\nVertex.\n~~~~~~~\nA Vertex object is defined by a data field holding whatever you want\nassociated to that vertex. It inherits from a vertex_core that --- when the\nVertex is added into a graph --- is holding the list of edges connected to\nthis Vertex and provides all methods associated to the properties of the\nvertex inside the graph (degree, list of neigbors, list of input edges,\noutput edges, etc).\nOf course, unless a Vertex belongs to a graph, all properties are empty or\nNone.\nExample:\n\n.. code-block:: python\n\n \u003e\u003e\u003e v1 = Vertex('a')\n \u003e\u003e\u003e v2 = Vertex('b')\n \u003e\u003e\u003e v3 = Vertex('c')\n \u003e\u003e\u003e v1.data\n 'a'\n\nEdge.\n~~~~~\nAn Edge is defined by a pair of Vertex objects. If the graph is directed, the\ndirection of the edge is induced by the e.v list order otherwise the order is\nirrelevant. See Usage section for details.\nExample:\n\n.. code-block:: python\n\n \u003e\u003e\u003e e1 = Edge(v1,v2)\n \u003e\u003e\u003e e2 = Edge(v1,v3,w=2)\n\nOptional arguments includes a weight (defaults to 1) and a data holding\nwhatever you want associated with the edge (defaults to None). Edge weight\nare used by the Dijkstra algorithm for finding 'shortest' paths with\nrespect to these weights.\n\ngraph_core.\n~~~~~~~~~~~\nA graph_core is used to hold a connected graph only. If the graph is not\nconnected (ie there exists two vertex that can't be connected by an\nundirected path), then an exception is raised.\nUse of the Graph class is preferable unless you really know that your graph\nis connected.\nExample:\n\n.. code-block:: python\n\n \u003e\u003e\u003e g  = graph_core([v1,v2,v3],[e1,e2])\n\nThe graph object can be updated by g.add_edge(e), g.remove_edge(e) or\ng.remove_vertex(v) which all raise an exception if connectivity is lost. Note\nthat add_edge() will possibly extend the graph's vertex set with at most one\nnew Vertex found in the added edge.\nSee the Usage section for further details.\n\nGraph.\n~~~~~~\nThis is the main class for graphs. The resulting graph is stored as \"Disjoint\nSets\" by processing the input lists of Vertex and Edge objects into a list of\ngraph_core components.\nExample:\n\n.. code-block:: python\n\n \u003e\u003e\u003e v4,v5 = Vertex(4),Vertex(5)\n \u003e\u003e\u003e g = Graph([v1,v2,v3,v4],[e1,e2])\n\nThe graph object can be updated by g.add_vertex(v), g.add_edge(e),\ng.remove_vertex(v) and g.remove_edge(e) which all may result in updating a\ngraph_core, creating a new graph_core, or removing a graph_core from the\ngraph's internal list.\n\n*layouts.py*\n------------\nContains the \"drawing\" algorithms.\nThis module defines the classes:\n\n- Layer\n- SugiyamaLayout\n- DigcoLayout\n\nSugiyamaLayout.\n~~~~~~~~~~~~~~~\nThis class performs a 2D hierarchical placement of a connected graph.\nThe algorithm works only for directed acyclic graphs (DAG), so that a\n\"feedback acyclic set\" of edges is needed.\nTo create a graph layout, you need to provide:\n\n- a graph_core object where every Vertex has been equiped with a '.view'\n  interface providing the width and height of the graphical representation of\n  the Vertex (in our terminology, a Vertex equiped with a '.view' is a \"node\"\n  of the graph)\n\nTo initiate the drawing (init_all) you will optionally provide:\n\n- the list of \"root\" nodes\n- the list of feedback acyclic edges\n- constraint parameter related to how inverted edges are routed\n\nIn order to minimize edge crossings between each consecutive layers, the\nalgorithm uses several rounds of nodes reordering (draw(N)). Increasing this\nparameter N can lead to layout with less crossings.\nFor educational or debugging purpose, the drawing computation can be observed\nstep-by-step (draw_step).\n\nDigcoLayout.\n~~~~~~~~~~~~\nThis class performs a 2D hierarchical placement of a connected graph.\nThe main difference with SugiyamaLayout is that this algorithm is based on\noptimization theory rather than on heuristics. It computes the node\ncoordinates by minimization of an \"energy\" function that describes the stress\nfactor associated to a layout.\nThis approach allows to take into account new constraints on node placement.\nTo create a graph layout, you only need to provide:\n- a graph_core object where every Vertex has been equiped with a '.view'\n\n*routing.py*\n------------\nContains the edge routing algorithms.\nThis module defines the classes and functions:\n\n- EdgeViewer\n- route_with_lines\n- route_with_splines\n\nEdgeViewer.\n~~~~~~~~~~~\nThis class provides a default 'view' for edges. Edges with no view will be\nignored by the draw_edge method of the layouts. If a view is provided it must\nbe equiped with a 'setpath' method to which a list of waypoints will be\npassed.\n\nroute_with_lines.\n~~~~~~~~~~~~~~~~~\nThis function allows to adjust the waypoints of the edge. It allows to\ndraw a poly-line edge going through all points computed by the layout engine\nand adjusts the tail head position on the boundary of their nodes and\nprecomputes the head angle.\nTo use this routing method,  set the route_edge field of the layout instance\nto this function (sug.route_edge = route_with_lines).\n\nroute_with_splines.\n~~~~~~~~~~~~~~~~~~~\nThis function allows to draw edges by a combination of lines and bezier\ncurves. The curves are computed such that corners of a poly-line edge given\nby route_with_lines are rounded.\nTo use this routing method,  set the route_edge field of the layout instance\nto this function (sug.route_edge = route_with_splines) and use the values\nreturned in the .splines field of the edge view :\n- an array of 2 points defines a line\n- an array of 4 points defines a bezier curve.\n\n*utils.py*\n----------\nProvides utilities like partially ordered sets, linear programming solvers,\nparsers for external formats (Dot, etc.)\nThis module defines :\n\n- Poset\n- Dot\n\nand some general purpose functions like:\n\n- intersect2lines\n- intersectR\n- getangle (computing the atan2 value for directed edge heading)\n- intersectC\n- setcurve (computing a nurbs locally interpolating a given set of points)\n- setroundcorner\n\nPoset.\n~~~~~~\nThis class is used by graph_core for both efficiently detecting if a Vertex\nor Edge is in a graph (using builtin set()) and ensuring that elements of\nthe set are iterated always in the same order (using builtin list()).\nBasically, a Poset is pair (set,list) that is kept synchronized.\n\nDot.\n~~~~\nThis class contains a PLY lexer and parser for the graphviz dot format.\nThe parser reads all graphs currently defined in graphviz_\n``graphs/{directed,undirected}/*.gvi``\nas well as the dg.dot and ug.dot databases (\u003e 5000 graphs parsed OK)\nincluding *latin1* and *utf8* support (see russian.gv or Latin1.gv).\n\nsetcurve.\n~~~~~~~~~\nThis function is used internally for edge routing. It is based on an method\ndescribed in \"The NURBS Book\" (Les A. Piegl, Wayne Tiller, Springer 1997)\nimplementing local interpolation of a given set of points with a set of\nnon-uniform b-splines of degree 3. The non-uniform knots are ignored.\n\nsetroundcorner.\n~~~~~~~~~~~~~~~\nThis function uses setcurve to smooth the polyline edge at each corner. This\nmethod provides the best result for edge routing with the SugiyamaLayout.\nIt is used in the route_with_splines function in routing.py.\n\n*tests/*\n--------\nContains many testing procedures as well as some graph samples.\n\n\nUsage and Pitfalls\n==================\n\nRather than an exhaustive library reference with all methods for all classes,\n(see Python help() for that) we focus on a typical usage of grandalf and try to\nalso emphasize important notes.\n\n\nGraph creation\n--------------\n\nLets start by creating an empty graph:\n\n.. code-block:: python\n\n \u003e\u003e\u003e g = Graph()\n\nWether you first create the graph and add elements in it or create it after all\nVertex and Edge objects have been defined, is up to you.\nFor the moment the graph has no components :\n\n.. code-block:: python\n\n \u003e\u003e\u003e g.order()\n 0\n \u003e\u003e\u003e g.C\n []\n\nLets create some vertices now.\n\n.. code-block:: python\n\n \u003e\u003e\u003e v1 = Vertex('a')\n \u003e\u003e\u003e v2 = Vertex('b')\n \u003e\u003e\u003e v3 = Vertex()\n \u003e\u003e\u003e v3.data = 'c'\n \u003e\u003e\u003e v1.data\n 'a'\n\nFirst, note that the 'data' field is optional and can be added anytime in the\nvertex. We are associating a string to this field so that it is easy to\nidentify a given vertex, but keep in mind that this data is not needed for\ngraph computations and drawings.\nFor the moment, the vertex objects are \"free\" in the sense that they are not\nassociated with any graph_core object. When a vertex belongs to a graph_core,\nthe reference to this graph_core is found in the 'c' field (component field).\n\nTo insert a Vertex in a Graph object we do:\n\n.. code-block:: python\n\n \u003e\u003e\u003e g.add_vertex(v1)\n\nor we can add a new edge, then any new vertex it the edge will be attached to\nthe graph also:\n\n.. code-block:: python\n\n \u003e\u003e\u003e e1 = Edge(v1,v2)\n \u003e\u003e\u003e e2 = Edge(v1,v3,w=2)\n \u003e\u003e\u003e g.add_edge(e1)\n \u003e\u003e\u003e g.add_edge(e2)\n \u003e\u003e\u003e v2 in g.C[0]\n True\n\nWarning: Vertex and Edge objects MUST belong to only one graph_core object at a\ntime. So you should never use the same Vertex/Edge into another graph without\nremoving it first from the current one !\nOf course, removing a vertex also removes all edges linked to it.\n\n.. code-block:: python\n\n \u003e\u003e\u003e g.remove_vertex(v1)\n \u003e\u003e\u003e e1 in g\n False\n \u003e\u003e\u003e len(g.C)\n 3\n\nRemoving v1 here has removed e1 and e2, and the graph g is now cut in 3\ncomponents holding each one vertex only. Lets rebuild the graph and extend it:\n\n.. code-block:: python\n\n \u003e\u003e\u003e g.add_edge(e1)\n \u003e\u003e\u003e g.add_edge(e2)\n \u003e\u003e\u003e v4,v5 = Vertex(4),Vertex(5)\n \u003e\u003e\u003e g.add_edge(Edge(v4,v5))\n\nNow g has two graph_core objects in g.C, and if\n\n.. code-block:: python\n\n \u003e\u003e\u003e g.add_edge(Edge(v5,v3))\n\nthe cores are merged in one component only.\n\n\nGraph drawing\n-------------\n\nThere are many possible layouts when it comes to graph drawings.\nThe current layout implemented is a hierarchical 2D layout suited for\n*directed* graphs based on an method proposed by Sugiyama et al.\nOur implementation is derived from the paper by Brandes \u0026 Kopf (GD 2001.)\nThis method is quite efficient but is based on many heuristics that are not\neasy to tweak when you want to add some constraints like for example\n\"I want that nodes with property P to be placed near each others.\"\n\nThe \"dig-cola\" method is based on a different approach where graph properties\nare expressed as constraints on node's coordinates, reducing the problem to\nsolving a set of inequalities with unknowns being the x,y coords of every\nnodes. With this approach, adding new contraints is very simple.\nThe dig-cola method is implemented in old commits and is currently being\nrewritten to match the design of SugiyamaLayout.\n\nIn Grandalf, a layout engine only applies on a graph_core object.\nBasically drawing a Graph() requires that you draw all its connex components\nand decide how to organize the entire drawing by moving each component where\nyou want. Since some methods involve \"dummy\" nodes inserted in the graph, it is\nimportant to note that layout classes are completely separated from the\noriginal : the underlying graph_core topology is never permanently modified.\nThis means that redrawing a graph for whatever reason (vertex added, edges\nadded, etc) is as simple as creating a new layout instance.\nOf course, if you know what you are doing, you can try to update the drawing\nbased on the current layout instance but unless modifications of the topology\nare very simple, this can be very difficult (enhancing this adaptative drawing\npart is definetly in the TODO list!).\n\nBefore creating a layout engine associated with a graph_core, each vertex MUST\nbe equiped with what we call a 'view'. For a vertex v, such view must be an\nobject with attributes\n\n- ``w`` (width) and\n- ``h`` (height),\n- ``xy`` (position)\n\nand the layout engine will set the v.view.xy field with a (x,y) tuple value\ncorresponding to the center of the node.\nIn practice, this allows to use ``view`` objects that inherits from graphic\nwidgets (e.g. a rectangle in a Canvas) which will position the widget in the\ncanvas when the xy attribute is set.\n\nIf you want the layout to perform also edge routing, you MAY equipe edges also\nwith a 'view' attribute. For an edge e, the view must have a ``setpath`` method\ntaking a list of points as argument.\nThe layout engine will provide the list of (x,y) routing points, starting by\nthe ``e.v[0].view.xy``, then all intermediate dummy vertices position through\nwhich the edge drawing should go, including the e.v[1].view.xy last point.\nThe routing.py module provides enhanced routing functions as well as a\nrepresentative EdgeViewer class to help finding the exact position where\ndrawing the 'tail' or the 'arrowhead' or define a set of splines made of Bezier\ncurves so that almost any curve Canvas primitive can be used.\n\n\nSugiyamaLayout\n~~~~~~~~~~~~~~\nThe Sugiyama layout draws a graph by separating the nodes in several layers.\nThese layers are stacked one under the others. The first layer contains the\n\"root\" nodes.\n\nthe root nodes and the feedback edges sets\n++++++++++++++++++++++++++++++++++++++++++\nMost of the time, you don't need to bother with these notions because\ninit_all() will find the needed root nodes and feedback edges. Still, in some\ncases it may help to know about these essential sets:\n\nThe Sugiyama layout is made for directed acyclic graphs. So the first requirement\nfor this layout is to have the list of inverted edges\n(aka the feedback acyclic set needed to make the graph acyclic when needed.)\nThese edges are inverted in the graph_core only during some specific operations\nand are reverted immediately after these computations.\nFor example, the graph is made acyclic for ranking the nodes into hierarchical\nlayers.\nThe graph_core class contains a method that computes the \"strongly connected\nsets\" of the graph_core by using the Tarjan algorithm (get_scs_with_feedback).\nA strongly connected set is a subset of vertex where for any two vertices A B,\nthere exist a directed path from A to B.\nOf course a cycle is a strongly connected set, but such set may contain several\ninterlaced cycles. The algorithm constructs the \"feedback acyclic set\" by\ntagging the edges with the 'feedback' field set to True. It performs a DFS\nstarting from the given set of nodes.\nA good choice is of course to start with the set of nodes that have no incoming\nedges, but if this set is empty (because the graph is cyclic) you will have to\nchoose a preferred set :\nHence,\n\n.. code-block:: python\n\n \u003e\u003e\u003e r = filter(lambda x: len(x.e_in())==0, gr.sV)\n \u003e\u003e\u003e if len(r)==0: r = [my_guessed_root_node]\n \u003e\u003e\u003e L = gr.get_scs_with_feedback(r)\n \u003e\u003e\u003e inverted_edges=filter(lambda x:x.feedback, gr.sE)\n\nleads to L containing the SCS of the ``gr`` component, and the feedback set is\nthen obtained by filter edges with the feedback flag.\n\nAs mentioned before, drawing with the SugiyamaLayout engine also requires that\nyou provide the list of \"root\" nodes.\nIts up to you to decide which nodes are the \"roots\", but the natural definition\nis as stated before :\n\n.. code-block:: python\n\n \u003e\u003e\u003e gr = g.C[0]\n \u003e\u003e\u003e r = filter(lambda x: len(x.e_in())==0, gr.sV)\n\nthat is, the list r of vertex with no incoming edges.\nWarning: if r is empty, you might want to use the set of edges computed before\nto temporarily remove cycles and retry (look at ``__edge_inverter`` method.)\n\nthe init_all() and draw() methods\n+++++++++++++++++++++++++++++++++\nDrawing the gr component by computing .view.xy coordinates just resumes to:\n\n.. code-block:: python\n\n \u003e\u003e\u003e sug = SugiyamaLayout(gr)\n \u003e\u003e\u003e sug.init_all()\n \u003e\u003e\u003e sug.draw()\n\nThis will perform ONE round of the drawing algorithm. A single\nround means that the node placement has been performed from the top layer to the\nbottom layer and back to top. This may not be sufficient to reduce the edge\ncrossings, so you can draw again or simply provide the number of pass to\nperform:\n\n.. code-block:: python\n\n \u003e\u003e\u003e sug.draw(3)\n\nIf you want to be able to draw the graph while the engine is running, you can\nuse the draw_step() iterator which yields at each layer during the forward and\nbackward trip.\n\nThen, drawing the graph with a graphical canvas can be done by drawing each\nviews at their xy positions and either defining a ``setpath`` method that will\nbe called by grandalf draw_edges() with a set of routing points, or by using\npredefined functions in ``routing.py`` like ``route_with_lines`` or\n``route_with_splines``.\n\nIf you have installed masr_, just do:\n\n.. code-block:: bash\n\n $ cd /path/to/grandalf\n $ ./masr-graph tests/samples/brandes.dot\n\nWhen a node is focused, the SPACE key is bound to draw_step().next(). This\nwill show how the algorithm tries to reduce edge crossing in each layer by\nmodifying the layer ordering. Modified nodes will appear with green shadow.\nThe P key will cycle through the 4 internal alignment policies\n(top-left, top-right, bottom-left, bottom-right.)\n\nOptionally, inverted edges can be constrained to always start from the bottom\nof their init vertex, and end on the top of their terminal vertex.\n\n.. code-block:: bash\n\n $ ./masr-graph tests/samples/manhattan1.dot -ce\n\n\nDigcoLayout\n~~~~~~~~~~~\nThe DigcoLayout stands for \"Directed Graph Constrained Layout\". The method was\nproposed by Dwyer \u0026 Koren in a paper presented at InfoVis 2005. It relies on\na stress minimization approach (similar to force-driven layouts like /neato/)\nwith hierarchical properties taken into account as additional constraints on\nnode coordinates.\n\nthe init_all() and draw() methods\n+++++++++++++++++++++++++++++++++\nLike for SugiyamaLayout, just do for example:\n\n.. code-block:: python\n\n \u003e\u003e\u003e dco = DigcoLayout(gr)\n \u003e\u003e\u003e dco.init_all()\n \u003e\u003e\u003e dco.draw(limit=100)\n\nThe init_all() method will take into account hierarchical information if the\ngraph is directed, and will randomly choose an initial distribution of node\ncoordinates. The draw() method will then converge towards the optimal solution\nby using a conjugate-gradient method.\nThe ``limit`` parameter (defaults to gr.order() if not provided,) controls the\nmaximum iteration count of the convergence loop.\nFIXME: In the current implementation, hierarchical levels are not taken into\naccount as additional constraints.\n\nIf you have installed masr_, just do:\n\n.. code-block:: bash\n\n $ cd /path/to/grandalf\n $ masr-graph -digco -N 25 tests/samples/circle.dot\n\nOr, you may visualize each step of the convergence by:\n\n.. code-block:: bash\n\n $ masr-graph -digco -N 1 tests/samples/circle.dot\n\nNow mouse-focus one of the nodes and press SPACE to see the next iteration.\nCheck out the masr/plugins/graph code to see how it works!\n\nTODO\n====\n- add hierarchical constraints in DigcoLayout to support directed graphs\n- add support for GraphML format import/export\n- add support for pgf/tikz export\n- provide facilities for efficient (interactive) edge re-routing\n\nFAQ\n===\n1. Why is there no 'add_vertex()' method in the graph_core class ?\n\n Because graph_core are connected graphs, only add_single_vertex() makes sense.\n If you want to add a vertex directly into a graph_core, the vertex must be\n connected with an edge to another vertex already in the graph_core\n (use add_edge()).\n However, if the graph is empty, the first vertex can be attached to the graph\n by using add_single_vertex().\n\n.. _graphviz: https://www.graphviz.org/\n.. _OGDF: https://ogdf.uos.de/\n.. _amoco: https://github.com/bdcht/amoco\n.. _masr: https://github.com/bdcht/masr\n.. _Wiki: https://github.com/bdcht/grandalf/wiki\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdcht%2Fgrandalf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbdcht%2Fgrandalf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdcht%2Fgrandalf/lists"}