diff --git a/gap/dot.gi b/gap/dot.gi index 2dfa745..f53b0ba 100644 --- a/gap/dot.gi +++ b/gap/dot.gi @@ -50,6 +50,8 @@ function(arg...) [msg]); end); +DeclareOperation("GV_GraphSearchChildren", [IsGVGraph, IsFunction]); +DeclareOperation("GV_GraphTreeSearch", [IsGVGraph, IsFunction]); DeclareOperation("GV_GetCounter", [IsGVGraph]); DeclareOperation("GV_IncCounter", [IsGVGraph]); DeclareCategory("IsGV_Map", IsObject); @@ -549,9 +551,14 @@ function(g) local result, edges, nodes, kind; result := ""; - edges := Length(GraphvizEdges(g)); - nodes := - Length(GV_MapNames(GraphvizNodes(g))); + edges := 0; + nodes := 0; + + GV_GraphSearchChildren(g, function(s) + nodes := nodes + Length(GV_MapNames(GraphvizNodes(s))); + edges := edges + Length(GraphvizEdges(s)); + return false; + end); if IsGVDigraph(g) then kind := "digraph"; @@ -724,7 +731,36 @@ InstallMethod(GV_GetParent, "for a graphviz graph", [IsGVGraph], graph -> graph!.Parent); -DeclareOperation("GV_GraphTreeSearch", [IsGVGraph, IsFunction]); +# tree search only on the children of the graph +InstallMethod(GV_GraphSearchChildren, +"for a graphviz graph and a predicate", +[IsGVGraph, IsFunction], +function(graph, pred) + local _, curr, queue, count, subs, key; + + queue := [graph]; + while Length(queue) > 0 do + count := Length(queue); + + for _ in [1 .. count] do + # TODO: make sure this is using a linked list rather than an array list + curr := Remove(queue, 1); + + # Check this graph (visit) + if pred(curr) then + return curr; + fi; + + # Add children + subs := GraphvizSubgraphs(curr); + for key in GV_MapNames(subs) do + Add(queue, subs[key]); + od; + od; + od; + + return fail; +end); InstallMethod(GV_GraphTreeSearch, "for a graphviz graph and a predicate", @@ -1189,9 +1225,38 @@ end); InstallMethod(GraphvizRemoveAttr, "for a graphviz graph and an object", [IsGVGraph, IsObject], function(obj, attr) - local attrs; - attrs := GraphvizAttrs(obj); - obj!.Attrs := Filtered(attrs, item -> item[1] <> String(attr)); + local attrs, i, k, str, match; + + attrs := GraphvizAttrs(obj); + attr := String(attr); + + # remove by value + if '=' in attr then + obj!.Attrs := Filtered(attrs, item -> item <> attr); + else + + for k in [1 .. Length(attrs)] do + str := attrs[k]; + + # check if the string is of the form '`attr`=...' + match := true; + for i in [1 .. Length(attr)] do + if i > Length(str) or attr[i] <> str[i] then + match := false; + break; + fi; + od; + match := match and i + 1 <= Length(str) and str[i + 1] = '='; + + # if so remove it + if match then + RemoveElmList(attrs, k); + else + k := k + 1; + fi; + od; + + fi; return obj; end); diff --git a/tst/examples/er.tst b/tst/examples/er.tst index a1dcbf3..8f0d58d 100644 --- a/tst/examples/er.tst +++ b/tst/examples/er.tst @@ -99,9 +99,9 @@ gap> GraphvizSetAttrs(GraphvizAddEdge(e, "course", "S-C"), # gap> GraphvizSetAttr(e, "label=\"Entity Relation Diagram\ndrawn by NEATO\""); - + gap> GraphvizSetAttr(e, "fontsize=\"20\""); - + # gap> AsString(e); diff --git a/tst/examples/traffic_lights.tst b/tst/examples/traffic_lights.tst index 295ac0f..5fc2359 100644 --- a/tst/examples/traffic_lights.tst +++ b/tst/examples/traffic_lights.tst @@ -69,14 +69,14 @@ gap> for pair in [[2, 1], [1, 2]] do # gap> GraphvizSetAttr(t, "overlap=\"false\""); - + gap> GraphvizSetAttr(t, > """label="PetriNet Model TrafficLights > Extracted from ConceptBase and laid out by Graphviz" > """); - + gap> GraphvizSetAttr(t, "fontsize=12"); - + # gap> AsString(t); diff --git a/tst/graph.tst b/tst/graph.tst index a0aea1d..ed5428c 100644 --- a/tst/graph.tst +++ b/tst/graph.tst @@ -241,5 +241,41 @@ gap> GraphvizSetColor(g, "red");; gap> GraphvizAttrs(g); [ "color=red" ] +# testing removing attributes from graphs by value +gap> g := GraphvizGraph();; +gap> GraphvizSetAttr(g, "label", "test");; +gap> GraphvizAttrs(g); +[ "label=test" ] +gap> GraphvizSetAttr(g, 1, 2); +#I unknown attribute "1", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr + +gap> GraphvizAttrs(g); +[ "label=test", "1=2" ] +gap> GraphvizRemoveAttr(g, "1=2"); + +gap> GraphvizAttrs(g); +[ "label=test" ] + +# testing removing attributes from graphs by key +gap> g := GraphvizGraph();; +gap> GraphvizSetAttr(g, "label", "test");; +gap> GraphvizAttrs(g); +[ "label=test" ] +gap> GraphvizSetAttr(g, 1, 2);; +#I unknown attribute "1", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr +gap> GraphvizAttrs(g); +[ "label=test", "1=2" ] +gap> GraphvizRemoveAttr(g, 1);; +gap> GraphvizAttrs(g); +[ "label=test" ] +gap> GraphvizRemoveAttr(g, "label");; +gap> GraphvizAttrs(g); +[ ] +gap> GraphvizSetAttr(g, 1, 2);; +#I unknown attribute "1", the graphviz object may no longer be valid, it can be removed using GraphvizRemoveAttr +gap> GraphvizRemoveAttr(g, "label");; +gap> GraphvizAttrs(g); +[ "1=2" ] + # gap> STOP_TEST("graphviz package: graph.tst", 0); diff --git a/tst/node.tst b/tst/node.tst index 6388f06..9396a8d 100644 --- a/tst/node.tst +++ b/tst/node.tst @@ -112,5 +112,64 @@ gap> s := GraphvizGraph();; gap> GraphvizAddNode(s, n); Error, Cannot add node objects directly to graphs. Please use the node's name. +# Test updating an attribute +gap> g := GraphvizGraph();; +gap> n := GraphvizAddNode(g, "n");; +gap> GraphvizSetAttr(n, "label", "a");; +gap> AsString(g); +"//dot\ngraph {\n\tn [label=a]\n}\n" +gap> GraphvizSetAttr(n, "label", "b");; +gap> AsString(g); +"//dot\ngraph {\n\tn [label=b]\n}\n" +gap> GraphvizSetAttr(n, "color", "red");; +gap> AsString(g); +"//dot\ngraph {\n\tn [color=red, label=b]\n}\n" +gap> GraphvizSetAttr(n, "color", "blue");; +gap> AsString(g); +"//dot\ngraph {\n\tn [color=blue, label=b]\n}\n" + +# test changing labels functions properly +gap> g := GraphvizGraph("xxx");; +gap> GraphvizAddNode(g, 1);; +gap> GraphvizAddNode(g, 2);; +gap> GraphvizAddNode(g, 3);; +gap> GraphvizSetNodeLabels(g, ["i", "ii", "iii"]); + +gap> AsString(g); +"//dot\ngraph xxx {\n\t1 [label=i]\n\t2 [label=ii]\n\t3 [label=iii]\n}\n" +gap> GraphvizSetNodeLabels(g, ["a", "b", "c"]); + +gap> AsString(g); +"//dot\ngraph xxx {\n\t1 [label=a]\n\t2 [label=b]\n\t3 [label=c]\n}\n" + +# test changing labels and colors functions properly +gap> g := GraphvizGraph("xxx"); + +gap> GraphvizAddNode(g, 1); + +gap> GraphvizAddNode(g, 2); + +gap> GraphvizAddNode(g, 3); + +gap> GraphvizSetNodeColors(g, ["i", "ii", "iii"]); +Error, invalid color "i" (list (string)), valid colors are RGB values or names\ + from the GraphViz 2.44.1 X11 Color Scheme http://graphviz.org/doc/info/colors\ +.html +gap> GraphvizSetNodeColors(g, ["red", "green", "blue"]); + +gap> AsString(g); +"//dot\ngraph xxx {\n\t1 [color=red, style=filled]\n\t2 [color=green, style=fi\ +lled]\n\t3 [color=blue, style=filled]\n}\n" +gap> GraphvizSetNodeColors(g, ["red", "#00FF00", "blue"]); + +gap> AsString(g); +"//dot\ngraph xxx {\n\t1 [color=red, style=filled]\n\t2 [color=\"#00FF00\", st\ +yle=filled]\n\t3 [color=blue, style=filled]\n}\n" +gap> GraphvizSetNodeColors(g, ["#FF0000", "#00FF00", "#0000FF"]); + +gap> AsString(g); +"//dot\ngraph xxx {\n\t1 [color=\"#FF0000\", style=filled]\n\t2 [color=\"#00FF\ +00\", style=filled]\n\t3 [color=\"#0000FF\", style=filled]\n}\n" + # gap> STOP_TEST("graphviz package: node.tst", 0); diff --git a/tst/subgraph.tst b/tst/subgraph.tst index 94d0bc3..5f16259 100644 --- a/tst/subgraph.tst +++ b/tst/subgraph.tst @@ -380,5 +380,41 @@ gap> o := GraphvizFindGraph(g, 1); gap> IsIdenticalObj(o, s); true +# Test edge and node numbers when subgraphs are present +gap> g := GraphvizGraph("main");; +gap> a := GraphvizAddSubgraph(g, 1);; +gap> b := GraphvizAddSubgraph(g, 2);; +gap> c := GraphvizAddSubgraph(a, 3);; # nested subgraph +gap> GraphvizAddNode(g, 1);; +gap> GraphvizAddNode(a, 2);; +gap> GraphvizAddNode(a, 3);; +gap> GraphvizAddNode(b, 4);; +gap> GraphvizAddNode(b, 5);; +gap> GraphvizAddNode(b, 6);; +gap> GraphvizAddNode(c, 7);; +gap> GraphvizAddNode(c, 8);; +gap> GraphvizAddNode(c, 9);; +gap> GraphvizAddNode(c, 10);; +gap> g; + +gap> GraphvizAddEdge(g, 1, 2);; +gap> GraphvizAddEdge(a, 2, 3);; +gap> GraphvizAddEdge(a, 3, 4);; +gap> GraphvizAddEdge(b, 4, 5);; +gap> GraphvizAddEdge(b, 5, 6);; +gap> GraphvizAddEdge(b, 6, 7);; +gap> GraphvizAddEdge(c, 7, 8);; +gap> GraphvizAddEdge(c, 8, 9);; +gap> GraphvizAddEdge(c, 9, 10);; +gap> GraphvizAddEdge(c, 10, 1);; +gap> g; + +gap> a; + +gap> b; + +gap> c; + + # gap> STOP_TEST("graphviz package: subgraph.tst", 0);