327768f4ad
Use [33]byte for graph vertex representation. Delete unneeded stuff: 1. DeepEqual for graph comparison 2. EdgePath 3. 2-thread BFS 4. Table transfer messages and neighborhood radius 5. Beacons Refactor: 1. Change ID to Vertex 2. Test use table driven approach 3. Add comments 4. Make graph internal representation private 5. Use wire.OutPoint as EdgeId 6. Decouple routing messages from routing implementation 7. Delete Async methods 8. Delete unneeded channels and priority buffer from manager 9. Delete unneeded interfaces in internal graph realisation 10. Renamed ID to Vertex
175 lines
4.9 KiB
Go
175 lines
4.9 KiB
Go
// Copyright (c) 2016 Bitfury Group Limited
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
|
|
|
|
package visualizer
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/rt/visualizer/prefix_tree"
|
|
"github.com/lightningnetwork/lnd/routing/rt/graph"
|
|
"github.com/awalterschulze/gographviz"
|
|
"encoding/hex"
|
|
)
|
|
|
|
type visualizer struct {
|
|
// Graph used for visualisation.
|
|
G *graph.Graph
|
|
// Vertexes which should be highlighted.
|
|
HighlightedNodes []graph.Vertex
|
|
// Edges which should be highlighted.
|
|
HighlightedEdges []graph.Edge
|
|
// Configuration parameters used for visualisation.
|
|
Config *VisualizerConfig
|
|
// Function applied to node to obtain its label
|
|
ApplyToNode func(graph.Vertex) string
|
|
// Function applied to edge to obtain its label
|
|
ApplyToEdge func(*graph.ChannelInfo) string
|
|
// Prefix used for creating shortcuts.
|
|
pt prefix_tree.PrefixTree
|
|
graphviz *gographviz.Graph
|
|
}
|
|
|
|
// New creates new visualiser.
|
|
func New(g *graph.Graph, highlightedNodes []graph.Vertex,
|
|
highlightedEdges []graph.Edge, config *VisualizerConfig) *visualizer {
|
|
if config == nil {
|
|
config = &DefaultVisualizerConfig
|
|
}
|
|
return &visualizer{
|
|
G: g,
|
|
HighlightedNodes: highlightedNodes,
|
|
HighlightedEdges: highlightedEdges,
|
|
Config: config,
|
|
ApplyToNode: func(v graph.Vertex) string { return hex.EncodeToString(v.ToByte()) },
|
|
ApplyToEdge: func(info *graph.ChannelInfo) string { return "nil" },
|
|
pt: prefix_tree.NewPrefixTree(),
|
|
graphviz: gographviz.NewGraph(),
|
|
}
|
|
}
|
|
|
|
// EnableShortcut enables/disables shortcuts.
|
|
// Shortcut is a small unique string used for labeling.
|
|
func (viz *visualizer) EnableShortcut(value bool) {
|
|
viz.Config.EnableShortcut = value
|
|
}
|
|
|
|
// BuildPrefixTree builds prefix tree for nodes.
|
|
// It is needed for shortcuts.
|
|
func (viz *visualizer) BuildPrefixTree() {
|
|
for _, node := range viz.G.GetVertexes() {
|
|
id := viz.ApplyToNode(node)
|
|
viz.pt.Add(id)
|
|
}
|
|
}
|
|
|
|
// Draw creates graph representation in Graphviz dot language.
|
|
func (viz *visualizer) Draw() string {
|
|
viz.base()
|
|
viz.drawNodes()
|
|
viz.drawEdges()
|
|
return viz.graphviz.String()
|
|
}
|
|
|
|
// Base makes initialization.
|
|
func (viz *visualizer) base() {
|
|
viz.graphviz.SetName(viz.Config.GlobalCfg.Name)
|
|
viz.graphviz.SetDir(viz.Config.GlobalCfg.Dir)
|
|
viz.graphviz.SetStrict(viz.Config.GlobalCfg.Strict)
|
|
// TODO(evg): use viz.Add(...) instead viz.Attrs.Add(...)
|
|
viz.graphviz.Attrs.Add("bgcolor", viz.Config.GlobalCfg.BgColor)
|
|
}
|
|
|
|
func (viz *visualizer) drawNodes() {
|
|
for _, node := range viz.G.GetVertexes() {
|
|
if viz.isHighlightedNode(node) {
|
|
viz.drawNode(node, viz.Config.HighlightedNodeCfg)
|
|
} else {
|
|
viz.drawNode(node, viz.Config.NodeCfg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (viz *visualizer) drawNode(node graph.Vertex, cfg *NodeConfig) {
|
|
id := viz.ApplyToNode(node)
|
|
if viz.Config.EnableShortcut {
|
|
// TODO(evg): processing errors
|
|
id, _ = viz.pt.Shortcut(id)
|
|
}
|
|
attrs := gographviz.Attrs{
|
|
"shape": cfg.Shape,
|
|
"style": cfg.Style,
|
|
"fontsize": cfg.FontSize,
|
|
"fontcolor": cfg.FontColor,
|
|
"color": cfg.Color,
|
|
"fillcolor": cfg.FillColor,
|
|
}
|
|
viz.graphviz.AddNode(viz.Config.GlobalCfg.Name, id, attrs)
|
|
}
|
|
|
|
func (viz *visualizer) isHighlightedNode(node graph.Vertex) bool {
|
|
for _, value := range viz.HighlightedNodes {
|
|
if node.String() == value.String() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (viz *visualizer) drawEdges() {
|
|
for _, edge := range viz.G.GetUndirectedEdges() {
|
|
if viz.isHighlightedEdge(edge) {
|
|
viz.drawEdge(edge, viz.Config.HighlightedEdgeCfg)
|
|
} else {
|
|
viz.drawEdge(edge, viz.Config.EdgeCfg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (viz *visualizer) drawEdge(edge graph.Edge, cfg *EdgeConfig) {
|
|
src := viz.ApplyToNode(edge.Src)
|
|
tgt := viz.ApplyToNode(edge.Tgt)
|
|
if viz.Config.EnableShortcut {
|
|
// TODO(evg): processing errors
|
|
src, _ = viz.pt.Shortcut(src)
|
|
tgt, _ = viz.pt.Shortcut(tgt)
|
|
}
|
|
attrs := gographviz.Attrs{
|
|
"fontsize": cfg.FontSize,
|
|
"fontcolor": cfg.FontColor,
|
|
"labeldistance": cfg.Scale,
|
|
"dir": cfg.Dir,
|
|
"style": cfg.Style,
|
|
"color": cfg.Color,
|
|
"label": viz.ApplyToEdge(edge.Info),
|
|
}
|
|
viz.graphviz.AddEdge(src, tgt, true, attrs)
|
|
}
|
|
|
|
func (viz *visualizer) isHighlightedEdge(edge graph.Edge) bool {
|
|
for _, value := range viz.G.GetEdges() {
|
|
if edge == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Run graphviz command line utility (such as neato).
|
|
// Used for creation image from textual graph representation.
|
|
func Run(utility string, TempFile, ImageFile *os.File) error {
|
|
extension := filepath.Ext(ImageFile.Name())[1:]
|
|
_, err := exec.Command(utility, "-T"+extension, "-o"+ImageFile.Name(), TempFile.Name()).Output()
|
|
return err
|
|
}
|
|
|
|
// Opens file in a command line open program.
|
|
// Used for displaying graphical files.
|
|
func Open(ImageFile *os.File) error {
|
|
_, err := exec.Command("open", ImageFile.Name()).Output()
|
|
return err
|
|
}
|