treemap: move render into template
This commit is contained in:
parent
b8c137efaf
commit
8fb9d45289
56
index.html
56
index.html
|
@ -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>
|
||||
|
|
|
@ -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
100
main.go
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user