Skip to content

Commit c54e4a3

Browse files
committed
graph pkg update
1 parent 7aafd84 commit c54e4a3

File tree

3 files changed

+127
-30
lines changed

3 files changed

+127
-30
lines changed

graph/graph.go

+39-17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77

88
// NodeConstrain is a constraint for a node in a graph
99
type NodeConstrain interface {
10+
// Name of the node, should be unique in the graph
11+
GetName() string
12+
// DotSpec returns the dot spec for this node
1013
DotSpec() *DotNodeSpec
1114
}
1215

@@ -20,21 +23,23 @@ type Edge[NT NodeConstrain] struct {
2023

2124
// DotNodeSpec is the specification for a node in a DOT graph
2225
type DotNodeSpec struct {
23-
ID string
24-
Name string
25-
Tooltip string
26-
Shape string
27-
Style string
28-
FillColor string
26+
// id of the node
27+
Name string
28+
// display text of the node
29+
DisplayName string
30+
Tooltip string
31+
Shape string
32+
Style string
33+
FillColor string
2934
}
3035

3136
// DotEdgeSpec is the specification for an edge in DOT graph
3237
type DotEdgeSpec struct {
33-
FromNodeID string
34-
ToNodeID string
35-
Tooltip string
36-
Style string
37-
Color string
38+
FromNodeName string
39+
ToNodeName string
40+
Tooltip string
41+
Style string
42+
Color string
3843
}
3944

4045
// Graph hold the nodes and edges of a graph
@@ -55,7 +60,7 @@ func NewGraph[NT NodeConstrain](edgeSpecFunc EdgeSpecFunc[NT]) *Graph[NT] {
5560

5661
// AddNode adds a node to the graph
5762
func (g *Graph[NT]) AddNode(n NT) error {
58-
nodeKey := n.DotSpec().ID
63+
nodeKey := n.GetName()
5964
if _, ok := g.nodes[nodeKey]; ok {
6065
return NewGraphError(ErrDuplicateNode, fmt.Sprintf("node with key %s already exists in this graph", nodeKey))
6166
}
@@ -65,8 +70,8 @@ func (g *Graph[NT]) AddNode(n NT) error {
6570
}
6671

6772
func (g *Graph[NT]) Connect(from, to NT) error {
68-
fromNodeKey := from.DotSpec().ID
69-
toNodeKey := to.DotSpec().ID
73+
fromNodeKey := from.GetName()
74+
toNodeKey := to.GetName()
7075
var ok bool
7176
if from, ok = g.nodes[fromNodeKey]; !ok {
7277
return NewGraphError(ErrConnectNotExistingNode, fmt.Sprintf("cannot connect node %s, it's not added in this graph yet", fromNodeKey))
@@ -102,7 +107,24 @@ func (g *Graph[NT]) ToDotGraph() (string, error) {
102107
return buf.String(), nil
103108
}
104109

105-
type templateRef struct {
106-
Nodes []*DotNodeSpec
107-
Edges []*DotEdgeSpec
110+
func (g *Graph[NT]) TopologicalSort() []NT {
111+
visited := make(map[string]bool)
112+
stack := make([]NT, 0)
113+
114+
for _, node := range g.nodes {
115+
if !visited[node.GetName()] {
116+
g.topologicalSortInternal(node, &visited, &stack)
117+
}
118+
}
119+
return stack
120+
}
121+
122+
func (g *Graph[NT]) topologicalSortInternal(node NT, visited *map[string]bool, stack *[]NT) {
123+
(*visited)[node.GetName()] = true
124+
for _, edge := range g.nodeEdges[node.GetName()] {
125+
if !(*visited)[edge.To.GetName()] {
126+
g.topologicalSortInternal(edge.To, visited, stack)
127+
}
128+
}
129+
*stack = append([]NT{node}, *stack...)
108130
}

graph/graph_test.go

+81-11
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ func TestSimpleGraph(t *testing.T) {
3131
}
3232
t.Log(graphStr)
3333

34+
sortedNodes := g.TopologicalSort()
35+
assert.Equal(t, 4, len(sortedNodes))
36+
assert.Equal(t, root, sortedNodes[0])
37+
assert.Equal(t, summary, sortedNodes[3])
38+
3439
err = g.AddNode(calc1)
3540
assert.Error(t, err)
3641
assert.True(t, errors.Is(err, graph.ErrDuplicateNode))
@@ -41,27 +46,92 @@ func TestSimpleGraph(t *testing.T) {
4146
assert.True(t, errors.Is(err, graph.ErrConnectNotExistingNode))
4247
}
4348

49+
func TestDemoGraph(t *testing.T) {
50+
g := graph.NewGraph(edgeSpecFromConnection)
51+
root := &testNode{Name: "root"}
52+
g.AddNode(root)
53+
54+
paramServerName := &testNode{Name: "param_serverName"}
55+
g.AddNode(paramServerName)
56+
g.Connect(root, paramServerName)
57+
connect := &testNode{Name: "func_getConnection"}
58+
g.AddNode(connect)
59+
g.Connect(paramServerName, connect)
60+
checkAuth := &testNode{Name: "func_checkAuth"}
61+
g.AddNode(checkAuth)
62+
63+
paramTable1 := &testNode{Name: "param_table1"}
64+
g.AddNode(paramTable1)
65+
g.Connect(root, paramTable1)
66+
tableClient1 := &testNode{Name: "func_getTableClient1"}
67+
g.AddNode(tableClient1)
68+
g.Connect(connect, tableClient1)
69+
g.Connect(paramTable1, tableClient1)
70+
paramQuery1 := &testNode{Name: "param_query1"}
71+
g.AddNode(paramQuery1)
72+
g.Connect(root, paramQuery1)
73+
queryTable1 := &testNode{Name: "func_queryTable1"}
74+
g.AddNode(queryTable1)
75+
g.Connect(paramQuery1, queryTable1)
76+
g.Connect(tableClient1, queryTable1)
77+
g.Connect(checkAuth, queryTable1)
78+
79+
paramTable2 := &testNode{Name: "param_table2"}
80+
g.AddNode(paramTable2)
81+
g.Connect(root, paramTable2)
82+
tableClient2 := &testNode{Name: "func_getTableClient2"}
83+
g.AddNode(tableClient2)
84+
g.Connect(connect, tableClient2)
85+
g.Connect(paramTable2, tableClient2)
86+
paramQuery2 := &testNode{Name: "param_query2"}
87+
g.AddNode(paramQuery2)
88+
g.Connect(root, paramQuery2)
89+
queryTable2 := &testNode{Name: "func_queryTable2"}
90+
g.AddNode(queryTable2)
91+
g.Connect(paramQuery2, queryTable2)
92+
g.Connect(tableClient2, queryTable2)
93+
g.Connect(checkAuth, queryTable2)
94+
95+
summary := &testNode{Name: "func_summarize"}
96+
g.AddNode(summary)
97+
g.Connect(queryTable1, summary)
98+
g.Connect(queryTable2, summary)
99+
100+
email := &testNode{Name: "func_email"}
101+
g.AddNode(email)
102+
g.Connect(summary, email)
103+
104+
sortedNodes := g.TopologicalSort()
105+
for _, n := range sortedNodes {
106+
fmt.Println(n.GetName())
107+
}
108+
}
109+
44110
type testNode struct {
45111
Name string
46112
}
47113

114+
func (tn *testNode) GetName() string {
115+
return tn.Name
116+
}
117+
48118
func (tn *testNode) DotSpec() *graph.DotNodeSpec {
49119
return &graph.DotNodeSpec{
50-
ID: tn.Name,
51-
Name: tn.Name,
52-
Tooltip: tn.Name,
53-
Shape: "box",
54-
Style: "filled",
55-
FillColor: "green",
120+
Name: tn.Name,
121+
DisplayName: tn.Name,
122+
Tooltip: tn.Name,
123+
Shape: "box",
124+
Style: "filled",
125+
FillColor: "green",
56126
}
57127
}
58128

59129
func edgeSpecFromConnection(from, to *testNode) *graph.DotEdgeSpec {
60130
return &graph.DotEdgeSpec{
61-
FromNodeID: from.DotSpec().ID,
62-
ToNodeID: to.DotSpec().ID,
63-
Tooltip: fmt.Sprintf("%s -> %s", from.DotSpec().Name, to.DotSpec().Name),
64-
Style: "solid",
65-
Color: "black",
131+
FromNodeName: from.GetName(),
132+
ToNodeName: to.GetName(),
133+
Tooltip: fmt.Sprintf("%s -> %s", from.DotSpec().Name, to.DotSpec().Name),
134+
Style: "solid",
135+
Color: "black",
66136
}
67137
}

graph/template.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ import (
99

1010
var digraphTemplate = template.Must(template.New("digraph").Parse(digraphTemplateText))
1111

12+
type templateRef struct {
13+
Nodes []*DotNodeSpec
14+
Edges []*DotEdgeSpec
15+
}
16+
1217
const digraphTemplateText = `digraph {
1318
newrank = "true"
14-
{{ range $node := $.Nodes}} "{{$node.ID}}" [label="{{$node.Name}}" shape={{$node.Shape}} style={{$node.Style}} tooltip="{{$node.Tooltip}}" fillcolor={{$node.FillColor}}]
19+
{{ range $node := $.Nodes}} "{{$node.Name}}" [label="{{$node.DisplayName}}" shape={{$node.Shape}} style={{$node.Style}} tooltip="{{$node.Tooltip}}" fillcolor={{$node.FillColor}}]
1520
{{ end }}
16-
{{ range $edge := $.Edges}} "{{$edge.FromNodeID}}" -> "{{$edge.ToNodeID}}" [style={{$edge.Style}} tooltip="{{$edge.Tooltip}}" color={{$edge.Color}}]
21+
{{ range $edge := $.Edges}} "{{$edge.FromNodeName}}" -> "{{$edge.ToNodeName}}" [style={{$edge.Style}} tooltip="{{$edge.Tooltip}}" color={{$edge.Color}}]
1722
{{ end }}
1823
}`

0 commit comments

Comments
 (0)