cmd/lncli: use .svg for graph rendering add weighted edge thickness

This commit is contained in:
Olaoluwa Osuntokun 2017-01-24 18:07:15 -08:00
parent 160317c9fa
commit eb52f7f695
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
@ -771,7 +772,7 @@ var DescribeGraphCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "draw", Name: "draw",
Usage: "If true, then an image of graph will be generated and displayed. The generated image is stored within the current directory with a file name of 'graph.png'", Usage: "If true, then an image of graph will be generated and displayed. The generated image is stored within the current directory with a file name of 'graph.svg'",
}, },
}, },
Action: describeGraph, Action: describeGraph,
@ -797,6 +798,36 @@ func describeGraph(ctx *cli.Context) error {
return nil return nil
} }
// normalizeFunc is a factory function which returns a function that normalizes
// the capacity of of edges within the graph. The value of the returned
// function can be used to either plot the capacities, or to use a weight in a
// rendering of the graph.
func normalizeFunc(edges []*lnrpc.ChannelEdge, scaleFactor float64) func(int64) float64 {
var (
min float64 = math.MaxInt64
max float64
)
for _, edge := range edges {
// In order to obtain saner values, we reduce the capacity of a
// channel to it's base 2 logarithm.
z := math.Log2(float64(edge.Capacity))
if z < min {
min = z
}
if z > max {
max = z
}
}
return func(x int64) float64 {
y := math.Log2(float64(x))
return float64(y-min) / float64(max-min) * scaleFactor
}
}
func drawChannelGraph(graph *lnrpc.ChannelGraph) error { func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
// First we'll create a temporary file that we'll write the compiled // First we'll create a temporary file that we'll write the compiled
// string that describes our graph in the dot format to. // string that describes our graph in the dot format to.
@ -808,7 +839,7 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
// Next, we'll create (or re-create) the file that the final graph // Next, we'll create (or re-create) the file that the final graph
// image will be written to. // image will be written to.
imageFile, err := os.Create("graph.png") imageFile, err := os.Create("graph.svg")
if err != nil { if err != nil {
return err return err
} }
@ -818,7 +849,7 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
graphName := "LightningNetwork" graphName := "LightningNetwork"
graphCanvas := gographviz.NewGraph() graphCanvas := gographviz.NewGraph()
graphCanvas.SetName(graphName) graphCanvas.SetName(graphName)
graphCanvas.SetDir(true) graphCanvas.SetDir(false)
// For each node within the graph, we'll add a new vertex to the graph. // For each node within the graph, we'll add a new vertex to the graph.
for _, node := range graph.Nodes { for _, node := range graph.Nodes {
@ -833,6 +864,8 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
graphCanvas.AddNode(graphName, nodeID, gographviz.Attrs{}) graphCanvas.AddNode(graphName, nodeID, gographviz.Attrs{})
} }
normalize := normalizeFunc(graph.Edges, 3)
// Similarly, for each edge we'll add an edge between the corresponding // Similarly, for each edge we'll add an edge between the corresponding
// nodes added to the graph above. // nodes added to the graph above.
for _, edge := range graph.Edges { for _, edge := range graph.Edges {
@ -843,6 +876,8 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
// The weight for our edge will be the total capacity of the // The weight for our edge will be the total capacity of the
// channel, in BTC. // channel, in BTC.
// TODO(roasbeef): can also factor in the edges time-lock delta
// and fee information
amt := btcutil.Amount(edge.Capacity).ToBTC() amt := btcutil.Amount(edge.Capacity).ToBTC()
edgeWeight := strconv.FormatFloat(amt, 'f', -1, 64) edgeWeight := strconv.FormatFloat(amt, 'f', -1, 64)
@ -850,21 +885,17 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
// of it's channel ID. // of it's channel ID.
edgeLabel := strconv.FormatUint(edge.ChannelId, 10)[:10] edgeLabel := strconv.FormatUint(edge.ChannelId, 10)[:10]
// We'll only draw an edge from a source to a destination if // We'll also use a normalized version of the channels'
// the source is currently advertising a route in that // capacity in satoshis in order to modulate the "thickness" of
// direction. // the line that creates the edge within the graph.
if edge.Node1Policy.TimeLockDelta != 0 { normalizedCapacity := normalize(edge.Capacity)
graphCanvas.AddEdge(src, dest, true, gographviz.Attrs{ edgeThickness := strconv.FormatFloat(normalizedCapacity, 'f', -1, 64)
"weight": edgeWeight,
"label": edgeLabel, graphCanvas.AddEdge(src, dest, false, gographviz.Attrs{
}) "penwidth": edgeThickness,
} "weight": edgeWeight,
if edge.Node2Policy.TimeLockDelta != 0 { "label": edgeLabel,
graphCanvas.AddEdge(dest, src, true, gographviz.Attrs{ })
"weight": edgeWeight,
"label": edgeLabel,
})
}
} }
// With the declarative generation of the graph complete, we now write // With the declarative generation of the graph complete, we now write
@ -880,7 +911,7 @@ func drawChannelGraph(graph *lnrpc.ChannelGraph) error {
// Once our dot file has been written to disk, we can use the dot // Once our dot file has been written to disk, we can use the dot
// command itself to generate the drawn rendering of the graph // command itself to generate the drawn rendering of the graph
// described. // described.
drawCmd := exec.Command("dot", "-T"+"png", "-o"+imageFile.Name(), drawCmd := exec.Command("dot", "-T"+"svg", "-o"+imageFile.Name(),
tempDotFile.Name()) tempDotFile.Name())
if err := drawCmd.Run(); err != nil { if err := drawCmd.Run(); err != nil {
return err return err