-
Notifications
You must be signed in to change notification settings - Fork 19
/
graph.go
116 lines (104 loc) · 2.89 KB
/
graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package visualization
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
type graphNode interface {
getID() string
getLabel() string
getGroup() string
}
type graphEdge interface {
getSrc() graphNode
getDst() graphNode
getLabel() string
}
// Graph is a data structure for visualizing Aptomi objects in a form of a graph with nodes and edges.
// It gets created by GraphBuilder. Once created, use GetDataJSON() to get the result and feed into vis.js.
type Graph struct {
hasObject map[string]graphEntry
nodes graphEntryList
edges graphEntryList
}
// Creates a new graph
func newGraph() *Graph {
return &Graph{
hasObject: make(map[string]graphEntry),
nodes: graphEntryList{},
edges: graphEntryList{},
}
}
func idEscape(id string) string {
return strings.NewReplacer("#", "_", ":", "_").Replace(id)
}
// GetData returns a struct, which can be fed directly into vis.js as network map
func (g *Graph) GetData() interface{} {
// Sort nodes and edges, so we can get a stable response from API that doesn't change over reloads
// This will ensure that UI will show the same layout over refreshes
sort.Sort(g.nodes)
sort.Sort(g.edges)
// Wrap it into a graph structure for visjs
return graphEntry{
"nodes": g.nodes,
"edges": g.edges,
}
}
// GetDataJSON returns a JSON-formatted byte array, which can be fed directly into vis.js as network map
func (g *Graph) GetDataJSON() []byte {
result, err := json.Marshal(g.GetData())
if err != nil {
panic(fmt.Sprintf("Failed to marshal graph to JSON: %s", err))
}
return result
}
func (g *Graph) addNode(n graphNode, level int) {
id := idEscape(fmt.Sprintf("node-%s", n.getID()))
if _, ok := g.hasObject[id]; !ok {
nodeEntry := graphEntry{
"id": id,
"g_type": "node",
"label": n.getLabel(),
"group": n.getGroup(),
}
if level >= 0 {
nodeEntry["level"] = level
}
g.addEntry(nodeEntry)
}
}
func (g *Graph) addEdge(e graphEdge) {
idFrom := idEscape(fmt.Sprintf("node-%s", e.getSrc().getID()))
idTo := idEscape(fmt.Sprintf("node-%s", e.getDst().getID()))
id := fmt.Sprintf("edge-%s-%s", idFrom, idTo)
if existing, ok := g.hasObject[id]; !ok {
edgeEntry := graphEntry{
"id": id,
"g_type": "edge",
"from": idFrom,
"to": idTo,
"labelMap": map[string]bool{e.getLabel(): true},
"label": e.getLabel(),
}
g.addEntry(edgeEntry)
} else {
existing["labelMap"].(map[string]bool)[e.getLabel()] = true
labels := []string{}
for label := range existing["labelMap"].(map[string]bool) {
labels = append(labels, label)
}
sort.Strings(labels)
existing["label"] = strings.Join(labels, ",")
}
}
func (g *Graph) addEntry(e graphEntry) {
g.hasObject[e["id"].(string)] = e
if e["g_type"] == "node" {
g.nodes = append(g.nodes, e)
} else if e["g_type"] == "edge" {
g.edges = append(g.edges, e)
} else {
panic(fmt.Sprintf("Can't add entry to the graph. Unknown type: %s", e))
}
}