diff --git a/PackageInfo.g b/PackageInfo.g index cfb5046..1e9d83f 100644 --- a/PackageInfo.g +++ b/PackageInfo.g @@ -31,6 +31,8 @@ Persons := [ rec( FirstNames := "Matthew", LastName := "Pancer", + WWWHome := "https://github.com/mpan322", + # TODO make personal website Email := "mp322@st-andrews.ac.uk", IsAuthor := true, IsMaintainer := true, @@ -55,8 +57,6 @@ ArchiveURL := Concatenation(~.PackageWWWHome, ~.Version), ArchiveFormats := ".tar.gz", -AbstractHTML := "TODO", - PackageDoc := rec( BookName := "graphviz", ArchiveURLSubset := ["doc"], @@ -75,4 +75,30 @@ Dependencies := rec( AvailabilityTest := ReturnTrue, -TestFile := "tst/testall.g")); +TestFile := "tst/testall.g", + +AutoDoc := rec( + TitlePage := rec( + Copyright := """©right; by J. D. Mitchell and M. Pancer.

+ &GAPGraphviz; is free software; you can redistribute it and/or modify + it, under the terms of the GNU General Public License, version 3 of + the License, or (at your option) any later, version.""", + Abstract := """ + This package facilitates the creation and rendering of graph + descriptions in the ˙ language of the &Graphviz; graph drawing + software from ⪆. +

+ + Create a graphviz object, assemble the graph by adding nodes and edges, + and retrieve its ˙ source code string. Save the source code to a file + and render it with the &Graphviz; installation of your system. +

+ + Use the function to directly inspect the resulting + graph. +

+ + This package was inspired by the python package of the same name + &PyGraphviz;.""")), + +AbstractHTML := ~.AutoDoc.TitlePage.Abstract)); diff --git a/README.md b/README.md index 7242a81..8b0cb2c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,126 @@ -# The GAP package graphviz +## README -TODO: add a description of your package; perhaps also instructions how how to -install and use it, resp. where to find out more +### Graphviz package for GAP -## Contact +#### Copyright (C) 2024 by J. D. Mitchell and M. Pancer -TODO: add info on how to contact you and/or how to report issues with your -package +## Graphviz for GAP + +This package facilitates the creation and rendering of graph +descriptions in the [DOT][] language of the [Graphviz][] graph drawing +software from [GAP][]. + +You can create a graphviz object, assemble the graph by adding nodes and +edges, attributes, labels, colours, subgraphs, and clusters, and +retrieve its [DOT][] source code string. Save the source code to a file +and render it with the [Graphviz] installation on your system. + +You can use the [Splash] function to directly inspect the resulting +graph. + +This package was inspired by the python package of the same name +[Python Graphviz][]. ## License -TODO: Provide information on the license of your package. A license is -important as it determines who has a right to distribute your package. The -"default" license to consider is GNU General Public License v2 or later, as -that is the license of GAP itself. +This package is distributed under the GNU General Public License v2 or later. +See the `LICENSE` file for more details. + +## Links + +- GitHub: [https://github.com/digraphs/graphviz](https://github.com/digraphs/graphviz) +- Documentation: TODO +- Changelog: TODO +- Issue Tracker: [https://github.com/digraphs/graphviz/issues](https://github.com/digraphs/graphviz/issues) +- Download: TODO + +## Installation + +This package requires [GAP][] version 4.11.0 or higher. The most +up-to-date version of GAP, and instructions on how to install it, can be +obtained from the [main GAP webpage](https://www.gap-system.org). This +package has no further dependencies! + +### From sources + +To get the latest version of the package, download the archive file +`graphviz-x.x.x.tar.gz` from the [Graphviz package for GAP webpage][]. +Then, inside the `pkg` subdirectory of your GAP installation, unpack the +archive `graphviz-x.x.x.tar.gz` in your `gap/pkg` directory, using + + gunzip graphviz-x.x.x.tar.gz; tar xvf graphviz-x.x.x.tar + +for example. This will create a subdirectory `graphviz-x.x.x`. + +### Using the [PackageManager][] + +Start GAP in the usual way, then type: + + LoadPackage("PackageManager"); + InstallPackage("graphviz"); + +## Quickstart + +Create a graph object: + + gap> LoadPackage("graphviz"); + ─────────────────────────────────────────────────────────────────────────────────── + Loading graphviz 0.0.0 (TODO) + by James D. Mitchell (https://jdbm.me) and + Matthew Pancer (mp322@st-andrews.ac.uk). + Homepage: https://digraphs.github.io/graphviz + Report issues at https://github.com/digraphs/graphviz/issues + ─────────────────────────────────────────────────────────────────────────────────── + true + gap> dot := GraphvizDigraph("The Round Table"); + + +Add nodes and edges: + + gap> GraphvizSetAttr(GraphvizAddNode(dot, "A"), "label", "King Arthur"); + + gap> GraphvizSetAttr(GraphvizAddNode(dot, "B"), "label", "Sir Bedevere the Wise"); + + gap> GraphvizSetAttr(GraphvizAddNode(dot, "L"), "label", "Sir Lancelot the Brave"); + + gap> GraphvizAddEdge(dot, "A", "B"); + + gap> GraphvizAddEdge(dot, "A", "L"); + + gap> GraphvizSetAttr(GraphvizAddEdge(dot, "B", "L"), "constraint", false); + +Check the generated source code: + + gap> Print(AsString(dot)); + //dot + digraph { + A [label="King Arthur"] + B [label="Sir Bedevere the Wise"] + L [label="Sir Lancelot the Brave"] + A -> B + A -> L + B -> L [constraint=false] + } + +Save the source code: + + gap> FileString("round-table.gv", AsString(dot)); + 134 + +Render and view the result: + + gap> Splash(dot); + +![The Round Table](https://raw.github.com/digraphs/graphviz/main/docs/png/The_Round_Table.png) + +## Issues + +For questions, remarks, suggestions, and issues please use the +[issue tracker](https://github.com/digraphs/graphviz/issues). + +[DOT]: https://www.graphviz.org/doc/info/lang.html +[GAP]: https://www.gap-system.org +[Graphviz]: https://www.graphviz.org +[Graphviz webpage]: https://digraphs.github.io/Digraphs +[PackageManager]: https://gap-packages.github.io/PackageManager +[Python Graphviz]: https://pypi.org/project/graphviz/ diff --git a/gap/dot.gd b/gap/dot.gd index b91b7f8..1320241 100644 --- a/gap/dot.gd +++ b/gap/dot.gd @@ -9,85 +9,310 @@ ## #! @Chapter -#! @ChapterTitle An introduction to the DOT language and Graphviz. -#! This chapter explains what the DOT and graphviz are, -#! key basic concepts relating to them, and how this package interacts with them. - -#! @Section A Brief Introduction -#! DOT is a language for descrbing to a computer how to display a visualization -#! for a graph or digraph. Graphviz is a graph visualization software which can -#! consume DOT and produce visual outputs. This package is designed to allow -#! users to programmatically construct objects in GAP which can then be -#! converted into DOT. That DOT can then be inputted into the graphviz software -#! to produce a visual output. As DOT is central to the design of this package +#! @ChapterTitle Getting started +#! This chapter very briefly explains what ˙ and &Graphviz; are, provides +#! some key basic concepts relating to them, and how this package interacts +#! with them. + +#! @Section A brief introduction +#! ˙ is a language for descrbing to a computer how to display a visualization +#! of a graph or digraph. &Graphviz; is a graph visualization software which can +#! consume ˙ and produce visual outputs. This package is designed to allow +#! users to programmatically construct objects in ⪆ which can then be +#! converted into ˙. That ˙ can then be input to the &Graphviz; software +#! to produce a visual output. As ˙ is central to the design of this package #! it will likely be helpful to have a basic understanding of the language. -#! For more information about DOT see +#! For more information about ˙ see #! https://graphviz.org/doc/info/lang.html. - -#! @Chapter -#! @ChapterTitle The Graphviz Package +#!

+#! +#! The &GAPGraphviz; package for ⪆ is intended to facilitate the creation +#! and rendering of graph descriptions in the ˙ language of the &Graphviz; +#! graph drawing software. +#!

+#! +#! You can create a &GAPGraphviz; object, assemble the graph by adding nodes +#! and edges, setting attributes, labels and so on, and retrieve its &DOT; +#! source code string. You can save the source code +#! to a file (using ) and render it +#! with the &Graphviz; installation of your system; or you can +#! use the function to directly inspect the resulting +#! graph (depending on your system and the software installed). + +#! @Section What this package is not +#! +#! This package does not implement a parser of the &DOT; language and does only +#! minimal checks when assembling a graph. In particular, if you set attributes +#! which don't exist in &DOT;, then the resulting string might not be valid, +#! and might not render correctly using &Graphviz;. + +#! @Section A first example +#! Here's an example of how to use the &GAPGraphviz; package, to construct a +#! &DOT; representation of a finite state automata. This example is taken from +#! https://graphviz.readthedocs.io/en/stable/examples.html or +#! https://graphviz.org/Gallery/directed/fsm.html. +#! +#! @BeginLogSession +#! gap> LoadPackage("graphviz");; +#! gap> f := GraphvizDigraph("finite_state_machine"); +#! +#! gap> GraphvizSetAttr(f, "rankdir=LR"); +#! +#! gap> GraphvizSetAttr(f, "size=\"8,5\""); +#! +#! gap> terminals := GraphvizAddContext(f, "terminals"); +#! +#! gap> GraphvizSetAttr(terminals, "node [shape=doublecircle]"); +#! +#! gap> GraphvizAddNode(terminals, "LR_0"); +#! +#! gap> GraphvizAddNode(terminals, "LR_3"); +#! +#! gap> GraphvizAddNode(terminals, "LR_4"); +#! +#! gap> GraphvizAddNode(terminals, "LR_8"); +#! +#! gap> nodes := GraphvizAddContext(f, "nodes"); +#! +#! gap> GraphvizSetAttr(nodes, "node [shape=circle]"); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_2"), +#! > "label", "\"SS(B)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_1"), +#! > "label", "\"SS(S)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_1", "LR_3"), +#! > "label", "\"S($end)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_6"), +#! > "label", "\"SS(b)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_5"), +#! > "label", "\"SS(a)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_4"), +#! > "label", "\"S(A)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_5", "LR_7"), +#! > "label", "\"S(b)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_5", "LR_5"), +#! > "label", "\"S(a)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_6", "LR_6"), +#! > "label", "\"S(b)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_6", "LR_5"), +#! > "label", "\"S(a)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_7", "LR_8"), +#! > "label", "\"S(b)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_7", "LR_5"), +#! > "label", "\"S(a)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_8", "LR_6"), +#! > "label", "\"S(b)\""); +#! +#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_8", "LR_5"), +#! > "label", "\"S(a)\""); +#! +#! gap> Print(AsString(f), "\n"); +#! //dot +#! digraph finite_state_machine { +#! rankdir=LR size="8,5" +#! // terminals context +#! node [shape=doublecircle] +#! LR_0 +#! LR_3 +#! LR_4 +#! LR_8 +#! rankdir=LR size="8,5" +#! +#! // nodes context +#! node [shape=circle] +#! LR_2 +#! LR_0 -> LR_2 [label="SS(B)"] +#! LR_1 +#! LR_0 -> LR_1 [label="SS(S)"] +#! LR_1 -> LR_3 [label="S($end)"] +#! LR_6 +#! LR_2 -> LR_6 [label="SS(b)"] +#! LR_5 +#! LR_2 -> LR_5 [label="SS(a)"] +#! LR_2 -> LR_4 [label="S(A)"] +#! LR_7 +#! LR_5 -> LR_7 [label="S(b)"] +#! LR_5 -> LR_5 [label="S(a)"] +#! LR_6 -> LR_6 [label="S(b)"] +#! LR_6 -> LR_5 [label="S(a)"] +#! LR_7 -> LR_8 [label="S(b)"] +#! LR_7 -> LR_5 [label="S(a)"] +#! LR_8 -> LR_6 [label="S(b)"] +#! LR_8 -> LR_5 [label="S(a)"] +#! rankdir=LR size="8,5" +#! +#! } +#! gap> Splash(f); +#! @EndLogSession +#! +#! Provided that you have &Graphviz; installed on your computer, the last line +#! of the example Splash(f) would produce the following picture: +#! +#! +#! +#! +#! +#! ]]> +#! +#! +#! There are lots more examples in the examples directory within the +#! &Graphviz; package for &GAP; directory. + +#! @Chapter Full Reference +#! This chapter contains all of the gory details about the functionality of the +#! &GAPGraphviz; package for &GAP;. #! @Section Graphviz Categories - -#! @BeginGroup Filters -#! @Description Every object in graphviz belongs to the IsGraphvizObject +#! @BeginGroup +#! Every object in graphviz belongs to the IsGraphvizObject #! category. The categories following it are for further specificity on the -#! type of objects. These are graphs, digraphs, nodes and edges respectively. -#! All are direct subcategories of IsGraphvizObject excluding IsGraphvizDigraph -#! which is a subcategory of is IsGraphvizGraph. - +#! type of objects. These are graphs, digraphs, contexts, nodes, and +#! edges, and combinations of these that have some common features. DeclareCategory("IsGraphvizObject", IsObject); - DeclareCategory("IsGraphvizGraphDigraphOrContext", IsGraphvizObject); DeclareCategory("IsGraphvizGraph", IsGraphvizGraphDigraphOrContext); DeclareCategory("IsGraphvizDigraph", IsGraphvizGraphDigraphOrContext); DeclareCategory("IsGraphvizContext", IsGraphvizGraphDigraphOrContext); - DeclareCategory("IsGraphvizNodeOrEdge", IsGraphvizObject); DeclareCategory("IsGraphvizNode", IsGraphvizNodeOrEdge); DeclareCategory("IsGraphvizEdge", IsGraphvizNodeOrEdge); +#! The names of these categories are fairly descriptive, where a graph +#! has undirected edges, a digraph has directed edges, and a context is +#! a part of a &Graphviz; file/string consisting of objects (nodes, +#! edges, further contexts, subgraphs etc) that share some common +#! attributes. #! @EndGroup #! @Section Constructors #! @BeginGroup -#! @GroupTitle Constructors for Graphs +#! @GroupTitle Creating a new &GAPGraphviz; graphs #! @Arguments name -#! @Returns a new graphviz graph -#! @Description Creates a new graphviz graph optionally with the provided name. +#! @Returns A new &GAPGraphviz; graph. +#! @Description These operations create a new &GAPGraphviz; graph objects. +#! +#! In the first form, the created &GAPGraphviz; graph object has name +#! name. In the second form, the constructed &GAPGraphviz; graph +#! object has an empty string as a name. +#! +#! The argument name can be any &GAP; object for which there is a +#! method, and the name of the +#! created object will be equal to String(name). +#! +#! A "graph" in &Graphviz; has undirected edges that are represented +#! using the string "--" in the &DOT; language. +#! +#! See also: +#! * +#! * +#! * +#! +#! @BeginExampleSession +#! gap> gv := GraphvizGraph("GraphyMcGraphFace"); +#! +#! gap> GraphvizName(gv); +#! "GraphyMcGraphFace" +#! gap> GraphvizGraph(666); +#! +#! gap> gv := GraphvizGraph(); +#! +#! gap> GraphvizName(gv); +#! "" +#! @EndExampleSession DeclareOperation("GraphvizGraph", [IsObject]); DeclareOperation("GraphvizGraph", []); #! @EndGroup #! @BeginGroup -#! @GroupTitle Constructors for Digraphs +#! @GroupTitle Creating a new &GAPGraphviz; digraphs #! @Arguments name -#! @Returns a new graphviz digraph -#! @Description Creates a new graphviz digraph optionally with the provided name. +#! @Returns A new &GAPGraphviz; digraph. +#! @Description These operations create a new &GAPGraphviz; digraph objects. +#! +#! In the first form, the created &GAPGraphviz; digraph object has name +#! name. In the second form, the constructed &GAPGraphviz; digraph +#! object has an empty string as a name. +#! +#! The argument name can be any &GAP; object for which there is a +#! method, and the name of the +#! created object will be equal to String(name). +#! +#! A "digraph" in &Graphviz; has directed edges that are represented +#! using the string "->" in the &DOT; language. +#! +#! See also: +#! * +#! * +#! * +#! +#! @BeginExampleSession +#! gap> gv := GraphvizDigraph("GraphyMcGraphFace"); +#! +#! gap> GraphvizName(gv); +#! "GraphyMcGraphFace" +#! gap> GraphvizDigraph(666); +#! +#! gap> gv := GraphvizDigraph(); +#! +#! gap> GraphvizName(gv); +#! "" +#! @EndExampleSession DeclareOperation("GraphvizDigraph", [IsObject]); DeclareOperation("GraphvizDigraph", []); #! @EndGroup -#! @Section Get Operations -#! This section covers the operations for getting information about graphviz -#! objects. - -#! @Subsection For all graphviz objects. +#! @Section Getters for any object +#! This section covers the operations for getting information about +#! &GAPGraphviz; any object. #! @Arguments obj -#! @Returns the name of the provided graphviz object -#! @Description Gets the name of the provided graphviz object. +#! @Returns A string. +#! @Description If the argument obj is a &GAPGraphviz; object +#! (), then this +#! function returns the name of the &Graphviz; object obj. +#! +#! @BeginExampleSession +#! gap> dot := GraphvizDigraph("The Round Table");; +#! gap> GraphvizName(dot); +#! "The Round Table" +#! gap> n := GraphvizSetAttr(GraphvizAddNode(dot, "A"), "label", "King Arthur"); +#! gap> GraphvizName(n); +#! "A" +#! gap> e := GraphvizAddEdge(dot, "A", "B");; +#! gap> GraphvizName(e); +#! "(A, B)" +#! @EndExampleSession DeclareOperation("GraphvizName", [IsGraphvizObject]); #! @Arguments obj #! @Returns the attributes of the provided graphviz object #! @Description Gets the attributes of the provided graphviz object. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession +# HERE DeclareOperation("GraphvizAttrs", [IsGraphvizObject]); -#! @Subsection For only graphs and digraphs. +#! @Section Getters for graphs and digraphs #! @Arguments graph -#! @Returns the nodes of the provided graphviz graph. +#! @Returns the nodes of the provided graphviz graph +#! as a mapping from node ids to names. #! @Description Gets the nodes of the provided graphviz graph. # From https://graphviz.org/doc/info/lang.html # An ID is one of the following: @@ -97,17 +322,26 @@ DeclareOperation("GraphvizAttrs", [IsGraphvizObject]); # any double-quoted string ("...") possibly containing escaped quotes (\")¹; # an HTML string (<...>). # TODO specify +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizNodes", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph #! @Returns the subgraphs of the provided graphviz graph. #! @Description gets the subgraphs of a provided graphviz graph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizSubgraphs", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph #! @Returns the contexts of the provided graphviz graph, digraph or context. #! @Description gets the contexts of a provided graphviz graph, digraph #! or context. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizContexts", [IsGraphvizGraphDigraphOrContext]); #! @Arguments graph, name @@ -116,43 +350,69 @@ DeclareOperation("GraphvizContexts", [IsGraphvizGraphDigraphOrContext]); #! Searches through the tree of subgraphs connected to this subgraph for a graph #! with the provided name. #! It returns the graph if it exists. -#! If no such graph exists then it will return fail. +#! If no such graph exists then it will return fail. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizFindSubgraphRecursive", [IsGraphvizGraphDigraphOrContext, IsObject]); +#! @BeginGroup +#! @GroupTitle Getting Graphviz Edges #! @Arguments graph #! @Returns the edges of the provided graphviz graph. #! @Description Gets the edges of the provided graphviz graph. +#! If a head and tail are provided will only return edges +#! between those two nodes. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizEdges", [IsGraphvizGraphDigraphOrContext]); +#! @Arguments graph, head, tail DeclareOperation("GraphvizEdges", [IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); +#! @EndGroup #! @Subsection For only edges. +#! This section contains getters only applicable to graphviz edges. #! @Arguments edge #! @Returns the head of the provided graphviz edge. #! @Description Gets the head of the provided graphviz graph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizHead", [IsGraphvizEdge]); #! @Arguments edge #! @Returns the head of the provided graphviz tail. #! @Description Gets the tail of the provided graphviz graph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizTail", [IsGraphvizEdge]); #! @Section Set Operations #! This section covers operations for modifying graphviz objects. #! @Subsection For modifying graphs. +#! Operations below only pertain to graphs, digraphs and contexts. #! @Arguments graph, name #! @Returns the modified graph. #! @Description Sets the name of a graphviz graph or digraph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizSetName", [IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, node #! @Returns the modified graph. #! @Description Adds a node to the graph. #! If a node with the same name is already present the operation fails. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizAddNode", [IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, edge @@ -163,44 +423,93 @@ DeclareOperation("GraphvizAddNode", [IsGraphvizGraphDigraphOrContext, IsObject]) #! then the operation fails. #! TODO I dont believe this is accurate - think it will connect existing ones #! underlying private function would fail though - TODO double check. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizAddEdge", [IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); -#! @Arguments graph, filter, name +#! @BeginGroup +#! @GroupTitle Adding Subgraphs +#! @Arguments graph, name #! @Returns the new subgraph. #! @Description Adds a subgraph to a graph. +#! The type of structure (graph or digraph) will be the same as the parent graph. +#! At the moment it is not possible to add an existing graph as a +#! subgraph of another graph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizAddSubgraph", [IsGraphvizGraphDigraphOrContext, IsObject]); +#! @Arguments graph DeclareOperation("GraphvizAddSubgraph", [IsGraphvizGraphDigraphOrContext]); +#! @EndGroup -#! @Arguments graph, filter, name +#! @BeginGroup +#! @GroupTitle Adding Contexts +#! @Arguments graph, name #! @Returns the new context. #! @Description Adds a context to a graph. +#! A context can be thought as being similar to a subgraph +#! when manipulating it in this package. +#! However, when rendered contexts do not +#! create a subgraph in outputted DOT code. +#! Instead their nodes are rendered inline within the parent graph. +#! This allows for scoping node and edge attributes +#! without modifying the rendering behaviour. +#! The type of graph edge (directed or undirected) +#! will be the same as the parent graph. +#! At the moment it is not possible to add an existing context to +#! a new graph. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizAddContext", [IsGraphvizGraphDigraphOrContext, IsObject]); +#! @Arguments graph DeclareOperation("GraphvizAddContext", [IsGraphvizGraphDigraphOrContext]); +#! @EndGroup #! @Arguments graph, node #! @Returns the modified graph. #! @Description Removes the node from the graph. +#! The node attribute may be an object, string or graphviz node. +#! Objects will be converted to strings. +#! Strings are then interpreted as the id of the node to remove. +#! All edges containing the node are also removed. +#! If no such node exists the operation fails. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizRemoveNode", [IsGraphvizGraphDigraphOrContext, IsObject]); #! @Arguments graph, predicate #! @Returns the modified graph. #! @Description Filters the graph's edges using the provided predicate. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizFilterEdges", [IsGraphvizGraphDigraphOrContext, IsFunction]); -#! @Arguments graph, head_name, tail_name +#! @Arguments graph, head_id, tail_id #! @Returns the modified graph. #! @Description Filters the graph's edges, removing edges between nodes with #! the specified names. +#! If no edges exist between the two nodes, the operation fails. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizRemoveEdges", [IsGraphvizGraphDigraphOrContext, IsObject, IsObject]); -#! @Subsection For modifying object attributes. +#! @Subsection Modifying object attributes +#! Operations for modifying attributes. +#! @BeginGroup +#! @GroupTitle Setting Attributes #! @Arguments obj, attrs #! @Returns the modified object. #! @Description @@ -208,18 +517,34 @@ DeclareOperation("GraphvizRemoveEdges", #! All current attributes remain. #! If an attribute already exists and a new value is provided, the old value #! will be overwritten. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizSetAttrs", [IsGraphvizObject, IsRecord]); +#! @Arguments obj, name, value DeclareOperation("GraphvizSetAttr", [IsGraphvizObject, IsObject, IsObject]); +#! @Arguments obj, name DeclareOperation("GraphvizSetAttr", [IsGraphvizObject, IsObject]); +#! @EndGroup #! @Arguments obj, attr #! @Returns the modified object. #! @Description Removes an attribute from the object provided. +#! If no attributes are removed then the operation fails. +#! Attributes may be removed by key or by +#! key-value pair eg. "label" or "label=\"test\"". +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizRemoveAttr", [IsGraphvizObject, IsObject]); #! @Section Outputting #! @Arguments graph #! @Returns the dot representation of the graphviz object. +#! @Description TODO +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("AsString", [IsGraphvizGraphDigraphOrContext]); #! @Arguments obj @@ -227,24 +552,81 @@ DeclareOperation("AsString", [IsGraphvizGraphDigraphOrContext]); #! @Description #! Unimplemented operation which depending packages can implement. #! Should output the graphviz package representation of the object. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("Graphviz", [IsObject]); +#! @Section Shortcuts +#! @Arguments graph, colours +#! @Returns the modified object +#! @Description +#! Sets the colors of the nodes in the (di)graph. +#! If there are a different number of colours than nodes the operation fails. +#! Also sets the node style to filled. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizSetNodeColors", [IsGraphvizGraphDigraphOrContext, IsList]); + +#! @Arguments graph, labels +#! @Returns the modified object +#! @Description +#! Sets the labels of the nodes in the (di)graph. +#! If there are fewer labels than nodes the operation fails. +#! If there is an invalid label the operation halts there and fails. +#! What constitutes a valid label can be found here, +#! "https://graphviz.org/doc/info/lang.html". +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("GraphvizSetNodeLabels", [IsGraphvizGraphDigraphOrContext, IsList]); +#! @Arguments color +#! @Returns true or false +#! @Description +#! Determines if the color provided is a valid graphviz color. +#! Valid graphviz colors are described here, +#! "http://graphviz.org/doc/info/colors.html". +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareGlobalFunction("ErrorIfNotValidColor"); -# TODO doc +#! @BeginGroup +#! @GroupTitle Getting attributes +#! @Arguments edge, attr +#! @Returns the value associated with the provided attribute. +#! @Description +#! Gets the value associated with the attribute attr. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession +DeclareOperation("\[\]", [IsGraphvizEdge, IsObject]); +#! @Arguments node, attr DeclareOperation("\[\]", [IsGraphvizNode, IsObject]); -# TODO doc -DeclareOperation("\[\]\:\=", [IsGraphvizNode, IsObject, IsObject]); +#! @EndGroup -# TODO doc -DeclareOperation("\[\]", [IsGraphvizEdge, IsObject]); -# TODO doc +#! @BeginGroup +#! @GroupTitle Setting attributes +#! @Arguments node, attr +#! @Description +#! Sets the value associated with the attribute attr. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession +DeclareOperation("\[\]\:\=", [IsGraphvizNode, IsObject, IsObject]); +#! @Arguments edge, attr DeclareOperation("\[\]\:\=", [IsGraphvizEdge, IsObject, IsObject]); +#! @EndGroup -# TODO doc +#! @Arguments graph, node_name +#! @Returns The associated node or fail if no such node exists. +#! @Description +#! Gets a node from a (di)graph by id. +#! @BeginExampleSession +#! gap> +#! @EndExampleSession DeclareOperation("\[\]", [IsGraphvizGraphDigraphOrContext, IsObject]); diff --git a/gap/dot.gi b/gap/dot.gi index f3be316..6766cae 100644 --- a/gap/dot.gi +++ b/gap/dot.gi @@ -27,6 +27,9 @@ function(name) Counter := 1)); end); +InstallMethod(GraphvizGraph, "for an object", [IsObject], +obj -> GraphvizGraph(String(obj))); + InstallMethod(GraphvizGraph, "for no args", [], {} -> GraphvizGraph("")); InstallMethod(GraphvizDigraph, "for a string", [IsString], @@ -46,6 +49,9 @@ end); InstallMethod(GraphvizDigraph, "for no args", [], {} -> GraphvizDigraph("")); +InstallMethod(GraphvizDigraph, "for an object", [IsObject], +obj -> GraphvizDigraph(String(obj))); + ############################################################################# # ViewString ############################################################################# @@ -54,15 +60,7 @@ InstallMethod(PrintString, "for a graphviz node", [IsGraphvizNode], n -> StringFormatted("", GraphvizName(n))); InstallMethod(PrintString, "for a graphviz edge", [IsGraphvizEdge], -function(e) - local head, tail; - head := GraphvizHead(e); - tail := GraphvizTail(e); - - return StringFormatted("", - GraphvizName(head), - GraphvizName(tail)); -end); +e -> StringFormatted("", GraphvizName(e))); InstallMethod(PrintString, "for a graphviz (di)graph or context", [IsGraphvizGraphDigraphOrContext], diff --git a/gap/gv.gi b/gap/gv.gi index 4d52941..7cba024 100644 --- a/gap/gv.gi +++ b/gap/gv.gi @@ -147,9 +147,12 @@ InstallMethod(GV_Edge, "for two graphviz nodes", [IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode], function(graph, head, tail) local out; + out := Objectify(GV_EdgeType, rec( - Name := "", + Name := StringFormatted("({}, {})", + GraphvizName(head), + GraphvizName(tail)), Head := head, Tail := tail, Attrs := GV_Map(), diff --git a/gap/splash.gd b/gap/splash.gd index 919a68a..22e9857 100644 --- a/gap/splash.gd +++ b/gap/splash.gd @@ -12,4 +12,88 @@ # function was written by A. Egri-Nagy and Manuel Delgado, with some minor # modifications by J. Mitchell. +#! @ChapterInfo Full Reference, Outputting +#! @Arguments x[, opts] +#! @Returns Nothing. +#! @Description +#! This function attempts to convert the string str into a pdf +#! document and open this document, i.e. to splash it all over your monitor.

+#! +#! The argument x must be one of: TODO +#! +#! must correspond to a valid dot or +#! LaTeX text file and you must have have GraphViz and +#! pdflatex installed on your computer. For details about these file +#! formats, see https://www.latex-project.org and +#! https://www.graphviz.org.

+#! +#! The optional second argument opts should be a record with +#! components corresponding to various options, given below. +#! +#! +#! path +#! +#! this should be a string representing the path to the directory where +#! you want Splash to do its work. The default value of this +#! option is "~/". +#! +#! +#! directory +#! +#! this should be a string representing the name of the directory in +#! path where you want Splash to do its work. This function +#! will create this directory if does not already exist.

+#! +#! The default value of this option is "tmp.viz" if the option +#! path is present, and the result of +#! is used otherwise. +#! +#! +#! filename +#! +#! this should be a string representing the name of the file where +#! str will be written. The default value of this option is +#! "vizpicture". +#! +#! +#! viewer +#! +#! this should be a string representing the name of the program which +#! should open the files produced by GraphViz or pdflatex. +#! +#! +#! type +#! +#! this option can be used to specify that the string str contains +#! a &LaTeX; or dot document. You can specify this option in +#! str directly by making the first line "%latex" or +#! "//dot". There is no default value for this option, this +#! option must be specified in str or in opt.type. +#! +#! +#! engine +#! +#! this option can be used to specify the GraphViz engine to use +#! to render a dot document. The valid choices are "dot", +#! "neato", "circo", "twopi", "fdp", +#! "sfdp", and "patchwork". Please refer to the +#! GraphViz documentation for details on these engines. +#! The default value for this option is "dot", and it +#! must be specified in opt.engine. +#! +#! +#! filetype +#! +#! this should be a string representing the type of file which +#! Splash should produce. For &LaTeX; files, this option is +#! ignored and the default value "pdf" is used. +#! +#! +#! +#! This function was originally written by Attila Egri-Nagy and Manuel Delgado, +#! the present version incorporates some minor changes.

+#! @BeginLogSession +#! gap> TODO +#! gap> Splash(); +#! @EndLogSession DeclareGlobalFunction("Splash"); diff --git a/gap/splash.gi b/gap/splash.gi index 9d3c67e..41a3da2 100644 --- a/gap/splash.gi +++ b/gap/splash.gi @@ -32,7 +32,7 @@ function(arg...) file[i] := '_'; fi; od; - arg[1] := String(arg[1]); + arg[1] := AsString(arg[1]); else file := "vizpicture"; fi; @@ -42,7 +42,7 @@ function(arg...) if IsBound(arg[2]) and IsRecord(arg[2]) then opt := arg[2]; elif IsBound(arg[2]) then - ErrorNoReturn("the 2nd argument must be a record,"); + ErrorNoReturn("the 2nd argument must be a record"); fi; path := UserHomeExpand("~/"); diff --git a/makedoc.g b/makedoc.g index a04d383..b18c2fe 100644 --- a/makedoc.g +++ b/makedoc.g @@ -10,9 +10,39 @@ ## This file is a script which compiles the package manual. +UrlEntity := function(name, url) + return StringFormatted("""{2} + {1}""", name, url); +end; + +PackageEntity := function(name) + if TestPackageAvailability(name) <> fail then + return UrlEntity(PackageInfo(name)[1].PackageName, + PackageInfo(name)[1].PackageWWWHome); + fi; + return StringFormatted("{1}", name); +end; + if fail = LoadPackage("AutoDoc", "2022.10.20") then Error("AutoDoc version 2022.10.20 or newer is required."); fi; -AutoDoc(rec(scaffold := true, autodoc := true)); +XMLEntities := rec(); + +XMLEntities.GAPGraphviz := PackageEntity("Graphviz"); +XMLEntities.PyGraphviz := UrlEntity("graphviz", + "https://pypi.org/project/graphviz/"); +XMLEntities.DOT := UrlEntity("DOT", + "https://www.graphviz.org/doc/info/lang.html"); +XMLEntities.Graphviz := UrlEntity("Graphviz", "https://www.graphviz.org"); + +AutoDoc("graphviz", +rec(scaffold := rec(entities := XMLEntities), + autodoc := true, + extract_examples := true, +)); + +Unbind(PackageEntity); +Unbind(UrlEntity); +Unbind(XMLEntities); QUIT; diff --git a/tst/graphviz01.tst b/tst/graphviz01.tst new file mode 100644 index 0000000..8fa4ba4 --- /dev/null +++ b/tst/graphviz01.tst @@ -0,0 +1,49 @@ +# graphviz, chapter 2 +# +# DO NOT EDIT THIS FILE - EDIT EXAMPLES IN THE SOURCE INSTEAD! +# +# This file has been generated by AutoDoc. It contains examples extracted from +# the package documentation. Each example is preceded by a comment which gives +# the name of a GAPDoc XML file and a line range from which the example were +# taken. Note that the XML file in turn may have been generated by AutoDoc +# from some other input. +# +gap> START_TEST("graphviz01.tst"); + +# doc/_Chapter_Full_Reference.xml:78-89 +gap> gv := GraphvizGraph("GraphyMcGraphFace"); + +gap> GraphvizName(gv); +"GraphyMcGraphFace" +gap> GraphvizGraph(666); + +gap> gv := GraphvizGraph(); + +gap> GraphvizName(gv); +"" + +# doc/_Chapter_Full_Reference.xml:131-142 +gap> gv := GraphvizDigraph("GraphyMcGraphFace"); + +gap> GraphvizName(gv); +"GraphyMcGraphFace" +gap> GraphvizDigraph(666); + +gap> gv := GraphvizDigraph(); + +gap> GraphvizName(gv); +"" + +# doc/_Chapter_Full_Reference.xml:167-177 +gap> dot := GraphvizDigraph("The Round Table");; +gap> GraphvizName(dot); +"The Round Table" +gap> n := GraphvizSetAttr(GraphvizAddNode(dot, "A"), "label", "King Arthur");; +gap> GraphvizName(n); +"A" +gap> e := GraphvizAddEdge(dot, "A", "B");; +gap> GraphvizName(e); +"(A, B)" + +# +gap> STOP_TEST("graphviz01.tst", 1); diff --git a/tst/testall.g b/tst/testall.g index a5ad9d1..e737bce 100644 --- a/tst/testall.g +++ b/tst/testall.g @@ -4,6 +4,6 @@ LoadPackage("graphviz"); TestDirectory(DirectoriesPackageLibrary("graphviz", "tst"), - rec(exitGAP := true)); + rec(exitGAP := true, compareFunction := "uptowhitespace")); FORCE_QUIT_GAP(1); # if we ever get here, there was an error