render svg treemap
This commit is contained in:
parent
9ce19debd8
commit
b8c137efaf
|
@ -26,6 +26,11 @@
|
|||
|
||||
{{ block "update" .Update }}<div id="updatemsg" class="card {{ .Type }}" hx-swap-oob="morph">{{ with .Content }}{{ . }}{{ end }}</div>{{ end }}
|
||||
|
||||
{{ block "svg" .SVG }}
|
||||
<div id="treemap" hx-swap-oob="morph">
|
||||
{{ . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ block "used" .Api }}
|
||||
<div id="state" hx-swap-oob="morph">
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
94
main.go
94
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, "<svg viewBox=\"-1 -1 %d %d\">\n", shape.w+2, shape.h+2)
|
||||
fmt.Fprintf(&b, " <defs>")
|
||||
fmt.Fprintf(&b, " <filter id=\"shadow\">")
|
||||
fmt.Fprintf(&b, " <feGaussianBlur stdDeviation=\"0.1\" />")
|
||||
fmt.Fprintf(&b, " </filter>")
|
||||
fmt.Fprintf(&b, `</defs>`)
|
||||
|
||||
for _, p := range ps {
|
||||
place := placeInto(p, into)
|
||||
fmt.Fprintf(&b, " <g style=\"fill:gray; filter:url(#shadow);\" transform=\"translate(0.1 0.1)\">\n")
|
||||
fmt.Fprintf(&b, " <rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" rx=\"0.2\" />\n", place.x, place.y, place.w, place.h)
|
||||
fmt.Fprintf(&b, " </g>\n")
|
||||
}
|
||||
for _, p := range ps {
|
||||
place := placeInto(p, into)
|
||||
fmt.Fprintf(&b, " <g style=\"fill:var(--cfg); stroke:white; stroke-width:0.1;\">\n")
|
||||
fmt.Fprintf(&b, " <title>%s</title>\n", p)
|
||||
fmt.Fprintf(&b, " <rect hx-get=\"/api/dealloc/%s\" hx-swap=\"none\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" rx=\"0.2\"/>\n", p, place.x, place.y, place.w, place.h)
|
||||
fmt.Fprintf(&b, " </g>\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(&b, `</svg>`)
|
||||
return template.HTML(b.String())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue