Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update grid cell's label/icon positioning #1670

Merged
merged 10 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@

- Fixes a bug calculating grid height with only grid-rows and different horizontal-gap and vertical-gap values. [#1646](https://github.com/terrastruct/d2/pull/1646)
- Grid layout now accounts for each cell's outside labels and icons [#1624](https://github.com/terrastruct/d2/pull/1624)
- Grid layout now accounts for labels wider or taller than the shape and fixes default label positions for image grid cells. [#1670](https://github.com/terrastruct/d2/pull/1670)
- Fixes a panic with a spread substitution in a glob map [#1643](https://github.com/terrastruct/d2/pull/1643)
- Fixes use of `null` in `sql_table` constraints (ty @landmaj) [#1660](https://github.com/terrastruct/d2/pull/1660)
27 changes: 27 additions & 0 deletions d2graph/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,33 @@ func (obj *Object) GetMargin() geo.Spacing {
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
margin.Right = labelWidth
}

// if an outside label is larger than the object add margin accordingly
if labelWidth > obj.Width {
dx := labelWidth - obj.Width
switch position {
case label.OutsideTopLeft, label.OutsideBottomLeft:
// label fixed at left will overflow on right
margin.Right = dx
case label.OutsideTopCenter, label.OutsideBottomCenter:
margin.Left = math.Ceil(dx / 2)
margin.Right = math.Ceil(dx / 2)
case label.OutsideTopRight, label.OutsideBottomRight:
margin.Left = dx
}
}
if labelHeight > obj.Height {
dy := labelHeight - obj.Height
switch position {
case label.OutsideLeftTop, label.OutsideRightTop:
margin.Bottom = dy
case label.OutsideLeftMiddle, label.OutsideRightMiddle:
margin.Top = math.Ceil(dy / 2)
margin.Bottom = math.Ceil(dy / 2)
case label.OutsideLeftBottom, label.OutsideRightBottom:
margin.Top = dy
}
}
}

if obj.Icon != nil && obj.IconPosition != nil && obj.Shape.Value != d2target.ShapeImage {
Expand Down
131 changes: 111 additions & 20 deletions d2layouts/d2grid/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,34 @@ func Layout(ctx context.Context, g *d2graph.Graph) error {
func layoutGrid(g *d2graph.Graph, obj *d2graph.Object) (*gridDiagram, error) {
gd := newGridDiagram(obj)

// position labels and icons
for _, o := range gd.objects {
positionedLabel := false
if o.Icon != nil && o.IconPosition == nil {
if len(o.ChildrenArray) > 0 {
o.IconPosition = go2.Pointer(string(label.OutsideTopLeft))
// don't overwrite position if nested graph layout positioned label/icon
if o.LabelPosition == nil {
o.LabelPosition = go2.Pointer(string(label.OutsideTopRight))
positionedLabel = true
}
} else {
o.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
if !positionedLabel && o.HasLabel() && o.LabelPosition == nil {
if len(o.ChildrenArray) > 0 {
o.LabelPosition = go2.Pointer(string(label.OutsideTopCenter))
} else if o.HasOutsideBottomLabel() {
o.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
} else if o.Icon != nil {
o.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
o.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
}

// to handle objects with outside labels, we adjust their dimensions before layout and
// after layout, we remove the label adjustment and reposition TopLeft if needed
revertAdjustments := gd.sizeForOutsideLabels()
Expand All @@ -154,23 +182,6 @@ func layoutGrid(g *d2graph.Graph, obj *d2graph.Object) (*gridDiagram, error) {

revertAdjustments()

// position labels and icons
for _, o := range gd.objects {
if o.Icon != nil {
// don't overwrite position if nested graph layout positioned label/icon
if o.LabelPosition == nil {
o.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
}
if o.IconPosition == nil {
o.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
} else {
if o.LabelPosition == nil {
o.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
}

return gd, nil
}

Expand Down Expand Up @@ -852,15 +863,95 @@ func (gd *gridDiagram) sizeForOutsideLabels() (revert func()) {
o.Width += margin.Left + margin.Right
}

// Example: a single column with 3 shapes and
// `x.label: long label {near: outside-bottom-left}`
// `y.label: outsider {near: outside-right-center}`
// . ┌───────────────────┐
// . │ widest shape here │
// . └───────────────────┘
// . ┌───┐
// . │ x │
// . └───┘
// . long label
// . ├─────────┤ x's new width
// . ├─mr──┤ margin.right added to width during layout
// . ┌───┐
// . │ y │ outsider
// . └───┘
// . ├─────────────┤ y's new width
// . ├───mr────┤ margin.right added to width during layout

// BEFORE LAYOUT
// . ┌───────────────────┐
// . │ widest shape here │
// . └───────────────────┘
// . ┌─────────┐
// . │ x │
// . └─────────┘
// . ┌─────────────┐
// . │ y │
// . └─────────────┘

// AFTER LAYOUT
// . ┌───────────────────┐
// . │ widest shape here │
// . └───────────────────┘
// . ┌───────────────────┐
// . │ x │
// . └───────────────────┘
// . ┌───────────────────┐
// . │ y │
// . └───────────────────┘

// CLEANUP 1/2
// . ┌───────────────────┐
// . │ widest shape here │
// . └───────────────────┘
// . ┌─────────────┐
// . │ x │
// . └─────────────┘
// . long label ├─mr──┤ remove margin we added
// . ┌─────────┐
// . │ y │ outsider
// . └─────────┘
// . ├───mr────┤ remove margin we added
// CLEANUP 2/2
// . ┌───────────────────┐
// . │ widest shape here │
// . └───────────────────┘
// . ┌───────────────────┐
// . │ x │
// . └───────────────────┘
// . long label ├─mr──┤ we removed too much so add back margin we subtracted, then subtract new margin
// . ┌─────────┐
// . │ y │ outsider
// . └─────────┘
// . ├───mr────┤ margin.right is still needed

return func() {
for _, o := range gd.objects {
margin, has := margins[o]
m, has := margins[o]
if !has {
continue
}
dy := m.Top + m.Bottom
dx := m.Left + m.Right
o.Height -= dy
o.Width -= dx

o.Height -= margin.Top + margin.Bottom
o.Width -= margin.Left + margin.Right
// less margin may be needed if layout grew the object
// compute the new margin after removing the old margin we added
margin := o.GetMargin()
marginX := margin.Left + margin.Right
marginY := margin.Top + margin.Bottom
if marginX < dx {
// layout grew width and now we need less of a margin (but we subtracted too much)
// add back dx and subtract the new amount
o.Width += dx - marginX
}
if marginY < dy {
o.Height += dy - marginY
}

if margin.Left > 0 || margin.Top > 0 {
o.MoveWithDescendants(margin.Left, margin.Top)
Expand Down
1 change: 1 addition & 0 deletions e2etests/regression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,7 @@ cf many required: {
loadFromFile(t, "code_font_size"),
loadFromFile(t, "disclaimer"),
loadFromFile(t, "grid_rows_gap_bug"),
loadFromFile(t, "grid_image_label_position"),
}

runa(t, tcs)
Expand Down
25 changes: 25 additions & 0 deletions e2etests/testdata/files/grid_image_label_position.d2
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Processing: {
grid-columns: 2
grid-gap: 0
flink: {
label: stream processor
shape: image
icon: https://icons.terrastruct.com/essentials/004-picture.svg
}
sdb: {
label: streaming database
shape: image
icon: https://icons.terrastruct.com/essentials/004-picture.svg
}
o: {
label: 1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16
label.near: outside-right-center
shape: image
icon: https://icons.terrastruct.com/essentials/004-picture.svg
}
k: {
label: streaming database
shape: image
icon: https://icons.terrastruct.com/essentials/004-picture.svg
}
}
Loading