Skip to content

Commit d4b6bcd

Browse files
committed
Merge branch 'master' into archs
2 parents 2330bc4 + e7596be commit d4b6bcd

File tree

304 files changed

+13497
-820
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

304 files changed

+13497
-820
lines changed

README.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8
4242
- <a href="#community-plugins" id="toc-community-plugins">Community plugins</a>
4343
- <a href="#misc" id="toc-misc">Misc</a>
4444
- <a href="#faq" id="toc-faq">FAQ</a>
45+
- <a href="#open-source-projects-documenting-with-d2" id="toc-open-source-projects-documenting-with-d2">Open-source projects documenting with D2</a>
4546

4647
## What does D2 look like?
4748

@@ -130,7 +131,9 @@ improved security but the install script is by no means insecure.
130131
In addition to being a runnable CLI tool, D2 can also be used to produce diagrams from
131132
Go programs.
132133

133-
For examples, see [./docs/examples/lib](./docs/examples/lib).
134+
For examples, see [./docs/examples/lib](./docs/examples/lib). This [blog
135+
post](https://terrastruct.com/blog/post/generate-diagrams-programmatically/) also demos a
136+
complete, runnable example of using D2 as a library for a real-world use case.
134137

135138
## Themes
136139

@@ -218,6 +221,7 @@ let us know and we'll be happy to include it here!
218221
- **D2 org-mode support**: [https://github.com/xcapaldi/ob-d2](https://github.com/xcapaldi/ob-d2)
219222
- **Python D2 diagram builder**: [https://github.com/MrBlenny/py-d2](https://github.com/MrBlenny/py-d2)
220223
- **Clojure D2 transpiler**: [https://github.com/judepayne/dictim](https://github.com/judepayne/dictim)
224+
- **JavaScript D2 diagram builder**: [https://github.com/Kreshnik/d2lang-js](https://github.com/Kreshnik/d2lang-js)
221225

222226
### Misc
223227

@@ -240,3 +244,21 @@ let us know and we'll be happy to include it here!
240244
- Please open up a Github Issue.
241245
- I have a private inquiry.
242246
- Please reach out at [hi@d2lang.com](hi@d2lang.com).
247+
248+
## Open-source projects documenting with D2
249+
250+
Do you have or see an open-source project with `.d2` files? Please submit a PR adding to
251+
this list (ordered by star count, desc).
252+
253+
- [Block Protocol](https://github.com/blockprotocol/blockprotocol) - The Block Protocol is
254+
an open standard for building and using data-driven blocks.
255+
- [Ivy Wallet](https://github.com/Ivy-Apps/ivy-wallet) - Ivy Wallet is an open-source
256+
money manager app for Android.
257+
- [Learn EVM Attacks](https://github.com/coinspect/learn-evm-attacks) - Learn & Contribute
258+
on previously exploited vulnerabilities across several EVM projects.
259+
- [BYCEPS](https://github.com/byceps/byceps) - BYCEPS is a self-hosted web platform to run
260+
LAN parties.
261+
- [Re:Earth](https://github.com/reearth/reearth-web) - A free, open and highly extensible
262+
WebGIS platform.
263+
- [Terraform OCI VSCode Server](https://github.com/timoa/terraform-oci-vscode-server) -
264+
Terraform project that deploys VSCode Server on Oracle Cloud Infrastructure.

ci/release/changelogs/next.md

+25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
1+
Many meaningful quality of life improvements and bug fixes, along with a few small features. Overall, a stabilizing set of changes, while some huge features are brewing in the background for the next release!
2+
3+
Thank you to the new contributors that have been joining us. If you want to get involved, there's lots of issues tagged with "good first issue" that are relatively easy to pick up. We're always around to lend a hand, and feel free to drop by our Discord if you're not sure where to start.
4+
5+
Have you enjoyed using D2? We're redesigning some of the site and will have a section for testimonials. If you'd like to be included with a few words alongside your name or public profile, please email us at hi@d2lang.com (or just post it somewhere and let us know)!
6+
17
#### Features 🚀
28

9+
- `animated` keyword implemented for connections. [#652](https://github.com/terrastruct/d2/pull/652)
10+
![animated connection example](https://user-images.githubusercontent.com/3120367/213055161-e6f1918b-150c-4beb-b61c-3ea05cc29f00.svg)
11+
- `border-radius` keyword implemented for squares/rectangles. [#688](https://github.com/terrastruct/d2/pull/688)
12+
- `circle` arrowheads. [#634](https://github.com/terrastruct/d2/pull/634)
13+
314
#### Improvements 🧹
415

516
- ELK layouts tuned to have better defaults. [#627](https://github.com/terrastruct/d2/pull/627)
617
- Code snippets of unrecognized languages will render (just without syntax highlighting). [#650](https://github.com/terrastruct/d2/pull/650)
18+
- Adds sketched versions of arrowheads. [#656](https://github.com/terrastruct/d2/pull/656)
719

820
#### Bugfixes ⛑️
21+
22+
- Fixes code snippets not being tall enough with leading newlines. [#664](https://github.com/terrastruct/d2/pull/664)
23+
- Opacity was not being applied to labels of shapes (and other edge cases). [#677](https://github.com/terrastruct/d2/pull/677)
24+
- Fixes arrowheads sometimes appearing broken with sketch on. [#656](https://github.com/terrastruct/d2/pull/656)
25+
- Fixes attributes being ignored for `sql_table` to `sql_table` connections. [#658](https://github.com/terrastruct/d2/pull/658)
26+
- Icon URLs that needed escaping (e.g. with ampersands) are handled correctly by CLI. [#666](https://github.com/terrastruct/d2/pull/666)
27+
- Fixes self-connections inside layouts when using ELK. [#676](https://github.com/terrastruct/d2/pull/676)
28+
- Fixes inter-span messages between spans of the same actor in sequence diagrams. [#694](https://github.com/terrastruct/d2/pull/694)
29+
- Fixes arrowheads sometimes appearing broken in Dagre layouts. [#649](https://github.com/terrastruct/d2/pull/649)
30+
- Fixes tooltip/link attributes being ignored for `sql_table` and `class`. [#658](https://github.com/terrastruct/d2/pull/658)
31+
- Bounding box was not accounting for dimensions added by `multiple` and `3d` keywords, which made them look cut off with 0 padding. [#684](https://github.com/terrastruct/d2/pull/684), [#685](https://github.com/terrastruct/d2/pull/685)
32+
- Fixes markdown shapes being slightly too short for their text in some cases. [#665](https://github.com/terrastruct/d2/pull/665)
33+
- Fixes panic when the only diagram object has `near` set to a constant. [#687](https://github.com/terrastruct/d2/pull/687)

ci/sub

d2compiler/compile.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -757,13 +757,15 @@ func flattenContainer(g *d2graph.Graph, obj *d2graph.Object) {
757757
// TODO more attributes
758758
if e.SrcTableColumnIndex != nil {
759759
newEdge.SrcTableColumnIndex = new(int)
760+
newEdge.SrcArrowhead = e.SrcArrowhead
760761
*newEdge.SrcTableColumnIndex = *e.SrcTableColumnIndex
761762
}
762763
if e.DstTableColumnIndex != nil {
763764
newEdge.DstTableColumnIndex = new(int)
765+
newEdge.DstArrowhead = e.DstArrowhead
764766
*newEdge.DstTableColumnIndex = *e.DstTableColumnIndex
765767
}
766-
newEdge.Attributes.Label = e.Attributes.Label
768+
newEdge.Attributes = e.Attributes
767769
newEdge.References = e.References
768770
}
769771
updatedEdges := []*d2graph.Edge{}

d2compiler/compile_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,25 @@ b`, g.Objects[0].Attributes.Label.Value)
15971597
}
15981598
},
15991599
},
1600+
{
1601+
name: "table_connection_attr",
1602+
1603+
text: `x: {
1604+
shape: sql_table
1605+
y
1606+
}
1607+
a: {
1608+
shape: sql_table
1609+
b
1610+
}
1611+
x.y -> a.b: {
1612+
style.animated: true
1613+
}
1614+
`,
1615+
assertions: func(t *testing.T, g *d2graph.Graph) {
1616+
tassert.Equal(t, "true", g.Edges[0].Attributes.Style.Animated.Value)
1617+
},
1618+
},
16001619
{
16011620
name: "class_paren",
16021621

d2graph/d2graph.go

+21
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,27 @@ func GetTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2
10271027
var h int
10281028
if t.Language != "" {
10291029
w, h = ruler.Measure(d2fonts.SourceCodePro.Font(t.FontSize, d2fonts.FONT_STYLE_REGULAR), t.Text)
1030+
1031+
// count empty leading and trailing lines since ruler will not be able to measure it
1032+
lines := strings.Split(t.Text, "\n")
1033+
leadingLines := 0
1034+
for _, line := range lines {
1035+
if strings.TrimSpace(line) == "" {
1036+
leadingLines++
1037+
} else {
1038+
break
1039+
}
1040+
}
1041+
trailingLines := 0
1042+
for i := len(lines) - 1; i >= 0; i-- {
1043+
if strings.TrimSpace(lines[i]) == "" {
1044+
trailingLines++
1045+
} else {
1046+
break
1047+
}
1048+
}
1049+
h += t.FontSize * (leadingLines + trailingLines)
1050+
10301051
// padding
10311052
w += 12
10321053
h += 12

d2layouts/d2dagrelayout/layout.go

+54-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ var setupJS string
3030
//go:embed dagre.js
3131
var dagreJS string
3232

33+
const MIN_SEGMENT_LEN = 10
34+
3335
type ConfigurableOpts struct {
3436
NodeSep int `json:"nodesep"`
3537
EdgeSep int `json:"edgesep"`
@@ -247,6 +249,47 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
247249
}
248250
}
249251

252+
// arrowheads can appear broken if segments are very short from dagre routing a point just outside the shape
253+
// to fix this, we try extending the previous segment into the shape instead of having a very short segment
254+
if !start.Equals(points[0]) && startIndex+2 < len(points) {
255+
newStartingSegment := *geo.NewSegment(start, points[startIndex+1])
256+
if newStartingSegment.Length() < MIN_SEGMENT_LEN {
257+
// we don't want a very short segment right next to the source because it will mess up the arrowhead
258+
// instead we want to extend the next segment into the shape border if possible
259+
nextStart := points[startIndex+1]
260+
nextEnd := points[startIndex+2]
261+
262+
// Note: in other direction to extend towards source
263+
nextSegment := *geo.NewSegment(nextStart, nextEnd)
264+
v := nextSegment.ToVector()
265+
extendedStart := nextEnd.ToVector().Add(v.AddLength(MIN_SEGMENT_LEN)).ToPoint()
266+
extended := *geo.NewSegment(nextEnd, extendedStart)
267+
268+
if intersections := edge.Src.Box.Intersections(extended); len(intersections) > 0 {
269+
start = intersections[0]
270+
startIndex += 1
271+
}
272+
}
273+
}
274+
if !end.Equals(points[len(points)-1]) && endIndex-2 >= 0 {
275+
newEndingSegment := *geo.NewSegment(end, points[endIndex-1])
276+
if newEndingSegment.Length() < MIN_SEGMENT_LEN {
277+
// extend the prev segment into the shape border if possible
278+
prevStart := points[endIndex-2]
279+
prevEnd := points[endIndex-1]
280+
281+
prevSegment := *geo.NewSegment(prevStart, prevEnd)
282+
v := prevSegment.ToVector()
283+
extendedEnd := prevStart.ToVector().Add(v.AddLength(MIN_SEGMENT_LEN)).ToPoint()
284+
extended := *geo.NewSegment(prevStart, extendedEnd)
285+
286+
if intersections := edge.Dst.Box.Intersections(extended); len(intersections) > 0 {
287+
end = intersections[0]
288+
endIndex -= 1
289+
}
290+
}
291+
}
292+
250293
srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Attributes.Shape.Value)], edge.Src.Box)
251294
dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Attributes.Shape.Value)], edge.Dst.Box)
252295

@@ -263,18 +306,20 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
263306

264307
path := make([]*geo.Point, 0)
265308
path = append(path, points[0])
266-
path = append(path, points[0].AddVector(vectors[0].Multiply(.8)))
267-
for i := 1; i < len(vectors)-2; i++ {
268-
p := points[i]
269-
v := vectors[i]
270-
path = append(path, p.AddVector(v.Multiply(.2)))
271-
path = append(path, p.AddVector(v.Multiply(.5)))
272-
path = append(path, p.AddVector(v.Multiply(.8)))
309+
if len(vectors) > 1 {
310+
path = append(path, points[0].AddVector(vectors[0].Multiply(.8)))
311+
for i := 1; i < len(vectors)-2; i++ {
312+
p := points[i]
313+
v := vectors[i]
314+
path = append(path, p.AddVector(v.Multiply(.2)))
315+
path = append(path, p.AddVector(v.Multiply(.5)))
316+
path = append(path, p.AddVector(v.Multiply(.8)))
317+
}
318+
path = append(path, points[len(points)-2].AddVector(vectors[len(vectors)-1].Multiply(.2)))
319+
edge.IsCurve = true
273320
}
274-
path = append(path, points[len(points)-2].AddVector(vectors[len(vectors)-1].Multiply(.2)))
275321
path = append(path, points[len(points)-1])
276322

277-
edge.IsCurve = true
278323
edge.Route = path
279324
// compile needs to assign edge label positions
280325
if edge.Attributes.Label.Value != "" {

d2layouts/d2elklayout/layout.go

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
195195
ConfigurableOpts: ConfigurableOpts{
196196
NodeSpacing: opts.NodeSpacing,
197197
EdgeNodeSpacing: opts.EdgeNodeSpacing,
198+
SelfLoopSpacing: opts.SelfLoopSpacing,
198199
Padding: opts.Padding,
199200
},
200201
}

d2layouts/d2near/layout.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, constantNears []*d2graph.Obje
2727
// So place the center ones first, then the later ones will consider them for bounding box
2828
for _, processCenters := range []bool{true, false} {
2929
for _, obj := range constantNears {
30-
if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") {
30+
if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") {
3131
obj.TopLeft = geo.NewPoint(place(obj))
3232
}
3333
}
3434
for _, obj := range constantNears {
35-
if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") {
35+
if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") {
3636
// The z-index for constant nears does not matter, as it will not collide
3737
g.Objects = append(g.Objects, obj)
3838
obj.Parent.Children[obj.ID] = obj
@@ -152,5 +152,14 @@ func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) {
152152
}
153153
}
154154

155+
if math.IsInf(x1, 1) && math.IsInf(x2, -1) {
156+
x1 = 0
157+
x2 = 0
158+
}
159+
if math.IsInf(y1, 1) && math.IsInf(y2, -1) {
160+
y1 = 0
161+
y2 = 0
162+
}
163+
155164
return geo.NewPoint(x1, y1), geo.NewPoint(x2, y2)
156165
}

d2layouts/d2sequence/layout.go

+38-36
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,45 @@ func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string]
1717
edgesToRemove := make(map[*d2graph.Edge]struct{})
1818
sequenceDiagrams := make(map[string]*sequenceDiagram)
1919

20-
queue := make([]*d2graph.Object, 1, len(g.Objects))
21-
queue[0] = g.Root
22-
for len(queue) > 0 {
23-
obj := queue[0]
24-
queue = queue[1:]
25-
if len(obj.ChildrenArray) == 0 {
26-
continue
27-
}
28-
if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram {
29-
queue = append(queue, obj.ChildrenArray...)
30-
continue
31-
}
32-
33-
sd, err := layoutSequenceDiagram(g, obj)
34-
if err != nil {
35-
return nil, nil, nil, err
36-
}
37-
obj.Children = make(map[string]*d2graph.Object)
38-
obj.ChildrenArray = nil
39-
obj.Box = geo.NewBox(nil, sd.getWidth()+GROUP_CONTAINER_PADDING*2, sd.getHeight()+GROUP_CONTAINER_PADDING*2)
40-
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
41-
sequenceDiagrams[obj.AbsID()] = sd
20+
if len(g.Objects) > 0 {
21+
queue := make([]*d2graph.Object, 1, len(g.Objects))
22+
queue[0] = g.Root
23+
for len(queue) > 0 {
24+
obj := queue[0]
25+
queue = queue[1:]
26+
if len(obj.ChildrenArray) == 0 {
27+
continue
28+
}
29+
if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram {
30+
queue = append(queue, obj.ChildrenArray...)
31+
continue
32+
}
4233

43-
for _, edge := range sd.messages {
44-
edgesToRemove[edge] = struct{}{}
45-
}
46-
for _, obj := range sd.actors {
47-
objectsToRemove[obj] = struct{}{}
48-
}
49-
for _, obj := range sd.notes {
50-
objectsToRemove[obj] = struct{}{}
51-
}
52-
for _, obj := range sd.groups {
53-
objectsToRemove[obj] = struct{}{}
54-
}
55-
for _, obj := range sd.spans {
56-
objectsToRemove[obj] = struct{}{}
34+
sd, err := layoutSequenceDiagram(g, obj)
35+
if err != nil {
36+
return nil, nil, nil, err
37+
}
38+
obj.Children = make(map[string]*d2graph.Object)
39+
obj.ChildrenArray = nil
40+
obj.Box = geo.NewBox(nil, sd.getWidth()+GROUP_CONTAINER_PADDING*2, sd.getHeight()+GROUP_CONTAINER_PADDING*2)
41+
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
42+
sequenceDiagrams[obj.AbsID()] = sd
43+
44+
for _, edge := range sd.messages {
45+
edgesToRemove[edge] = struct{}{}
46+
}
47+
for _, obj := range sd.actors {
48+
objectsToRemove[obj] = struct{}{}
49+
}
50+
for _, obj := range sd.notes {
51+
objectsToRemove[obj] = struct{}{}
52+
}
53+
for _, obj := range sd.groups {
54+
objectsToRemove[obj] = struct{}{}
55+
}
56+
for _, obj := range sd.spans {
57+
objectsToRemove[obj] = struct{}{}
58+
}
5759
}
5860
}
5961

d2layouts/d2sequence/sequence_diagram.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,17 @@ func (sd *sequenceDiagram) routeMessages() error {
471471
isFromDescendant := strings.HasPrefix(message.Src.AbsID(), message.Dst.AbsID()+".")
472472
isSelfMessage := message.Src == message.Dst
473473

474-
if isSelfMessage || isToDescendant || isFromDescendant {
474+
currSrc := message.Src
475+
for !currSrc.Parent.IsSequenceDiagram() {
476+
currSrc = currSrc.Parent
477+
}
478+
currDst := message.Dst
479+
for !currDst.Parent.IsSequenceDiagram() {
480+
currDst = currDst.Parent
481+
}
482+
isToSibling := currSrc == currDst
483+
484+
if isSelfMessage || isToDescendant || isFromDescendant || isToSibling {
475485
midX := startX + SELF_MESSAGE_HORIZONTAL_TRAVEL
476486
endY := startY + MIN_MESSAGE_DISTANCE
477487
message.Route = []*geo.Point{

0 commit comments

Comments
 (0)