github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/tree_dotgraph.go (about)

     1  package iavl
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"text/template"
     8  )
     9  
    10  type graphEdge struct {
    11  	From, To string
    12  }
    13  
    14  type graphNode struct {
    15  	Hash  string
    16  	Label string
    17  	Value string
    18  	Attrs map[string]string
    19  }
    20  
    21  type graphContext struct {
    22  	Edges []*graphEdge
    23  	Nodes []*graphNode
    24  }
    25  
    26  var graphTemplate = `
    27  strict graph {
    28  	{{- range $i, $edge := $.Edges}}
    29  	"{{ $edge.From }}" -- "{{ $edge.To }}";
    30  	{{- end}}
    31  
    32  	{{range $i, $node := $.Nodes}}
    33  	"{{ $node.Hash }}" [label=<{{ $node.Label }}>,{{ range $k, $v := $node.Attrs }}{{ $k }}={{ $v }},{{end}}];
    34  	{{- end}}
    35  }
    36  `
    37  
    38  var tpl = template.Must(template.New("iavl").Parse(graphTemplate))
    39  
    40  var defaultGraphNodeAttrs = map[string]string{
    41  	"shape": "circle",
    42  }
    43  
    44  func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) {
    45  	ctx := &graphContext{}
    46  
    47  	tree.root.hashWithCount()
    48  	tree.root.traverse(tree, true, func(node *Node) bool {
    49  		graphNode := &graphNode{
    50  			Attrs: map[string]string{},
    51  			Hash:  fmt.Sprintf("%x", node.hash),
    52  		}
    53  		for k, v := range defaultGraphNodeAttrs {
    54  			graphNode.Attrs[k] = v
    55  		}
    56  		shortHash := graphNode.Hash[:7]
    57  
    58  		graphNode.Label = mkLabel(fmt.Sprintf("%s", node.key), 16, "sans-serif")
    59  		graphNode.Label += mkLabel(shortHash, 10, "monospace")
    60  		graphNode.Label += mkLabel(fmt.Sprintf("version=%d", node.version), 10, "monospace")
    61  
    62  		if node.value != nil {
    63  			graphNode.Label += mkLabel(string(node.value), 10, "sans-serif")
    64  		}
    65  
    66  		if node.height == 0 {
    67  			graphNode.Attrs["fillcolor"] = "lightgrey"
    68  			graphNode.Attrs["style"] = "filled"
    69  		}
    70  
    71  		for _, path := range paths {
    72  			for _, n := range path {
    73  				if bytes.Equal(n.Left, node.hash) || bytes.Equal(n.Right, node.hash) {
    74  					graphNode.Attrs["peripheries"] = "2"
    75  					graphNode.Attrs["style"] = "filled"
    76  					graphNode.Attrs["fillcolor"] = "lightblue"
    77  					break
    78  				}
    79  			}
    80  		}
    81  		ctx.Nodes = append(ctx.Nodes, graphNode)
    82  
    83  		if node.leftNode != nil {
    84  			ctx.Edges = append(ctx.Edges, &graphEdge{
    85  				From: graphNode.Hash,
    86  				To:   fmt.Sprintf("%x", node.leftNode.hash),
    87  			})
    88  		}
    89  		if node.rightNode != nil {
    90  			ctx.Edges = append(ctx.Edges, &graphEdge{
    91  				From: graphNode.Hash,
    92  				To:   fmt.Sprintf("%x", node.rightNode.hash),
    93  			})
    94  		}
    95  		return false
    96  	})
    97  
    98  	if err := tpl.Execute(w, ctx); err != nil {
    99  		panic(err)
   100  	}
   101  }
   102  
   103  func mkLabel(label string, pt int, face string) string {
   104  	return fmt.Sprintf("<font face='%s' point-size='%d'>%s</font><br />", face, pt, label)
   105  }