treemap: move render into template

This commit is contained in:
Johannes Kimmel 2023-11-09 21:36:48 +01:00
parent b8c137efaf
commit 8fb9d45289
3 changed files with 129 additions and 114 deletions

View File

@ -25,41 +25,57 @@
</div>
{{ block "update" .Update }}<div id="updatemsg" class="card {{ .Type }}" hx-swap-oob="morph">{{ with .Content }}{{ . }}{{ end }}</div>{{ end }}
{{ block "svg" .SVG }}
{{ block "treemap" .Treemap }}
<div id="treemap" hx-swap-oob="morph">
{{ . }}
{{- with .ViewBox }}
<svg viewBox="{{.X}} {{.Y}} {{.W}} {{.H}}">
{{- end }}
<defs>
<filter id="shadow">
<feGaussianBlur stdDeviation="0.1" />
</filter>
</defs>
<g style="fill:gray; filter:url(#shadow);" transform="translate(0.1 0.1)">
{{- range .Regions }}{{ with .Rect }}
<rect x="{{.X}}" y="{{.Y}}" width="{{.W}}" height="{{.H}}" rx="0.2" />
{{- end }}{{ end }}
</g>
{{- range .Regions }}
<g style="fill:var(--cfg); stroke:white; stroke-width:0.1;">
<title>{{.Prefix}}</title>\n", p
<rect hx-get="/api/dealloc/{{.Prefix}}" hx-swap="none" {{ with .Rect }}x="{{.X}}" y="{{.Y}}" width="{{.W}}" height="{{.H}}"{{end}} rx="0.2"/>
</g>
{{- end }}
</svg>
</div>
{{ end }}
{{- end }}
{{ block "used" .Api }}
<div id="state" hx-swap-oob="morph">
<h2>grouped</h2>
<div id="used-grouped" hx-swap-oob="morph">
{{- range .UsedGrouped }}
{{ $first := index . 0 }}
{{ $bits := $first.Bits }}
<div>
<h3>/{{ $bits }}</h3>
<table id="group-{{ $bits }}">
{{ range . }}
<tr hx-get="/api/dealloc/{{.}}" hx-swap="none"><td>{{.Addr}}</td><td>{{.Bits}}</td></tr>{{ end }}
</table>
</div>
{{ end }}
{{- $first := index . 0 }}
{{- $bits := $first.Bits }}
<div>
<h3>/{{ $bits }}</h3>
<table id="group-{{ $bits }}">
{{- range . }}
<tr hx-get="/api/dealloc/{{.}}" hx-swap="none"><td>{{.Addr}}</td><td>{{.Bits}}</td></tr>
{{- end }}
</table>
</div>
{{ end -}}
</div>
<h2>all</h2>
<div id="used" hx-swap-oob="morph">
<table>
{{- range .Used }}
<tr hx-get="/api/dealloc/{{.}}" hx-swap="none"><td>{{.Addr}}</td><td>{{.Bits}}</td></tr>{{ end }}
</table>
<tr hx-get="/api/dealloc/{{.}}" hx-swap="none"><td>{{.Addr}}</td><td>{{.Bits}}</td></tr>{{ end }}
</table>
</div>
</div>
{{ end }}
{{- end }}
</body>
</html>

View File

@ -441,6 +441,93 @@ func (db *DB) Root() netip.Prefix {
return db.provisions.prefix
}
type Treemap struct {
ViewBox Rect
Regions []Region
}
type Region struct {
Prefix netip.Prefix
Rect Rect
}
func (db *DB) Treemap() Treemap {
var ret Treemap
root := db.provisions.prefix
ret.ViewBox = prefixShape(root)
// leave room for shadows
ret.ViewBox.X--
ret.ViewBox.Y--
ret.ViewBox.W += 2
ret.ViewBox.H += 2
for _, p := range db.Used() {
ret.Regions = append(ret.Regions, Region{Prefix: p, Rect: placeInto(p, root)})
}
return ret
}
type Rect struct {
X, Y, W, H 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 := 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 (db *DB) Provision(p netip.Prefix) error {
// db.Lock()
// defer db.Unlock()

100
main.go
View File

@ -49,9 +49,9 @@ func (s *server) apiPrefix(w http.ResponseWriter, r *http.Request) {
}
type PageContent struct {
Api *ipalloc.DB
Update UpdateMessage
SVG template.HTML
Api *ipalloc.DB
Update UpdateMessage
Treemap ipalloc.Treemap
}
type UpdateMessage struct {
Type string
@ -59,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, SVG: renderSVG(s.ipdb.Used(), s.ipdb.Root())})
err := s.tmpl.Execute(w, PageContent{Api: s.ipdb, Treemap: s.ipdb.Treemap()})
if err != nil {
log.Println(err)
}
@ -103,7 +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()))
s.tmpl.ExecuteTemplate(w, "treemap", s.ipdb.Treemap())
if err := s.tmpl.ExecuteTemplate(w, "update", updmsg); err != nil {
log.Println(err)
}
@ -135,7 +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, "treemap", s.ipdb.Treemap())
s.tmpl.ExecuteTemplate(w, "update", updmsg)
s.tmpl.ExecuteTemplate(w, "used", s.ipdb)
}
@ -159,91 +159,3 @@ 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())
}