diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 2a77cc19bb..89a8916b73 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -19,3 +19,4 @@ - Adds a compiler error when accidentally using an arrowhead on a shape [#1686](https://github.com/terrastruct/d2/pull/1686) - Correctly reports errors from invalid values set by globs. [#1691](https://github.com/terrastruct/d2/pull/1691) - Fixes panic when spread substitution referenced a nonexistant var. [#1695](https://github.com/terrastruct/d2/pull/1695) +- Fixes incorrect appendix icon numbering. [#1704](https://github.com/terrastruct/d2/pull/1704) diff --git a/d2renderers/d2svg/appendix/appendix.go b/d2renderers/d2svg/appendix/appendix.go index 803651f355..736e39d62b 100644 --- a/d2renderers/d2svg/appendix/appendix.go +++ b/d2renderers/d2svg/appendix/appendix.go @@ -7,6 +7,7 @@ package appendix import ( "fmt" "regexp" + "sort" "strconv" "strings" @@ -146,19 +147,48 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by closingIndex := strings.LastIndex(svg, "") svg = svg[:closingIndex] + appendix + svg[closingIndex:] + // icons are numbered according to diagram.Shapes which is based on their order of definition, + // but they appear in the svg according to renderOrder so we have to replace in that order + type appendixIcon struct { + number int + isTooltip bool + shape d2target.Shape + } + var renderOrder []appendixIcon + i := 1 for _, s := range diagram.Shapes { if s.Tooltip != "" { - // The clip-path has a unique ID, so this won't replace any user icons - // In the existing SVG, the transform places it top-left, so we adjust - svg = strings.Replace(svg, d2svg.TooltipIcon, generateNumberedIcon(i, 0, ICON_RADIUS), 1) + renderOrder = append(renderOrder, appendixIcon{i, true, s}) i++ } if s.Link != "" { - svg = strings.Replace(svg, d2svg.LinkIcon, generateNumberedIcon(i, 0, ICON_RADIUS), 1) + renderOrder = append(renderOrder, appendixIcon{i, false, s}) i++ } } + // sort to match render order + sort.SliceStable(renderOrder, func(i, j int) bool { + iZIndex := renderOrder[i].shape.GetZIndex() + jZIndex := renderOrder[j].shape.GetZIndex() + if iZIndex != jZIndex { + return iZIndex < jZIndex + } + return renderOrder[i].shape.Level < renderOrder[j].shape.Level + }) + + // replace each rendered svg icon + for _, icon := range renderOrder { + // The clip-path has a unique ID, so this won't replace any user icons + // In the existing SVG, the transform places it top-left, so we adjust + var iconStr string + if icon.isTooltip { + iconStr = d2svg.TooltipIcon + } else { + iconStr = d2svg.LinkIcon + } + svg = strings.Replace(svg, iconStr, generateNumberedIcon(icon.number, 0, ICON_RADIUS), 1) + } return []byte(svg) }