From b8c137efafc52049a643e7a3ce67062a81deced4 Mon Sep 17 00:00:00 2001 From: Johannes Kimmel Date: Thu, 9 Nov 2023 16:32:30 +0100 Subject: [PATCH] render svg treemap --- index.html | 5 +++ ipalloc/ipalloc.go | 13 ++++-- ipalloc/ipalloc_test.go | 2 +- main.go | 94 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 7ac59c2..af6cffa 100644 --- a/index.html +++ b/index.html @@ -26,6 +26,11 @@ {{ block "update" .Update }}
{{ with .Content }}{{ . }}{{ end }}
{{ end }} + {{ block "svg" .SVG }} +
+ {{ . }} +
+ {{ end }} {{ block "used" .Api }}
diff --git a/ipalloc/ipalloc.go b/ipalloc/ipalloc.go index d552975..45ed0f2 100644 --- a/ipalloc/ipalloc.go +++ b/ipalloc/ipalloc.go @@ -159,7 +159,7 @@ func (t *tree) Prefix() netip.Prefix { } func (t *tree) Split() { - lo, hi := splitPrefix(t.prefix) + lo, hi := SplitPrefix(t.prefix) t.lo = &tree{prefix: lo, parent: t} t.hi = &tree{prefix: hi, parent: t} @@ -214,7 +214,7 @@ func (t *tree) Alloc(bits int) *tree { } for best.prefix.Bits() < bits { - lo, hi := splitPrefix(best.prefix) + lo, hi := SplitPrefix(best.prefix) //log.Println(lo, hi) switch { case best.lo == nil: @@ -236,7 +236,7 @@ func (t *tree) Insert(p netip.Prefix) *tree { var ret *tree for t.prefix.Bits() < p.Bits() { - lo, hi := splitPrefix(t.prefix) + lo, hi := SplitPrefix(t.prefix) //log.Println(lo, hi) switch { case lo.Overlaps(p): @@ -435,6 +435,11 @@ func (db *DB) Find(p netip.Prefix) bool { p = p.Masked() return db.provisions.Find(p) != nil } +func (db *DB) Root() netip.Prefix { + db.Lock() + defer db.Unlock() + return db.provisions.prefix +} //func (db *DB) Provision(p netip.Prefix) error { // db.Lock() @@ -446,7 +451,7 @@ func (db *DB) Find(p netip.Prefix) bool { // return nil //} -func splitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) { +func SplitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) { if p.IsSingleIP() { log.Fatal("can't split single ip prefix", p) } diff --git a/ipalloc/ipalloc_test.go b/ipalloc/ipalloc_test.go index 15abb2c..a8246c0 100644 --- a/ipalloc/ipalloc_test.go +++ b/ipalloc/ipalloc_test.go @@ -15,7 +15,7 @@ func TestSplitPrefix(t *testing.T) { } for _, test := range tests { - lo, hi := splitPrefix(netip.MustParsePrefix(test.in)) + lo, hi := SplitPrefix(netip.MustParsePrefix(test.in)) if lo != netip.MustParsePrefix(test.lo) || hi != netip.MustParsePrefix(test.hi) { t.Fatal(test.in, lo, hi) } diff --git a/main.go b/main.go index 992910c..b145935 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "net/netip" "strconv" "strings" + "git.freifunk-franken.de/jkimmel/sub/ipalloc" ) @@ -50,6 +51,7 @@ func (s *server) apiPrefix(w http.ResponseWriter, r *http.Request) { type PageContent struct { Api *ipalloc.DB Update UpdateMessage + SVG template.HTML } type UpdateMessage struct { Type string @@ -57,7 +59,7 @@ type UpdateMessage struct { } func (s *server) template(w http.ResponseWriter, r *http.Request) { - err := s.tmpl.Execute(w, PageContent{Api: s.ipdb}) + err := s.tmpl.Execute(w, PageContent{Api: s.ipdb, SVG: renderSVG(s.ipdb.Used(), s.ipdb.Root())}) if err != nil { log.Println(err) } @@ -101,6 +103,7 @@ func (s *server) apiAlloc(w http.ResponseWriter, r *http.Request) { updmsg.Type = "hint" updmsg.Content = fmt.Sprintf("Added %s", alloc.Prefix()) } + s.tmpl.ExecuteTemplate(w, "svg", renderSVG(s.ipdb.Used(), s.ipdb.Root())) if err := s.tmpl.ExecuteTemplate(w, "update", updmsg); err != nil { log.Println(err) } @@ -132,6 +135,7 @@ func (s *server) apiDealloc(w http.ResponseWriter, r *http.Request) { updmsg.Type = "hint" updmsg.Content = fmt.Sprintf("Removed %s", prefix) } + s.tmpl.ExecuteTemplate(w, "svg", renderSVG(s.ipdb.Used(), s.ipdb.Root())) s.tmpl.ExecuteTemplate(w, "update", updmsg) s.tmpl.ExecuteTemplate(w, "used", s.ipdb) } @@ -155,3 +159,91 @@ func main() { s.setup() http.ListenAndServe("[::1]:8080", &s) } + +type rect struct { + y, x, h, w int +} + +func prefixShape(p netip.Prefix) rect { + bits := p.Addr().BitLen() - p.Bits() + + return rect{ + y: 0, + x: 0, + h: 1 << (bits / 2), + w: 1 << ((bits + 1) / 2), + } +} + +func placeInto(p, into netip.Prefix) rect { + return placeIntoRect(p, into, prefixShape(into)) +} +func placeIntoRect(p, into netip.Prefix, r rect) rect { + if p == into { + return r + } + if p.Bits() < into.Bits() { + panic("unreachable") + } + lo, hi := ipalloc.SplitPrefix(into) + + switch { + case lo.Overlaps(p): + var subr rect + if r.w > r.h { + subr.y = r.y + subr.x = r.x + subr.h = r.h + subr.w = r.w / 2 + } else { + subr.y = r.y + subr.x = r.x + subr.h = r.h / 2 + subr.w = r.w + } + return placeIntoRect(p, lo, subr) + case hi.Overlaps(p): + var subr rect + if r.w > r.h { + subr.y = r.y + subr.x = (r.x + (r.x + r.w)) / 2 + subr.h = r.h + subr.w = r.w / 2 + } else { + subr.y = (r.y + (r.y + r.h)) / 2 + subr.x = r.x + subr.h = r.h / 2 + subr.w = r.w + } + return placeIntoRect(p, hi, subr) + default: + panic("unreachable") + } +} +func renderSVG(ps []netip.Prefix, into netip.Prefix) template.HTML { + var b strings.Builder + shape := prefixShape(into) + fmt.Fprintf(&b, "\n", shape.w+2, shape.h+2) + fmt.Fprintf(&b, " ") + fmt.Fprintf(&b, " ") + fmt.Fprintf(&b, " ") + fmt.Fprintf(&b, " ") + fmt.Fprintf(&b, ``) + + for _, p := range ps { + place := placeInto(p, into) + fmt.Fprintf(&b, " \n") + fmt.Fprintf(&b, " \n", place.x, place.y, place.w, place.h) + fmt.Fprintf(&b, " \n") + } + for _, p := range ps { + place := placeInto(p, into) + fmt.Fprintf(&b, " \n") + fmt.Fprintf(&b, " %s\n", p) + fmt.Fprintf(&b, " \n", p, place.x, place.y, place.w, place.h) + fmt.Fprintf(&b, " \n") + } + + fmt.Fprintf(&b, ``) + return template.HTML(b.String()) +}