Handle nested lifespan
This commit is contained in:
parent
f22580d9ed
commit
77b1166abd
2 changed files with 59 additions and 24 deletions
|
|
@ -8,7 +8,11 @@ const MIN_ACTOR_DISTANCE = 200.
|
||||||
// min vertical distance between edges
|
// min vertical distance between edges
|
||||||
const MIN_EDGE_DISTANCE = 100.
|
const MIN_EDGE_DISTANCE = 100.
|
||||||
|
|
||||||
|
// default size
|
||||||
const LIFESPAN_BOX_WIDTH = 20.
|
const LIFESPAN_BOX_WIDTH = 20.
|
||||||
|
|
||||||
//
|
// as the lifespan boxes start getting nested, their size grows
|
||||||
|
const LIFESPAN_DEPTH_GROW_FACTOR = 10.
|
||||||
|
|
||||||
|
// when a lifespan box has a single edge
|
||||||
const DEFAULT_LIFESPAN_BOX_HEIGHT = MIN_EDGE_DISTANCE / 2.
|
const DEFAULT_LIFESPAN_BOX_HEIGHT = MIN_EDGE_DISTANCE / 2.
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
|
|
@ -15,7 +16,7 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) {
|
||||||
sd := &sequenceDiagram{
|
sd := &sequenceDiagram{
|
||||||
graph: g,
|
graph: g,
|
||||||
objectRank: make(map[*d2graph.Object]int),
|
objectRank: make(map[*d2graph.Object]int),
|
||||||
edgeRank: make(map[*d2graph.Edge]int),
|
objectDepth: make(map[*d2graph.Object]int),
|
||||||
minEdgeRank: make(map[*d2graph.Object]int),
|
minEdgeRank: make(map[*d2graph.Object]int),
|
||||||
maxEdgeRank: make(map[*d2graph.Object]int),
|
maxEdgeRank: make(map[*d2graph.Object]int),
|
||||||
edgeYStep: MIN_EDGE_DISTANCE,
|
edgeYStep: MIN_EDGE_DISTANCE,
|
||||||
|
|
@ -40,8 +41,8 @@ type sequenceDiagram struct {
|
||||||
lifespans []*d2graph.Object
|
lifespans []*d2graph.Object
|
||||||
|
|
||||||
// can be either actors or lifespans
|
// can be either actors or lifespans
|
||||||
objectRank map[*d2graph.Object]int
|
objectRank map[*d2graph.Object]int
|
||||||
edgeRank map[*d2graph.Edge]int
|
objectDepth map[*d2graph.Object]int
|
||||||
|
|
||||||
// keep track of the first and last edge of a given actor
|
// keep track of the first and last edge of a given actor
|
||||||
// needed for lifespan
|
// needed for lifespan
|
||||||
|
|
@ -65,18 +66,27 @@ func (sd *sequenceDiagram) init() {
|
||||||
sd.edges = make([]*d2graph.Edge, len(sd.graph.Edges))
|
sd.edges = make([]*d2graph.Edge, len(sd.graph.Edges))
|
||||||
copy(sd.edges, sd.graph.Edges)
|
copy(sd.edges, sd.graph.Edges)
|
||||||
|
|
||||||
for rank, actor := range sd.graph.Root.ChildrenArray {
|
queue := make([]*d2graph.Object, len(sd.graph.Root.ChildrenArray))
|
||||||
sd.assignRank(actor, rank)
|
copy(queue, sd.graph.Root.ChildrenArray)
|
||||||
}
|
for len(queue) > 0 {
|
||||||
for _, obj := range sd.graph.Objects {
|
obj := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
if obj.Parent == sd.graph.Root {
|
if obj.Parent == sd.graph.Root {
|
||||||
sd.actors = append(sd.actors, obj)
|
sd.actors = append(sd.actors, obj)
|
||||||
|
sd.objectRank[obj] = len(sd.actors)
|
||||||
|
sd.objectDepth[obj] = 0
|
||||||
} else if obj != sd.graph.Root {
|
} else if obj != sd.graph.Root {
|
||||||
|
obj.Attributes.Label = d2graph.Scalar{Value: ""}
|
||||||
sd.lifespans = append(sd.lifespans, obj)
|
sd.lifespans = append(sd.lifespans, obj)
|
||||||
|
sd.objectRank[obj] = sd.objectRank[obj.Parent]
|
||||||
|
sd.objectDepth[obj] = sd.objectDepth[obj.Parent] + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queue = append(queue, obj.ChildrenArray...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for rank, edge := range sd.edges {
|
for rank, edge := range sd.edges {
|
||||||
sd.edgeRank[edge] = rank
|
|
||||||
if edge.Src.Parent == sd.graph.Root {
|
if edge.Src.Parent == sd.graph.Root {
|
||||||
sd.maxActorHeight = math.Max(sd.maxActorHeight, edge.Src.Height+HORIZONTAL_PAD)
|
sd.maxActorHeight = math.Max(sd.maxActorHeight, edge.Src.Height+HORIZONTAL_PAD)
|
||||||
}
|
}
|
||||||
|
|
@ -96,13 +106,6 @@ func (sd *sequenceDiagram) init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *sequenceDiagram) assignRank(actor *d2graph.Object, rank int) {
|
|
||||||
sd.objectRank[actor] = rank
|
|
||||||
for _, child := range actor.Children {
|
|
||||||
sd.assignRank(child, rank)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sd *sequenceDiagram) setMinMaxEdgeRank(actor *d2graph.Object, rank int) {
|
func (sd *sequenceDiagram) setMinMaxEdgeRank(actor *d2graph.Object, rank int) {
|
||||||
if minRank, exists := sd.minEdgeRank[actor]; exists {
|
if minRank, exists := sd.minEdgeRank[actor]; exists {
|
||||||
sd.minEdgeRank[actor] = intMin(minRank, rank)
|
sd.minEdgeRank[actor] = intMin(minRank, rank)
|
||||||
|
|
@ -176,17 +179,45 @@ func (sd *sequenceDiagram) placeLifespan() {
|
||||||
for _, actor := range sd.actors {
|
for _, actor := range sd.actors {
|
||||||
rankToX[sd.objectRank[actor]] = actor.Center().X
|
rankToX[sd.objectRank[actor]] = actor.Center().X
|
||||||
}
|
}
|
||||||
for _, lifespan := range sd.lifespans {
|
|
||||||
lifespan.Attributes.Label = d2graph.Scalar{Value: ""}
|
|
||||||
minRank := sd.minEdgeRank[lifespan]
|
|
||||||
maxRank := sd.maxEdgeRank[lifespan]
|
|
||||||
|
|
||||||
minY := sd.getEdgeY(minRank)
|
lifespanFromMostNested := make([]*d2graph.Object, len(sd.lifespans))
|
||||||
maxY := sd.getEdgeY(maxRank)
|
copy(lifespanFromMostNested, sd.lifespans)
|
||||||
|
sort.SliceStable(lifespanFromMostNested, func(i, j int) bool {
|
||||||
|
return sd.objectDepth[lifespanFromMostNested[i]] > sd.objectDepth[lifespanFromMostNested[j]]
|
||||||
|
})
|
||||||
|
for _, lifespan := range lifespanFromMostNested {
|
||||||
|
minChildY := math.Inf(1)
|
||||||
|
maxChildY := math.Inf(-1)
|
||||||
|
for _, child := range lifespan.ChildrenArray {
|
||||||
|
minChildY = math.Min(minChildY, child.TopLeft.Y)
|
||||||
|
maxChildY = math.Max(maxChildY, child.TopLeft.Y+child.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
minEdgeY := math.Inf(1)
|
||||||
|
if minRank, exists := sd.minEdgeRank[lifespan]; exists {
|
||||||
|
minEdgeY = sd.getEdgeY(minRank)
|
||||||
|
}
|
||||||
|
maxEdgeY := math.Inf(-1)
|
||||||
|
if maxRank, exists := sd.maxEdgeRank[lifespan]; exists {
|
||||||
|
maxEdgeY = sd.getEdgeY(maxRank)
|
||||||
|
}
|
||||||
|
|
||||||
|
minY := math.Min(minEdgeY, minChildY)
|
||||||
|
if minY == minChildY {
|
||||||
|
minY -= LIFESPAN_DEPTH_GROW_FACTOR
|
||||||
|
}
|
||||||
|
maxY := math.Max(maxEdgeY, maxChildY)
|
||||||
|
if maxY == maxChildY {
|
||||||
|
maxY += LIFESPAN_DEPTH_GROW_FACTOR
|
||||||
|
}
|
||||||
|
|
||||||
height := maxY - minY
|
height := maxY - minY
|
||||||
height = math.Max(height, DEFAULT_LIFESPAN_BOX_HEIGHT)
|
height = math.Max(height, DEFAULT_LIFESPAN_BOX_HEIGHT)
|
||||||
x := rankToX[sd.objectRank[lifespan]] - (LIFESPAN_BOX_WIDTH / 2.)
|
|
||||||
lifespan.Box = geo.NewBox(geo.NewPoint(x, minY), LIFESPAN_BOX_WIDTH, height)
|
width := LIFESPAN_BOX_WIDTH + (float64(sd.objectDepth[lifespan]-1) * LIFESPAN_DEPTH_GROW_FACTOR)
|
||||||
|
|
||||||
|
x := rankToX[sd.objectRank[lifespan]] - (width / 2.)
|
||||||
|
lifespan.Box = geo.NewBox(geo.NewPoint(x, minY), width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue