From e7b72b4365b804fccfb0d7b34334fe97e687b697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Tue, 29 Nov 2022 14:21:23 -0800 Subject: [PATCH] Add RenderPriority to objects and edges --- d2exporter/export.go | 19 ++++++++++++++++--- d2graph/d2graph.go | 4 ++++ d2renderers/d2svg/d2svg.go | 33 ++++++++++++++++++++------------- d2target/d2target.go | 17 ++++++++++++++++- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/d2exporter/export.go b/d2exporter/export.go index 987ede37d..da15a0424 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -8,6 +8,7 @@ import ( "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/d2/lib/go2" ) func Export(ctx context.Context, g *d2graph.Graph, themeID int64) (*d2target.Diagram, error) { @@ -16,13 +17,16 @@ func Export(ctx context.Context, g *d2graph.Graph, themeID int64) (*d2target.Dia diagram := d2target.NewDiagram() diagram.Shapes = make([]d2target.Shape, len(g.Objects)) + highestObjectPriority := 0 for i := range g.Objects { diagram.Shapes[i] = toShape(g.Objects[i], &theme) + highestObjectPriority = go2.IntMax(highestObjectPriority, diagram.Shapes[i].RenderPriority) } + edgeDefaultRenderPriority := highestObjectPriority + 1 diagram.Connections = make([]d2target.Connection, len(g.Edges)) for i := range g.Edges { - diagram.Connections[i] = toConnection(g.Edges[i], &theme) + diagram.Connections[i] = toConnection(g.Edges[i], &theme, edgeDefaultRenderPriority) } return diagram, nil @@ -88,13 +92,17 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape { shape := d2target.BaseShape() shape.SetType(obj.Attributes.Shape.Value) shape.ID = obj.AbsID() + if obj.RenderPriority == nil { + shape.RenderPriority = int(obj.Level()) + } else { + shape.RenderPriority = *obj.RenderPriority + } shape.Pos = d2target.NewPoint(int(obj.TopLeft.X), int(obj.TopLeft.Y)) shape.Width = int(obj.Width) shape.Height = int(obj.Height) text := obj.Text() shape.FontSize = text.FontSize - shape.Level = int(obj.Level()) applyStyles(shape, obj) applyTheme(shape, obj, theme) @@ -130,9 +138,14 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape { return *shape } -func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection { +func toConnection(edge *d2graph.Edge, theme *d2themes.Theme, defaultRenderPriotity int) d2target.Connection { connection := d2target.BaseConnection() connection.ID = edge.AbsID() + if edge.RenderPriority == nil { + connection.RenderPriority = defaultRenderPriotity + } else { + connection.RenderPriority = *edge.RenderPriority + } // edge.Edge.ID = go2.StringToIntHash(connection.ID) text := edge.Text() diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 7bc2ba1cc..b18455682 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -78,6 +78,8 @@ type Object struct { ChildrenArray []*Object `json:"-"` Attributes Attributes `json:"attributes"` + + RenderPriority *int `json:"renderPriority",omitempty` } type Attributes struct { @@ -630,6 +632,8 @@ type Edge struct { References []EdgeReference `json:"references,omitempty"` Attributes Attributes `json:"attributes"` + + RenderPriority *int `json:"renderPriority,omitempty"` } type EdgeReference struct { diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 9d2cb1bd1..21f55a077 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -10,6 +10,7 @@ import ( "fmt" "hash/fnv" "io" + "sort" "strings" "math" @@ -983,25 +984,31 @@ func Render(diagram *d2target.Diagram) ([]byte, error) { // SVG has no notion of z-index. The z-index is effectively the order it's drawn. // So draw from the least nested to most nested idToShape := make(map[string]d2target.Shape) - highest := 1 + allObjects := make([]d2target.DiagramObject, 0, len(diagram.Shapes)+len(diagram.Connections)) for _, s := range diagram.Shapes { - highest = go2.Max(highest, s.Level) idToShape[s.ID] = s + allObjects = append(allObjects, s) } - for i := 1; i <= highest; i++ { - for _, s := range diagram.Shapes { - if s.Level == i { - err := drawShape(buf, s) - if err != nil { - return nil, err - } - } - } + for _, c := range diagram.Connections { + allObjects = append(allObjects, c) } + sort.SliceStable(allObjects, func(i, j int) bool { + return allObjects[i].GetPriority() < allObjects[j].GetPriority() + }) + markers := map[string]struct{}{} - for _, c := range diagram.Connections { - drawConnection(buf, c, markers, idToShape) + for _, obj := range allObjects { + if c, is := obj.(d2target.Connection); is { + drawConnection(buf, c, markers, idToShape) + } else if s, is := obj.(d2target.Shape); is { + err := drawShape(buf, s) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("unknow object of type %t", obj) + } } embedFonts(buf) diff --git a/d2target/d2target.go b/d2target/d2target.go index 1582db666..0b25b35fe 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -17,6 +17,10 @@ const ( MAX_ICON_SIZE = 64 ) +type DiagramObject interface { + GetPriority() int +} + type Diagram struct { Name string `json:"name"` Description string `json:"description,omitempty"` @@ -91,7 +95,6 @@ type Shape struct { Pos Point `json:"pos"` Width int `json:"width"` Height int `json:"height"` - Level int `json:"level"` Opacity float64 `json:"opacity"` StrokeDash float64 `json:"strokeDash"` @@ -117,6 +120,8 @@ type Shape struct { Text LabelPosition string `json:"labelPosition,omitempty"` + + RenderPriority int `json:"renderPriority"` } func (s *Shape) SetType(t string) { @@ -130,6 +135,10 @@ func (s *Shape) SetType(t string) { s.Type = strings.ToLower(t) } +func (s Shape) GetPriority() int { + return s.RenderPriority +} + type Text struct { Label string `json:"label"` FontSize int `json:"fontSize"` @@ -183,6 +192,8 @@ type Connection struct { Animated bool `json:"animated"` Tooltip string `json:"tooltip"` Icon *url.URL `json:"icon"` + + RenderPriority int `json:"renderPriority"` } func BaseConnection() *Connection { @@ -210,6 +221,10 @@ func (c *Connection) GetLabelTopLeft() *geo.Point { ) } +func (c Connection) GetPriority() int { + return c.RenderPriority +} + type Arrowhead string const (