gographviz icon indicating copy to clipboard operation
gographviz copied to clipboard

How to define default attributes for node, edge, or graph

Open DreamerKMP opened this issue 4 years ago • 3 comments

Hi, all.

I am trying to use the node, edge, and graph statement from the Lexical and Semantic Notes. However, since this library always sorts and prints nodes, the order is changed. How can I always define it first?

Output:

digraph G {
	rank=LR;
	Hello->World[ headlabel="head", taillabel="tail" ];
	Hello->World:f0;
	Hello->World:f1;
	Hello->World:f2;
	Hello [ label="Hi\nStart", shape=Mrecord ];
	World [ label="<f0>one|<f1>two|<f2>three", shape=Mrecord ];
	node [ shape=Mrecord ];
}

You can see that the node line is defined last.

Expected:

digraph G {
	node [ shape=Mrecord ];  <-------------- This line
	rank=LR;
	Hello->World[ headlabel="head", taillabel="tail" ];
	Hello->World:f0;
	Hello->World:f1;
	Hello->World:f2;
	Hello [ label="Hi\nStart", shape=Mrecord ];
	World [ label="<f0>one|<f1>two|<f2>three", shape=Mrecord ];
}

Only when defined like this, the output I want is completed.

Looking at the source code, there seem to be no solutions. For now, is the only way to add properties to each node?

...
go 1.14

require github.com/awalterschulze/gographviz v2.0.1+incompatible
...

DreamerKMP avatar Oct 13 '20 09:10 DreamerKMP

I tried to create this test in analysewrite_test.go

func TestOrder(t *testing.T) {
	input := `digraph G {
		node [ shape=Mrecord ];
		rank=LR;
		Hello->World[ headlabel="head", taillabel="tail" ];
		Hello->World:f0;
		Hello->World:f1;
		Hello->World:f2;
		Hello [ label="Hi\nStart" ];
		World [ label="<f0>one|<f1>two|<f2>three" ];
	}`
	g, err := parser.ParseString(input)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("Parsed: %v\n", g)
}

It prints out node is the right position, but if I move the node to the bottom it stays at the bottom too.

If you add

        ag := NewGraph()
	if err := Analyse(g, ag); err != nil {
		t.Fatal(err)
	}
	t.Logf("Analysed: %v\n", ag)
	agstr := ag.String()
	t.Logf("Written: %v\n", agstr)

Then the node's properties are applied to all the appropriate nodes

        Hello [ label="Hi\nStart", shape=Mrecord ];
	World [ label="<f0>one|<f1>two|<f2>three", shape=Mrecord ];

So I don't think I understand the problem. Is it possible rephrase it as a test?

awalterschulze avatar Oct 22 '20 17:10 awalterschulze

Here is my code

package main

import (
	"fmt"

	"github.com/awalterschulze/gographviz"
)

func main() {
	g := gographviz.NewGraph()
	g.SetName("G")
	g.SetDir(true)
	g.AddAttr("G", "rank", "LR")
	// This node is ignored.
	g.AddNode("G", "node", map[string]string{
		"shape": "record",
	})
	g.AddNode("G", "Hello", map[string]string{
		"label": fmt.Sprintf("%q", "Hi\nStart"),
		"shape": "Mrecord",
	})
	g.AddNode("G", "node", map[string]string{
		"shape": "Mrecord",
	})
	g.AddNode("G", "World", map[string]string{
		"label": fmt.Sprintf("%q", "<f0>one|<f1>two|<f2>three"),
		"shape": "Mrecord",
	})
	g.AddPortEdge("Hello", "", "World", "", true, map[string]string{
		"headlabel": fmt.Sprintf("%q", "head"),
		"taillabel": fmt.Sprintf("%q", "tail"),
	})
	g.AddPortEdge("Hello", "", "World", "f0", true, nil)
	g.AddPortEdge("Hello", "", "World", "f1", true, nil)
	g.AddPortEdge("Hello", "", "World", "f2", true, nil)

	fmt.Println(g.String())
}

Result:

digraph G {
	rank=LR;
	Hello->World[ headlabel="head", taillabel="tail" ];
	Hello->World:f0;
	Hello->World:f1;
	Hello->World:f2;
	Hello [ label="Hi\nStart", shape=Mrecord ];
	World [ label="<f0>one|<f1>two|<f2>three", shape=Mrecord ];
	node [ shape=Mrecord ];

}

I am using this library in my network management program. Therefore, several default nodes can be added to the graph in the program logic.

DreamerKMP avatar Oct 23 '20 00:10 DreamerKMP

In an effort to keep the API smaller, the default node, edge and graph attrs are hidden away and get applied to the nodes, edges and graphs for you, during analyses of the parsed graph, see here: https://github.com/awalterschulze/gographviz/blob/master/analyse.go#L118

So this call will treat "node" as a normal node, named node, instead of a keyword.

g.AddNode("G", "node", map[string]string{
		"shape": "record",
	})

To keep track of default node attrs, I would save them in a map as they come in and apply them to each each node.

Another option is to create your own graph API, that can write to straight to the ast package.

I am sorry that I can't be of more help here.

awalterschulze avatar Oct 24 '20 15:10 awalterschulze