+
+- [Playground link to source code](https://play.d2lang.com/?script=nJRPbvMgEMX3nGIU6Vt-zp82bcoBeg-Mpw4yBhdD0qjK3SvAJMENatWFF8zDjx9vRlwyo7Al4bjKt4FCLRnvCJk6TuGTABz3wiKM744ZjBWAkCqFxSKsgieF9WYVlsEw7QSIzsFlqsQbU-DaqDepj2hq6XLxf_KMlmcSv1pq3iXr2Tl5BhTcMKDhbEzG14HKaSJfw0zHT0zNEGMgV4QzIZ54XYWIaBZOUDZF5aGoPBaVbVF5KirPRWVXVF7uKqRhbYsGULVCTUlPfdmtfGOmn0JXyKwh00ChaJHcJpoFfduTywzkE7BLuTN1gkbzDg1w3Q_MiloiGKes6P8GJ_2DcWH5BWCahXuEAP7pobC3dhjpculXY2XRGDZa47ituO6XDR7-bV7jNarx0KaLcRHRfoZutWwy3p4Zrb_T3pTvB3oUyr9qVXakFMp95KWecT3b1bkajUKLs_pXAAAA__8%3D&sketch=1&)
+
+Bunch of other features, improvements, and bug fixes too. Make sure to check out the updated docs for how to use these new features!
+
+#### Features 🚀
+
+- Classes are implemented. See [docs](https://d2lang.com/tour/classes). [#772](https://github.com/terrastruct/d2/pull/772)
+- Grid diagrams are implemented. See [docs](https://d2lang.com/tour/grid-diagrams). [#1122](https://github.com/terrastruct/d2/pull/1122)
+- Container with constant key near attribute now can have descendant objects and connections (thank you @donglixiaoche) [#1071](https://github.com/terrastruct/d2/pull/1071)
+- Multi-board SVG outputs with internal links go to their output paths [#1116](https://github.com/terrastruct/d2/pull/1116)
+
+#### Improvements 🧹
+
+- Labels on parallel `dagre` connections include a gap between them [#1134](https://github.com/terrastruct/d2/pull/1134)
+- Sequence diagram lifelines inherit the actor's `stroke` and `stroke-dash` [#1140](https://github.com/terrastruct/d2/pull/1140)
+- Add `text-transform` styling option (thank you @alexstoick for these 2) [#1118](https://github.com/terrastruct/d2/pull/1118)
+
+#### Bugfixes ⛑️
+
+- Fix inheritence in scenarios/steps [#1090](https://github.com/terrastruct/d2/pull/1090)
+- Fix a bug in 32bit builds [#1115](https://github.com/terrastruct/d2/issues/1115)
+- Fix a bug in ID parsing [#322](https://github.com/terrastruct/d2/issues/322)
+- Fix a bug in watch mode parsing SVG [#1119](https://github.com/terrastruct/d2/issues/1119)
+- Namespace transitions so that multiple animated D2 diagrams can exist on the same page [#1123](https://github.com/terrastruct/d2/issues/1123)
+- Fix a bug in vertical alignment of appendix lines [#1104](https://github.com/terrastruct/d2/issues/1104)
+- Fix precision difference for sketch mode running on different architectures [#921](https://github.com/terrastruct/d2/issues/921)
+- Fix an issue where markdown fonts weren't being applied correctly [#1132](https://github.com/terrastruct/d2/issues/1132)
+
+#### Breaking changes
+
+- `class` and `classes` are now reserved keywords.
+- `text-transform` is now a reserved keyword.
diff --git a/ci/release/changelogs/v0.4.1.md b/ci/release/changelogs/v0.4.1.md
new file mode 100644
index 000000000..be5771975
--- /dev/null
+++ b/ci/release/changelogs/v0.4.1.md
@@ -0,0 +1,35 @@
+Multi-board D2 compositions now get 2 more useful formats to export to: PowerPoint and GIFs.
+
+## Powerpoint example
+
+Make sure you click present, and click around the links and navigations!
+
+- Download: [wcc.pptx](https://github.com/terrastruct/d2/files/11256733/wcc.pptx)
+- Google Slides: [https://docs.google.com/presentation/d/18rRh4izu3k_43On8PXtVYdqRxmoQJd4y/view](https://docs.google.com/presentation/d/18rRh4izu3k_43On8PXtVYdqRxmoQJd4y/view)
+- Source code: [https://github.com/terrastruct/d2/blob/master/docs/examples/wcc/wcc.d2](https://github.com/terrastruct/d2/blob/master/docs/examples/wcc/wcc.d2)
+
+## GIF example
+
+This is the same diagram as one we shared when we announced animated SVGs, but in GIF form.
+
+
+
+#### Features 🚀
+
+- Export diagrams to `.pptx` (PowerPoint) [#1139](https://github.com/terrastruct/d2/pull/1139)
+- Export diagrams to `.gif` [#1200](https://github.com/terrastruct/d2/pull/1200)
+- Customize gap size in grid diagrams with `grid-gap`, `vertical-gap`, or `horizontal-gap` (see [docs](https://d2lang.com/tour/grid-diagrams/#gap-size)) [#1178](https://github.com/terrastruct/d2/issues/1178)
+- New dark theme "Dark Terrastruct Flagship" with the theme ID of `201` [#1150](https://github.com/terrastruct/d2/issues/1150)
+
+#### Improvements 🧹
+
+- `font-size` works with Markdown text [#1191](https://github.com/terrastruct/d2/issues/1191)
+- SVG outputs for internal links use relative paths instead of absolute [#1197](https://github.com/terrastruct/d2/pull/1197)
+- Improves arrowhead label positioning [#1207](https://github.com/terrastruct/d2/pull/1207)
+
+#### Bugfixes ⛑️
+
+- Fixes grid layouts not applying on objects with a constant near [#1173](https://github.com/terrastruct/d2/issues/1173)
+- Fixes Markdown header rendering in Firefox and Safari [#1177](https://github.com/terrastruct/d2/issues/1177)
+- Fixes a grid layout panic when there are fewer objects than rows/columns [#1189](https://github.com/terrastruct/d2/issues/1189)
+- Fixes an issue where PNG exports that were too large were crashing [#1093](https://github.com/terrastruct/d2/issues/1093)
diff --git a/d2cli/export.go b/d2cli/export.go
new file mode 100644
index 000000000..6da34bdb8
--- /dev/null
+++ b/d2cli/export.go
@@ -0,0 +1,42 @@
+package d2cli
+
+import (
+ "path/filepath"
+)
+
+type exportExtension string
+
+const GIF exportExtension = ".gif"
+const PNG exportExtension = ".png"
+const PPTX exportExtension = ".pptx"
+const PDF exportExtension = ".pdf"
+const SVG exportExtension = ".svg"
+
+var SUPPORTED_EXTENSIONS = []exportExtension{SVG, PNG, PDF, PPTX, GIF}
+
+func getExportExtension(outputPath string) exportExtension {
+ ext := filepath.Ext(outputPath)
+ for _, kext := range SUPPORTED_EXTENSIONS {
+ if kext == exportExtension(ext) {
+ return exportExtension(ext)
+ }
+ }
+ // default is svg
+ return exportExtension(SVG)
+}
+
+func (ex exportExtension) supportsAnimation() bool {
+ return ex == SVG || ex == GIF
+}
+
+func (ex exportExtension) requiresAnimationInterval() bool {
+ return ex == GIF
+}
+
+func (ex exportExtension) requiresPNGRenderer() bool {
+ return ex == PNG || ex == PDF || ex == PPTX || ex == GIF
+}
+
+func (ex exportExtension) supportsDarkTheme() bool {
+ return ex == SVG
+}
diff --git a/d2cli/export_test.go b/d2cli/export_test.go
new file mode 100644
index 000000000..eb7ac44ee
--- /dev/null
+++ b/d2cli/export_test.go
@@ -0,0 +1,88 @@
+package d2cli
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestOutputFormat(t *testing.T) {
+ type testCase struct {
+ outputPath string
+ extension exportExtension
+ supportsDarkTheme bool
+ supportsAnimation bool
+ requiresAnimationInterval bool
+ requiresPngRender bool
+ }
+ testCases := []testCase{
+ {
+ outputPath: "/out.svg",
+ extension: SVG,
+ supportsDarkTheme: true,
+ supportsAnimation: true,
+ requiresAnimationInterval: false,
+ requiresPngRender: false,
+ },
+ {
+ // assumes SVG by default
+ outputPath: "/out",
+ extension: SVG,
+ supportsDarkTheme: true,
+ supportsAnimation: true,
+ requiresAnimationInterval: false,
+ requiresPngRender: false,
+ },
+ {
+ outputPath: "-",
+ extension: SVG,
+ supportsDarkTheme: true,
+ supportsAnimation: true,
+ requiresAnimationInterval: false,
+ requiresPngRender: false,
+ },
+ {
+ outputPath: "/out.png",
+ extension: PNG,
+ supportsDarkTheme: false,
+ supportsAnimation: false,
+ requiresAnimationInterval: false,
+ requiresPngRender: true,
+ },
+ {
+ outputPath: "/out.pptx",
+ extension: PPTX,
+ supportsDarkTheme: false,
+ supportsAnimation: false,
+ requiresAnimationInterval: false,
+ requiresPngRender: true,
+ },
+ {
+ outputPath: "/out.pdf",
+ extension: PDF,
+ supportsDarkTheme: false,
+ supportsAnimation: false,
+ requiresAnimationInterval: false,
+ requiresPngRender: true,
+ },
+ {
+ outputPath: "/out.gif",
+ extension: GIF,
+ supportsDarkTheme: false,
+ supportsAnimation: true,
+ requiresAnimationInterval: true,
+ requiresPngRender: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.outputPath, func(t *testing.T) {
+ extension := getExportExtension(tc.outputPath)
+ assert.Equal(t, tc.extension, extension)
+ assert.Equal(t, tc.supportsAnimation, extension.supportsAnimation())
+ assert.Equal(t, tc.supportsDarkTheme, extension.supportsDarkTheme())
+ assert.Equal(t, tc.requiresPngRender, extension.requiresPNGRenderer())
+ })
+ }
+}
diff --git a/d2cli/main.go b/d2cli/main.go
index 400d81aa5..2f87fee80 100644
--- a/d2cli/main.go
+++ b/d2cli/main.go
@@ -7,6 +7,7 @@ import (
"io"
"os"
"os/exec"
+ "os/user"
"path/filepath"
"strconv"
"strings"
@@ -20,6 +21,7 @@ import (
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2lib"
+ "oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/d2renderers/d2animate"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
@@ -32,10 +34,11 @@ import (
"oss.terrastruct.com/d2/lib/imgbundler"
ctxlog "oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/pdf"
- pdflib "oss.terrastruct.com/d2/lib/pdf"
"oss.terrastruct.com/d2/lib/png"
+ "oss.terrastruct.com/d2/lib/pptx"
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/d2/lib/version"
+ "oss.terrastruct.com/d2/lib/xgif"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
@@ -98,6 +101,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
fontRegularFlag := ms.Opts.String("D2_FONT_REGULAR", "font-regular", "", "", "path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used.")
fontItalicFlag := ms.Opts.String("D2_FONT_ITALIC", "font-italic", "", "", "path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used.")
fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.")
+ fontSemiboldFlag := ms.Opts.String("D2_FONT_SEMIBOLD", "font-semibold", "", "", "path to .ttf file to use for the semibold font. If none provided, Source Sans Pro Semibold is used.")
ps, err := d2plugin.ListPlugins(ctx)
if err != nil {
@@ -118,7 +122,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return nil
}
- fontFamily, err := loadFonts(ms, *fontRegularFlag, *fontItalicFlag, *fontBoldFlag)
+ fontFamily, err := loadFonts(ms, *fontRegularFlag, *fontItalicFlag, *fontBoldFlag, *fontSemiboldFlag)
if err != nil {
return xmain.UsageErrorf("failed to load specified fonts: %v", err)
}
@@ -183,13 +187,16 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
inputPath = filepath.Join(inputPath, "index.d2")
}
}
+ if filepath.Ext(outputPath) == ".ppt" {
+ return xmain.UsageErrorf("D2 does not support ppt exports, did you mean \"pptx\"?")
+ }
+ outputFormat := getExportExtension(outputPath)
if outputPath != "-" {
outputPath = ms.AbsPath(outputPath)
- if *animateIntervalFlag > 0 {
- // Not checking for extension == "svg", because users may want to write SVG data to a non-svg-extension file
- if filepath.Ext(outputPath) == ".png" || filepath.Ext(outputPath) == ".pdf" {
- return xmain.UsageErrorf("-animate-interval can only be used when exporting to SVG.\nYou provided: %s", filepath.Ext(outputPath))
- }
+ if *animateIntervalFlag > 0 && !outputFormat.supportsAnimation() {
+ return xmain.UsageErrorf("-animate-interval can only be used when exporting to SVG or GIF.\nYou provided: %s", filepath.Ext(outputPath))
+ } else if *animateIntervalFlag <= 0 && outputFormat.requiresAnimationInterval() {
+ return xmain.UsageErrorf("-animate-interval must be greater than 0 for %s outputs.\nYou provided: %d", outputFormat, *animateIntervalFlag)
}
}
@@ -233,12 +240,14 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
}
ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, plocation)
- var pw png.Playwright
- if filepath.Ext(outputPath) == ".png" || filepath.Ext(outputPath) == ".pdf" {
+ if !outputFormat.supportsDarkTheme() {
if darkThemeFlag != nil {
ms.Log.Warn.Printf("--dark-theme cannot be used while exporting to another format other than .svg")
darkThemeFlag = nil
}
+ }
+ var pw png.Playwright
+ if outputFormat.requiresPNGRenderer() {
pw, err = png.InitPlaywright()
if err != nil {
return err
@@ -347,16 +356,66 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
return nil, false, err
}
- if filepath.Ext(outputPath) == ".pdf" {
- pageMap := pdf.BuildPDFPageMap(diagram, nil, nil)
- pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, nil, pageMap)
+ ext := getExportExtension(outputPath)
+ switch ext {
+ case GIF:
+ svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, diagram)
+ if err != nil {
+ return nil, false, err
+ }
+ out, err := xgif.AnimatePNGs(ms, pngs, int(animateInterval))
+ if err != nil {
+ return nil, false, err
+ }
+ err = os.MkdirAll(filepath.Dir(outputPath), 0755)
+ if err != nil {
+ return nil, false, err
+ }
+ err = ms.WritePath(outputPath, out)
+ if err != nil {
+ return nil, false, err
+ }
+ dur := time.Since(start)
+ ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), dur)
+ return svg, true, nil
+ case PDF:
+ pageMap := buildBoardIDToIndex(diagram, nil, nil)
+ path := []pdf.BoardTitle{
+ {Name: "root", BoardID: "root"},
+ }
+ pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, path, pageMap)
if err != nil {
return pdf, false, err
}
dur := time.Since(start)
ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), dur)
return pdf, true, nil
- } else {
+ case PPTX:
+ var username string
+ if user, err := user.Current(); err == nil {
+ username = user.Username
+ }
+ description := "Presentation generated with D2 - https://d2lang.com/"
+ rootName := getFileName(outputPath)
+ // version must be only numbers to avoid issues with PowerPoint
+ p := pptx.NewPresentation(rootName, description, rootName, username, version.OnlyNumbers())
+
+ boardIdToIndex := buildBoardIDToIndex(diagram, nil, nil)
+ path := []pptx.BoardTitle{
+ {Name: "root", BoardID: "root", LinkToSlide: boardIdToIndex["root"] + 1},
+ }
+ svg, err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, outputPath, page, diagram, path, boardIdToIndex)
+ if err != nil {
+ return nil, false, err
+ }
+ err = p.SaveTo(outputPath)
+ if err != nil {
+ return nil, false, err
+ }
+ dur := time.Since(start)
+ ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), dur)
+ return svg, true, nil
+ default:
compileDur := time.Since(start)
if animateInterval <= 0 {
// Rename all the "root.layers.x" to the paths that the boards get output to
@@ -364,28 +423,34 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
if err != nil {
return nil, false, err
}
- relink(diagram, linkToOutput)
+ err = relink("root", diagram, linkToOutput)
+ if err != nil {
+ return nil, false, err
+ }
}
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil {
return nil, false, err
}
- out := boards[0]
- if animateInterval > 0 {
- out, err = d2animate.Wrap(diagram, boards, renderOpts, int(animateInterval))
- if err != nil {
- return nil, false, err
+ var out []byte
+ if len(boards) > 0 {
+ out = boards[0]
+ if animateInterval > 0 {
+ out, err = d2animate.Wrap(diagram, boards, renderOpts, int(animateInterval))
+ if err != nil {
+ return nil, false, err
+ }
+ err = os.MkdirAll(filepath.Dir(outputPath), 0755)
+ if err != nil {
+ return nil, false, err
+ }
+ err = ms.WritePath(outputPath, out)
+ if err != nil {
+ return nil, false, err
+ }
+ ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), time.Since(start))
}
- err = os.MkdirAll(filepath.Dir(outputPath), 0755)
- if err != nil {
- return nil, false, err
- }
- err = ms.WritePath(outputPath, out)
- if err != nil {
- return nil, false, err
- }
- ms.Log.Success.Printf("successfully compiled %s to %s in %s", ms.HumanPath(inputPath), ms.HumanPath(outputPath), time.Since(start))
}
return out, true, nil
}
@@ -462,26 +527,40 @@ func resolveLinks(currDiagramPath, outputPath string, diagram *d2target.Diagram)
return linkToOutput, nil
}
-func relink(d *d2target.Diagram, linkToOutput map[string]string) {
+func relink(currDiagramPath string, d *d2target.Diagram, linkToOutput map[string]string) error {
for i, shape := range d.Shapes {
if shape.Link != "" {
for k, v := range linkToOutput {
if shape.Link == k {
- d.Shapes[i].Link = v
+ rel, err := filepath.Rel(filepath.Dir(linkToOutput[currDiagramPath]), v)
+ if err != nil {
+ return err
+ }
+ d.Shapes[i].Link = rel
break
}
}
}
}
for _, board := range d.Layers {
- relink(board, linkToOutput)
+ err := relink(strings.Join([]string{currDiagramPath, "layers", board.Name}, "."), board, linkToOutput)
+ if err != nil {
+ return err
+ }
}
for _, board := range d.Scenarios {
- relink(board, linkToOutput)
+ err := relink(strings.Join([]string{currDiagramPath, "scenarios", board.Name}, "."), board, linkToOutput)
+ if err != nil {
+ return err
+ }
}
for _, board := range d.Steps {
- relink(board, linkToOutput)
+ err := relink(strings.Join([]string{currDiagramPath, "steps", board.Name}, "."), board, linkToOutput)
+ if err != nil {
+ return err
+ }
}
+ return nil
}
func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) {
@@ -569,7 +648,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
}
func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) {
- toPNG := filepath.Ext(outputPath) == ".png"
+ toPNG := getExportExtension(outputPath) == PNG
svg, err := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: opts.Pad,
Sketch: opts.Sketch,
@@ -638,25 +717,13 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
return svg, nil
}
-func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, pdf *pdflib.GoFPDF, boardPath []string, pageMap map[string]int) (svg []byte, err error) {
+func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, doc *pdf.GoFPDF, boardPath []pdf.BoardTitle, pageMap map[string]int) (svg []byte, err error) {
var isRoot bool
- if pdf == nil {
- pdf = pdflib.Init()
+ if doc == nil {
+ doc = pdf.Init()
isRoot = true
}
- var currBoardPath []string
- // Root board doesn't have a name, so we use the output filename
- if diagram.Name == "" {
- ext := filepath.Ext(outputPath)
- trimmedPath := strings.TrimSuffix(outputPath, ext)
- splitPath := strings.Split(trimmedPath, "/")
- rootName := splitPath[len(splitPath)-1]
- currBoardPath = append(boardPath, rootName)
- } else {
- currBoardPath = append(boardPath, diagram.Name)
- }
-
if !diagram.IsFolderOnly {
rootFill := diagram.Root.Fill
// gofpdf will print the png img with a slight filter
@@ -700,33 +767,166 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
if err != nil {
return svg, err
}
- err = pdf.AddPDFPage(pngImg, currBoardPath, opts.ThemeID, rootFill, diagram.Shapes, int64(opts.Pad), viewboxX, viewboxY, pageMap)
+ err = doc.AddPDFPage(pngImg, boardPath, opts.ThemeID, rootFill, diagram.Shapes, int64(opts.Pad), viewboxX, viewboxY, pageMap)
if err != nil {
return svg, err
}
}
for _, dl := range diagram.Layers {
- _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, pdf, currBoardPath, pageMap)
+ path := append(boardPath, pdf.BoardTitle{
+ Name: dl.Name,
+ BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, "."),
+ })
+ _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap)
if err != nil {
return nil, err
}
}
for _, dl := range diagram.Scenarios {
- _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, pdf, currBoardPath, pageMap)
+ path := append(boardPath, pdf.BoardTitle{
+ Name: dl.Name,
+ BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, "."),
+ })
+ _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap)
if err != nil {
return nil, err
}
}
for _, dl := range diagram.Steps {
- _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, pdf, currBoardPath, pageMap)
+ path := append(boardPath, pdf.BoardTitle{
+ Name: dl.Name,
+ BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, "."),
+ })
+ _, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap)
if err != nil {
return nil, err
}
}
if isRoot {
- err := pdf.Export(outputPath)
+ err := doc.Export(outputPath)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return svg, nil
+}
+
+func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []pptx.BoardTitle, boardIDToIndex map[string]int) ([]byte, error) {
+ var svg []byte
+ if !diagram.IsFolderOnly {
+ // gofpdf will print the png img with a slight filter
+ // make the bg fill within the png transparent so that the pdf bg fill is the only bg color present
+ diagram.Root.Fill = "transparent"
+
+ var err error
+ svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{
+ Pad: opts.Pad,
+ Sketch: opts.Sketch,
+ Center: opts.Center,
+ SetDimensions: true,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ svg, err = plugin.PostProcess(ctx, svg)
+ if err != nil {
+ return nil, err
+ }
+
+ svg, bundleErr := imgbundler.BundleLocal(ctx, ms, svg)
+ svg, bundleErr2 := imgbundler.BundleRemote(ctx, ms, svg)
+ bundleErr = multierr.Combine(bundleErr, bundleErr2)
+ if bundleErr != nil {
+ return nil, bundleErr
+ }
+
+ svg = appendix.Append(diagram, ruler, svg)
+
+ pngImg, err := png.ConvertSVG(ms, page, svg)
+ if err != nil {
+ return nil, err
+ }
+
+ slide, err := presentation.AddSlide(pngImg, boardPath)
+ if err != nil {
+ return nil, err
+ }
+
+ viewboxSlice := appendix.FindViewboxSlice(svg)
+ viewboxX, err := strconv.ParseFloat(viewboxSlice[0], 64)
+ if err != nil {
+ return nil, err
+ }
+ viewboxY, err := strconv.ParseFloat(viewboxSlice[1], 64)
+ if err != nil {
+ return nil, err
+ }
+
+ // Draw links
+ for _, shape := range diagram.Shapes {
+ if shape.Link == "" {
+ continue
+ }
+
+ linkX := png.SCALE * (float64(shape.Pos.X) - viewboxX - float64(shape.StrokeWidth))
+ linkY := png.SCALE * (float64(shape.Pos.Y) - viewboxY - float64(shape.StrokeWidth))
+ linkWidth := png.SCALE * (float64(shape.Width) + float64(shape.StrokeWidth*2))
+ linkHeight := png.SCALE * (float64(shape.Height) + float64(shape.StrokeWidth*2))
+ link := &pptx.Link{
+ Left: int(linkX),
+ Top: int(linkY),
+ Width: int(linkWidth),
+ Height: int(linkHeight),
+ Tooltip: shape.Link,
+ }
+ slide.AddLink(link)
+ key, err := d2parser.ParseKey(shape.Link)
+ if err != nil || key.Path[0].Unbox().ScalarString() != "root" {
+ // External link
+ link.ExternalUrl = shape.Link
+ } else if pageNum, ok := boardIDToIndex[shape.Link]; ok {
+ // Internal link
+ link.SlideIndex = pageNum + 1
+ }
+ }
+ }
+
+ for _, dl := range diagram.Layers {
+ boardID := strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, ".")
+ path := append(boardPath, pptx.BoardTitle{
+ Name: dl.Name,
+ BoardID: boardID,
+ LinkToSlide: boardIDToIndex[boardID] + 1,
+ })
+ _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex)
+ if err != nil {
+ return nil, err
+ }
+ }
+ for _, dl := range diagram.Scenarios {
+ boardID := strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, ".")
+ path := append(boardPath, pptx.BoardTitle{
+ Name: dl.Name,
+ BoardID: boardID,
+ LinkToSlide: boardIDToIndex[boardID] + 1,
+ })
+ _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex)
+ if err != nil {
+ return nil, err
+ }
+ }
+ for _, dl := range diagram.Steps {
+ boardID := strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, ".")
+ path := append(boardPath, pptx.BoardTitle{
+ Name: dl.Name,
+ BoardID: boardID,
+ LinkToSlide: boardIDToIndex[boardID] + 1,
+ })
+ _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex)
if err != nil {
return nil, err
}
@@ -745,6 +945,11 @@ func renameExt(fp string, newExt string) string {
}
}
+func getFileName(path string) string {
+ ext := filepath.Ext(path)
+ return strings.TrimSuffix(filepath.Base(path), ext)
+}
+
// TODO: remove after removing slog
func DiscardSlog(ctx context.Context) context.Context {
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
@@ -785,14 +990,15 @@ func loadFont(ms *xmain.State, path string) ([]byte, error) {
return ttf, nil
}
-func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold string) (*d2fonts.FontFamily, error) {
- if pathToRegular == "" && pathToItalic == "" && pathToBold == "" {
+func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold, pathToSemibold string) (*d2fonts.FontFamily, error) {
+ if pathToRegular == "" && pathToItalic == "" && pathToBold == "" && pathToSemibold == "" {
return nil, nil
}
var regularTTF []byte
var italicTTF []byte
var boldTTF []byte
+ var semiboldTTF []byte
var err error
if pathToRegular != "" {
@@ -813,6 +1019,99 @@ func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold string)
return nil, err
}
}
+ if pathToSemibold != "" {
+ semiboldTTF, err = loadFont(ms, pathToSemibold)
+ if err != nil {
+ return nil, err
+ }
+ }
- return d2fonts.AddFontFamily("custom", regularTTF, italicTTF, boldTTF)
+ return d2fonts.AddFontFamily("custom", regularTTF, italicTTF, boldTTF, semiboldTTF)
+}
+
+const LAYERS = "layers"
+const STEPS = "steps"
+const SCENARIOS = "scenarios"
+
+// buildBoardIDToIndex returns a map from board path to page int
+// To map correctly, it must follow the same traversal of pdf/pptx building
+func buildBoardIDToIndex(diagram *d2target.Diagram, dictionary map[string]int, path []string) map[string]int {
+ newPath := append(path, diagram.Name)
+ if dictionary == nil {
+ dictionary = map[string]int{}
+ newPath[0] = "root"
+ }
+
+ key := strings.Join(newPath, ".")
+ dictionary[key] = len(dictionary)
+
+ for _, dl := range diagram.Layers {
+ buildBoardIDToIndex(dl, dictionary, append(newPath, LAYERS))
+ }
+ for _, dl := range diagram.Scenarios {
+ buildBoardIDToIndex(dl, dictionary, append(newPath, SCENARIOS))
+ }
+ for _, dl := range diagram.Steps {
+ buildBoardIDToIndex(dl, dictionary, append(newPath, STEPS))
+ }
+
+ return dictionary
+}
+
+func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) {
+ if !diagram.IsFolderOnly {
+ svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{
+ Pad: opts.Pad,
+ Sketch: opts.Sketch,
+ Center: opts.Center,
+ SetDimensions: true,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svg, err = plugin.PostProcess(ctx, svg)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svg, bundleErr := imgbundler.BundleLocal(ctx, ms, svg)
+ svg, bundleErr2 := imgbundler.BundleRemote(ctx, ms, svg)
+ bundleErr = multierr.Combine(bundleErr, bundleErr2)
+ if bundleErr != nil {
+ return nil, nil, bundleErr
+ }
+
+ svg = appendix.Append(diagram, ruler, svg)
+
+ pngImg, err := png.ConvertSVG(ms, page, svg)
+ if err != nil {
+ return nil, nil, err
+ }
+ pngs = append(pngs, pngImg)
+ }
+
+ for _, dl := range diagram.Layers {
+ _, layerPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl)
+ if err != nil {
+ return nil, nil, err
+ }
+ pngs = append(pngs, layerPNGs...)
+ }
+ for _, dl := range diagram.Scenarios {
+ _, scenarioPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl)
+ if err != nil {
+ return nil, nil, err
+ }
+ pngs = append(pngs, scenarioPNGs...)
+ }
+ for _, dl := range diagram.Steps {
+ _, stepsPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl)
+ if err != nil {
+ return nil, nil, err
+ }
+ pngs = append(pngs, stepsPNGs...)
+ }
+
+ return svg, pngs, nil
}
diff --git a/d2compiler/compile.go b/d2compiler/compile.go
index 28288795d..4b07a6684 100644
--- a/d2compiler/compile.go
+++ b/d2compiler/compile.go
@@ -73,6 +73,7 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
c.validateKeys(g.Root, ir)
}
c.validateNear(g)
+ c.validateEdges(g)
c.compileBoardsField(g, ir, "layers")
c.compileBoardsField(g, ir, "scenarios")
@@ -115,8 +116,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
}
type compiler struct {
- inEdgeGroup bool
- err d2parser.ParseError
+ err d2parser.ParseError
}
func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
@@ -124,6 +124,18 @@ func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
}
func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {
+ class := m.GetField("class")
+ if class != nil {
+ className := class.Primary()
+ if className == nil {
+ c.errorf(class.LastRef().AST(), "class missing value")
+ } else {
+ classMap := m.GetClassMap(className.String())
+ if classMap != nil {
+ c.compileMap(obj, classMap)
+ }
+ }
+ }
shape := m.GetField("shape")
if shape != nil {
c.compileField(obj, shape)
@@ -138,7 +150,7 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {
c.compileField(obj, f)
}
- switch obj.Attributes.Shape.Value {
+ switch obj.Shape.Value {
case d2target.ShapeClass:
c.compileClass(obj)
case d2target.ShapeSQLTable:
@@ -158,26 +170,46 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
return
}
_, isReserved := d2graph.SimpleReservedKeywords[keyword]
- if isReserved {
- c.compileReserved(obj.Attributes, f)
+ if f.Name == "classes" {
+ if f.Map() != nil {
+ if len(f.Map().Edges) > 0 {
+ c.errorf(f.Map().Edges[0].LastRef().AST(), "classes cannot contain an edge")
+ }
+ for _, classesField := range f.Map().Fields {
+ if classesField.Map() == nil {
+ continue
+ }
+ for _, cf := range classesField.Map().Fields {
+ if _, ok := d2graph.ReservedKeywords[cf.Name]; !ok {
+ c.errorf(cf.LastRef().AST(), "%s is an invalid class field, must be reserved keyword", cf.Name)
+ }
+ if cf.Name == "class" {
+ c.errorf(cf.LastRef().AST(), `"class" cannot appear within "classes"`)
+ }
+ }
+ }
+ }
+ return
+ } else if isReserved {
+ c.compileReserved(&obj.Attributes, f)
return
} else if f.Name == "style" {
if f.Map() == nil {
return
}
- c.compileStyle(obj.Attributes, f.Map())
- if obj.Attributes.Style.Animated != nil {
- c.errorf(obj.Attributes.Style.Animated.MapKey, `key "animated" can only be applied to edges`)
+ c.compileStyle(&obj.Attributes, f.Map())
+ if obj.Style.Animated != nil {
+ c.errorf(obj.Style.Animated.MapKey, `key "animated" can only be applied to edges`)
}
return
}
if obj.Parent != nil {
- if obj.Parent.Attributes.Shape.Value == d2target.ShapeSQLTable {
+ if obj.Parent.Shape.Value == d2target.ShapeSQLTable {
c.errorf(f.LastRef().AST(), "sql_table columns cannot have children")
return
}
- if obj.Parent.Attributes.Shape.Value == d2target.ShapeClass {
+ if obj.Parent.Shape.Value == d2target.ShapeClass {
c.errorf(f.LastRef().AST(), "class fields cannot have children")
return
}
@@ -185,14 +217,14 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
obj = obj.EnsureChild(d2graphIDA([]string{f.Name}))
if f.Primary() != nil {
- c.compileLabel(obj.Attributes, f)
+ c.compileLabel(&obj.Attributes, f)
}
if f.Map() != nil {
c.compileMap(obj, f.Map())
}
- if obj.Attributes.Label.MapKey == nil {
- obj.Attributes.Label.MapKey = f.LastPrimaryKey()
+ if obj.Label.MapKey == nil {
+ obj.Label.MapKey = f.LastPrimaryKey()
}
for _, fr := range f.References {
if fr.Primary() {
@@ -218,7 +250,7 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
scalar := f.Primary().Value
switch scalar := scalar.(type) {
case *d2ast.Null:
- // TODO: Delete instaed.
+ // TODO: Delete instead.
attrs.Label.Value = scalar.ScalarString()
case *d2ast.BlockString:
attrs.Language = scalar.Tag
@@ -305,18 +337,18 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
c.errorf(scalar, "non-integer width %#v: %s", scalar.ScalarString(), err)
return
}
- attrs.Width = &d2graph.Scalar{}
- attrs.Width.Value = scalar.ScalarString()
- attrs.Width.MapKey = f.LastPrimaryKey()
+ attrs.WidthAttr = &d2graph.Scalar{}
+ attrs.WidthAttr.Value = scalar.ScalarString()
+ attrs.WidthAttr.MapKey = f.LastPrimaryKey()
case "height":
_, err := strconv.Atoi(scalar.ScalarString())
if err != nil {
c.errorf(scalar, "non-integer height %#v: %s", scalar.ScalarString(), err)
return
}
- attrs.Height = &d2graph.Scalar{}
- attrs.Height.Value = scalar.ScalarString()
- attrs.Height.MapKey = f.LastPrimaryKey()
+ attrs.HeightAttr = &d2graph.Scalar{}
+ attrs.HeightAttr.Value = scalar.ScalarString()
+ attrs.HeightAttr.MapKey = f.LastPrimaryKey()
case "top":
v, err := strconv.Atoi(scalar.ScalarString())
if err != nil {
@@ -362,6 +394,74 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
}
attrs.Constraint.Value = scalar.ScalarString()
attrs.Constraint.MapKey = f.LastPrimaryKey()
+ case "grid-rows":
+ v, err := strconv.Atoi(scalar.ScalarString())
+ if err != nil {
+ c.errorf(scalar, "non-integer grid-rows %#v: %s", scalar.ScalarString(), err)
+ return
+ }
+ if v <= 0 {
+ c.errorf(scalar, "grid-rows must be a positive integer: %#v", scalar.ScalarString())
+ return
+ }
+ attrs.GridRows = &d2graph.Scalar{}
+ attrs.GridRows.Value = scalar.ScalarString()
+ attrs.GridRows.MapKey = f.LastPrimaryKey()
+ case "grid-columns":
+ v, err := strconv.Atoi(scalar.ScalarString())
+ if err != nil {
+ c.errorf(scalar, "non-integer grid-columns %#v: %s", scalar.ScalarString(), err)
+ return
+ }
+ if v <= 0 {
+ c.errorf(scalar, "grid-columns must be a positive integer: %#v", scalar.ScalarString())
+ return
+ }
+ attrs.GridColumns = &d2graph.Scalar{}
+ attrs.GridColumns.Value = scalar.ScalarString()
+ attrs.GridColumns.MapKey = f.LastPrimaryKey()
+ case "grid-gap":
+ v, err := strconv.Atoi(scalar.ScalarString())
+ if err != nil {
+ c.errorf(scalar, "non-integer grid-gap %#v: %s", scalar.ScalarString(), err)
+ return
+ }
+ if v < 0 {
+ c.errorf(scalar, "grid-gap must be a non-negative integer: %#v", scalar.ScalarString())
+ return
+ }
+ attrs.GridGap = &d2graph.Scalar{}
+ attrs.GridGap.Value = scalar.ScalarString()
+ attrs.GridGap.MapKey = f.LastPrimaryKey()
+ case "vertical-gap":
+ v, err := strconv.Atoi(scalar.ScalarString())
+ if err != nil {
+ c.errorf(scalar, "non-integer vertical-gap %#v: %s", scalar.ScalarString(), err)
+ return
+ }
+ if v < 0 {
+ c.errorf(scalar, "vertical-gap must be a non-negative integer: %#v", scalar.ScalarString())
+ return
+ }
+ attrs.VerticalGap = &d2graph.Scalar{}
+ attrs.VerticalGap.Value = scalar.ScalarString()
+ attrs.VerticalGap.MapKey = f.LastPrimaryKey()
+ case "horizontal-gap":
+ v, err := strconv.Atoi(scalar.ScalarString())
+ if err != nil {
+ c.errorf(scalar, "non-integer horizontal-gap %#v: %s", scalar.ScalarString(), err)
+ return
+ }
+ if v < 0 {
+ c.errorf(scalar, "horizontal-gap must be a non-negative integer: %#v", scalar.ScalarString())
+ return
+ }
+ attrs.HorizontalGap = &d2graph.Scalar{}
+ attrs.HorizontalGap.Value = scalar.ScalarString()
+ attrs.HorizontalGap.MapKey = f.LastPrimaryKey()
+ case "class":
+ attrs.Classes = append(attrs.Classes, scalar.ScalarString())
+ case "classes":
}
if attrs.Link != nil && attrs.Tooltip != nil {
@@ -430,15 +530,17 @@ func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) {
case "filled":
attrs.Style.Filled = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "width":
- attrs.Width = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ attrs.WidthAttr = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "height":
- attrs.Height = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ attrs.HeightAttr = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "top":
attrs.Top = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "left":
attrs.Left = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "double-border":
attrs.Style.DoubleBorder = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ case "text-transform":
+ attrs.Style.TextTransform = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
}
}
@@ -450,20 +552,13 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
}
if e.Primary() != nil {
- c.compileLabel(edge.Attributes, e)
+ c.compileLabel(&edge.Attributes, e)
}
if e.Map() != nil {
- for _, f := range e.Map().Fields {
- _, ok := d2graph.ReservedKeywords[f.Name]
- if !ok {
- c.errorf(f.References[0].AST(), `edge map keys must be reserved keywords`)
- continue
- }
- c.compileEdgeField(edge, f)
- }
+ c.compileEdgeMap(edge, e.Map())
}
- edge.Attributes.Label.MapKey = e.LastPrimaryKey()
+ edge.Label.MapKey = e.LastPrimaryKey()
for _, er := range e.References {
scopeObjIDA := d2ir.BoardIDA(er.Context.ScopeMap)
scopeObj := edge.Src.Graph.Root.EnsureChildIDVal(scopeObjIDA)
@@ -477,6 +572,29 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
}
}
+func (c *compiler) compileEdgeMap(edge *d2graph.Edge, m *d2ir.Map) {
+ class := m.GetField("class")
+ if class != nil {
+ className := class.Primary()
+ if className == nil {
+ c.errorf(class.LastRef().AST(), "class missing value")
+ } else {
+ classMap := m.GetClassMap(className.String())
+ if classMap != nil {
+ c.compileEdgeMap(edge, classMap)
+ }
+ }
+ }
+ for _, f := range m.Fields {
+ _, ok := d2graph.ReservedKeywords[f.Name]
+ if !ok {
+ c.errorf(f.References[0].AST(), `edge map keys must be reserved keywords`)
+ continue
+ }
+ c.compileEdgeField(edge, f)
+ }
+}
+
func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) {
keyword := strings.ToLower(f.Name)
_, isStyleReserved := d2graph.StyleKeywords[keyword]
@@ -486,13 +604,13 @@ func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) {
}
_, isReserved := d2graph.SimpleReservedKeywords[keyword]
if isReserved {
- c.compileReserved(edge.Attributes, f)
+ c.compileReserved(&edge.Attributes, f)
return
} else if f.Name == "style" {
if f.Map() == nil {
return
}
- c.compileStyle(edge.Attributes, f.Map())
+ c.compileStyle(&edge.Attributes, f.Map())
return
}
@@ -551,7 +669,7 @@ var FullToShortLanguageAliases map[string]string
func (c *compiler) compileClass(obj *d2graph.Object) {
obj.Class = &d2target.Class{}
for _, f := range obj.ChildrenArray {
- visiblity := "public"
+ visibility := "public"
name := f.IDVal
// See https://www.uml-diagrams.org/visibility.html
if name != "" {
@@ -559,35 +677,35 @@ func (c *compiler) compileClass(obj *d2graph.Object) {
case '+':
name = name[1:]
case '-':
- visiblity = "private"
+ visibility = "private"
name = name[1:]
case '#':
- visiblity = "protected"
+ visibility = "protected"
name = name[1:]
}
}
if !strings.Contains(f.IDVal, "(") {
- typ := f.Attributes.Label.Value
+ typ := f.Label.Value
if typ == f.IDVal {
typ = ""
}
obj.Class.Fields = append(obj.Class.Fields, d2target.ClassField{
Name: name,
Type: typ,
- Visibility: visiblity,
+ Visibility: visibility,
})
} else {
// TODO: Not great, AST should easily allow specifying alternate primary field
// as an explicit label should change the name.
- returnType := f.Attributes.Label.Value
+ returnType := f.Label.Value
if returnType == f.IDVal {
returnType = "void"
}
obj.Class.Methods = append(obj.Class.Methods, d2target.ClassMethod{
Name: name,
Return: returnType,
- Visibility: visiblity,
+ Visibility: visibility,
})
}
}
@@ -607,7 +725,7 @@ func (c *compiler) compileClass(obj *d2graph.Object) {
func (c *compiler) compileSQLTable(obj *d2graph.Object) {
obj.SQLTable = &d2target.SQLTable{}
for _, col := range obj.ChildrenArray {
- typ := col.Attributes.Label.Value
+ typ := col.Label.Value
if typ == col.IDVal {
// Not great, AST should easily allow specifying alternate primary field
// as an explicit label should change the name.
@@ -617,8 +735,8 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) {
Name: d2target.Text{Label: col.IDVal},
Type: d2target.Text{Label: typ},
}
- if col.Attributes.Constraint.Value != "" {
- d2Col.Constraint = col.Attributes.Constraint.Value
+ if col.Constraint.Value != "" {
+ d2Col.Constraint = col.Constraint.Value
}
obj.SQLTable.Columns = append(obj.SQLTable.Columns, d2Col)
}
@@ -648,41 +766,48 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
keyword := strings.ToLower(f.Name)
_, isReserved := d2graph.ReservedKeywords[keyword]
if isReserved {
- switch obj.Attributes.Shape.Value {
+ switch obj.Shape.Value {
case d2target.ShapeCircle, d2target.ShapeSquare:
- checkEqual := (keyword == "width" && obj.Attributes.Height != nil) || (keyword == "height" && obj.Attributes.Width != nil)
- if checkEqual && obj.Attributes.Width.Value != obj.Attributes.Height.Value {
- c.errorf(f.LastPrimaryKey(), "width and height must be equal for %s shapes", obj.Attributes.Shape.Value)
+ checkEqual := (keyword == "width" && obj.HeightAttr != nil) || (keyword == "height" && obj.WidthAttr != nil)
+ if checkEqual && obj.WidthAttr.Value != obj.HeightAttr.Value {
+ c.errorf(f.LastPrimaryKey(), "width and height must be equal for %s shapes", obj.Shape.Value)
}
}
switch f.Name {
case "style":
- if obj.Attributes.Style.ThreeDee != nil {
- if !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeHexagon) {
- c.errorf(obj.Attributes.Style.ThreeDee.MapKey, `key "3d" can only be applied to squares, rectangles, and hexagons`)
+ if obj.Style.ThreeDee != nil {
+ if !strings.EqualFold(obj.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeHexagon) {
+ c.errorf(obj.Style.ThreeDee.MapKey, `key "3d" can only be applied to squares, rectangles, and hexagons`)
}
}
- if obj.Attributes.Style.DoubleBorder != nil {
- if obj.Attributes.Shape.Value != "" && obj.Attributes.Shape.Value != d2target.ShapeSquare && obj.Attributes.Shape.Value != d2target.ShapeRectangle && obj.Attributes.Shape.Value != d2target.ShapeCircle && obj.Attributes.Shape.Value != d2target.ShapeOval {
- c.errorf(obj.Attributes.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`)
+ if obj.Style.DoubleBorder != nil {
+ if obj.Shape.Value != "" && obj.Shape.Value != d2target.ShapeSquare && obj.Shape.Value != d2target.ShapeRectangle && obj.Shape.Value != d2target.ShapeCircle && obj.Shape.Value != d2target.ShapeOval {
+ c.errorf(obj.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`)
}
}
case "shape":
- if obj.Attributes.Shape.Value == d2target.ShapeImage && obj.Attributes.Icon == nil {
+ if obj.Shape.Value == d2target.ShapeImage && obj.Icon == nil {
c.errorf(f.LastPrimaryKey(), `image shape must include an "icon" field`)
}
- in := d2target.IsShape(obj.Attributes.Shape.Value)
- _, arrowheadIn := d2target.Arrowheads[obj.Attributes.Shape.Value]
+ in := d2target.IsShape(obj.Shape.Value)
+ _, arrowheadIn := d2target.Arrowheads[obj.Shape.Value]
if !in && arrowheadIn {
- c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Attributes.Shape.Value))
+ c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Shape.Value))
+ }
+ case "grid-rows", "grid-columns", "grid-gap", "vertical-gap", "horizontal-gap":
+ for _, child := range obj.ChildrenArray {
+ if child.IsContainer() {
+ c.errorf(f.LastPrimaryKey(),
+ fmt.Sprintf(`%#v can only be used on containers with one level of nesting right now. (%#v has nested %#v)`, keyword, child.AbsID(), child.ChildrenArray[0].ID))
+ }
}
}
return
}
- if obj.Attributes.Shape.Value == d2target.ShapeImage {
+ if obj.Shape.Value == d2target.ShapeImage {
c.errorf(f.LastRef().AST(), "image shapes cannot have children.")
return
}
@@ -695,9 +820,9 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
func (c *compiler) validateNear(g *d2graph.Graph) {
for _, obj := range g.Objects {
- if obj.Attributes.NearKey != nil {
- nearObj, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey))
- _, isConst := d2graph.NearConstants[d2graph.Key(obj.Attributes.NearKey)[0]]
+ if obj.NearKey != nil {
+ nearObj, isKey := g.Root.HasChild(d2graph.Key(obj.NearKey))
+ _, isConst := d2graph.NearConstants[d2graph.Key(obj.NearKey)[0]]
if isKey {
// Doesn't make sense to set near to an ancestor or descendant
nearIsAncestor := false
@@ -708,7 +833,7 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
}
}
if nearIsAncestor {
- c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an ancestor")
+ c.errorf(obj.NearKey, "near keys cannot be set to an ancestor")
continue
}
nearIsDescendant := false
@@ -719,55 +844,72 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
}
}
if nearIsDescendant {
- c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an descendant")
+ c.errorf(obj.NearKey, "near keys cannot be set to an descendant")
continue
}
if nearObj.OuterSequenceDiagram() != nil {
- c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an object within sequence diagrams")
+ c.errorf(obj.NearKey, "near keys cannot be set to an object within sequence diagrams")
continue
}
- if nearObj.Attributes.NearKey != nil {
- _, nearObjNearIsConst := d2graph.NearConstants[d2graph.Key(nearObj.Attributes.NearKey)[0]]
+ if nearObj.NearKey != nil {
+ _, nearObjNearIsConst := d2graph.NearConstants[d2graph.Key(nearObj.NearKey)[0]]
if nearObjNearIsConst {
- c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an object with a constant near key")
+ c.errorf(obj.NearKey, "near keys cannot be set to an object with a constant near key")
continue
}
}
} else if isConst {
- is := false
- for _, e := range g.Edges {
- if e.Src == obj || e.Dst == obj {
- is = true
- break
- }
- }
- if is {
- c.errorf(obj.Attributes.NearKey, "constant near keys cannot be set on connected shapes")
- continue
- }
if obj.Parent != g.Root {
- c.errorf(obj.Attributes.NearKey, "constant near keys can only be set on root level shapes")
- continue
- }
- if len(obj.ChildrenArray) > 0 {
- c.errorf(obj.Attributes.NearKey, "constant near keys cannot be set on shapes with children")
+ c.errorf(obj.NearKey, "constant near keys can only be set on root level shapes")
continue
}
} else {
- c.errorf(obj.Attributes.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.Attributes.NearKey), strings.Join(d2graph.NearConstantsArray, ", "))
+ c.errorf(obj.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.NearKey), strings.Join(d2graph.NearConstantsArray, ", "))
continue
}
}
}
+
+ for _, edge := range g.Edges {
+ srcNearContainer := edge.Src.OuterNearContainer()
+ dstNearContainer := edge.Dst.OuterNearContainer()
+
+ var isSrcNearConst, isDstNearConst bool
+
+ if srcNearContainer != nil {
+ _, isSrcNearConst = d2graph.NearConstants[d2graph.Key(srcNearContainer.NearKey)[0]]
+ }
+ if dstNearContainer != nil {
+ _, isDstNearConst = d2graph.NearConstants[d2graph.Key(dstNearContainer.NearKey)[0]]
+ }
+
+ if (isSrcNearConst || isDstNearConst) && srcNearContainer != dstNearContainer {
+ c.errorf(edge.References[0].Edge, "cannot connect objects from within a container, that has near constant set, to objects outside that container")
+ }
+ }
+
+}
+
+func (c *compiler) validateEdges(g *d2graph.Graph) {
+ for _, edge := range g.Edges {
+ if gd := edge.Src.Parent.ClosestGridDiagram(); gd != nil {
+ c.errorf(edge.GetAstEdge(), "edges in grid diagrams are not supported yet")
+ continue
+ }
+ if gd := edge.Dst.Parent.ClosestGridDiagram(); gd != nil {
+ c.errorf(edge.GetAstEdge(), "edges in grid diagrams are not supported yet")
+ continue
+ }
+ }
}
func (c *compiler) validateBoardLinks(g *d2graph.Graph) {
for _, obj := range g.Objects {
- if obj.Attributes.Link == nil {
+ if obj.Link == nil {
continue
}
- linkKey, err := d2parser.ParseKey(obj.Attributes.Link.Value)
+ linkKey, err := d2parser.ParseKey(obj.Link.Value)
if err != nil {
continue
}
@@ -777,7 +919,7 @@ func (c *compiler) validateBoardLinks(g *d2graph.Graph) {
}
if !hasBoard(g.RootBoard(), linkKey.IDA()) {
- c.errorf(obj.Attributes.Link.MapKey, "linked board not found")
+ c.errorf(obj.Link.MapKey, "linked board not found")
continue
}
}
diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go
index 7fc88f41c..0f615566c 100644
--- a/d2compiler/compile_test.go
+++ b/d2compiler/compile_test.go
@@ -43,8 +43,8 @@ x: {
t.Fatalf("expected g.Objects[0].ID to be x: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value != d2target.ShapeCircle {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value to be circle: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value != d2target.ShapeCircle {
+ t.Fatalf("expected g.Objects[0].Shape.Value to be circle: %#v", g.Objects[0].Shape.Value)
}
},
@@ -65,8 +65,8 @@ x: {
t.Fatalf("expected g.Objects[0].ID to be x: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("expected g.Objects[0].Attributes.Style.Opacity.Value to be 0.4: %#v", g.Objects[0].Attributes.Style.Opacity.Value)
+ if g.Objects[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("expected g.Objects[0].Style.Opacity.Value to be 0.4: %#v", g.Objects[0].Style.Opacity.Value)
}
},
@@ -102,14 +102,14 @@ x: {
if g.Objects[0].ID != "hey" {
t.Fatalf("expected g.Objects[0].ID to be 'hey': %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value != d2target.ShapeHexagon {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value to be hexagon: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value != d2target.ShapeHexagon {
+ t.Fatalf("expected g.Objects[0].Shape.Value to be hexagon: %#v", g.Objects[0].Shape.Value)
}
- if g.Objects[0].Attributes.Width.Value != "200" {
- t.Fatalf("expected g.Objects[0].Attributes.Width.Value to be 200: %#v", g.Objects[0].Attributes.Width.Value)
+ if g.Objects[0].WidthAttr.Value != "200" {
+ t.Fatalf("expected g.Objects[0].Width.Value to be 200: %#v", g.Objects[0].WidthAttr.Value)
}
- if g.Objects[0].Attributes.Height.Value != "230" {
- t.Fatalf("expected g.Objects[0].Attributes.Height.Value to be 230: %#v", g.Objects[0].Attributes.Height.Value)
+ if g.Objects[0].HeightAttr.Value != "230" {
+ t.Fatalf("expected g.Objects[0].Height.Value to be 230: %#v", g.Objects[0].HeightAttr.Value)
}
},
},
@@ -121,7 +121,7 @@ x: {
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "200", g.Objects[0].Attributes.Top.Value)
+ tassert.Equal(t, "200", g.Objects[0].Top.Value)
},
},
{
@@ -160,13 +160,13 @@ d2/testdata/d2compiler/TestCompile/equal_dimensions_on_circle.d2:4:2: width and
if g.Objects[0].ID != "hey" {
t.Fatalf("expected ID to be 'hey': %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value != d2target.ShapeCircle {
- t.Fatalf("expected Attributes.Shape.Value to be circle: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value != d2target.ShapeCircle {
+ t.Fatalf("expected Attributes.Shape.Value to be circle: %#v", g.Objects[0].Shape.Value)
}
- if g.Objects[0].Attributes.Width != nil {
- t.Fatalf("expected Attributes.Width to be nil: %#v", g.Objects[0].Attributes.Width)
+ if g.Objects[0].WidthAttr != nil {
+ t.Fatalf("expected Attributes.Width to be nil: %#v", g.Objects[0].WidthAttr)
}
- if g.Objects[0].Attributes.Height == nil {
+ if g.Objects[0].HeightAttr == nil {
t.Fatalf("Attributes.Height is nil")
}
},
@@ -237,7 +237,7 @@ containers: {
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- if g.Objects[0].Attributes.Icon == nil {
+ if g.Objects[0].Icon == nil {
t.Fatal("Attribute icon is nil")
}
},
@@ -326,7 +326,7 @@ containers: {
if len(g.Objects) != 1 {
t.Fatalf("expected 1 objects: %#v", g.Objects)
}
- if g.Objects[0].Attributes.Style.StrokeWidth.Value != "0" {
+ if g.Objects[0].Style.StrokeWidth.Value != "0" {
t.Fatalf("unexpected")
}
},
@@ -442,8 +442,8 @@ y: "But it's real. And if it's real it can be affected ... we may not be able"
if len(g.Root.ChildrenArray) != 2 {
t.Fatalf("expected 2 objects at the root: %#v", len(g.Root.ChildrenArray))
}
- if g.Objects[1].Attributes.Label.Value != "But it's real. And if it's real it can be affected ... we may not be able" {
- t.Fatalf("expected g.Objects[1].Label.Value to be last value: %#v", g.Objects[1].Attributes.Label.Value)
+ if g.Objects[1].Label.Value != "But it's real. And if it's real it can be affected ... we may not be able" {
+ t.Fatalf("expected g.Objects[1].Label.Value to be last value: %#v", g.Objects[1].Label.Value)
}
},
},
@@ -470,8 +470,8 @@ x: {
if len(g.Root.ChildrenArray) != 2 {
t.Fatalf("expected 2 objects at the root: %#v", len(g.Root.ChildrenArray))
}
- if g.Objects[0].Attributes.Label.Value != "All we are given is possibilities -- to make ourselves one thing or another." {
- t.Fatalf("expected g.Objects[0].Label.Value to be last value: %#v", g.Objects[0].Attributes.Label.Value)
+ if g.Objects[0].Label.Value != "All we are given is possibilities -- to make ourselves one thing or another." {
+ t.Fatalf("expected g.Objects[0].Label.Value to be last value: %#v", g.Objects[0].Label.Value)
}
},
},
@@ -626,11 +626,11 @@ x: {
if g.Edges[1].Dst.ID != "b" {
t.Fatalf("expected g.Edges[1].Dst.ID to be b: %#v", g.Edges[1])
}
- if g.Edges[0].Attributes.Label.Value != "Can you imagine how life could be improved if we could do away with" {
- t.Fatalf("unexpected g.Edges[0].Label: %#v", g.Edges[0].Attributes.Label)
+ if g.Edges[0].Label.Value != "Can you imagine how life could be improved if we could do away with" {
+ t.Fatalf("unexpected g.Edges[0].Label: %#v", g.Edges[0].Label)
}
- if g.Edges[1].Attributes.Label.Value != "Well, it's garish, ugly, and derelicts have used it for a toilet." {
- t.Fatalf("unexpected g.Edges[1].Label: %#v", g.Edges[1].Attributes.Label)
+ if g.Edges[1].Label.Value != "Well, it's garish, ugly, and derelicts have used it for a toilet." {
+ t.Fatalf("unexpected g.Edges[1].Label: %#v", g.Edges[1].Label)
}
},
},
@@ -656,8 +656,8 @@ x: {
if g.Edges[0].Dst.ID != "b" {
t.Fatalf("expected g.Edges[0].Dst.ID to be b: %#v", g.Edges[0])
}
- if g.Edges[0].Attributes.Label.Value != "Well, it's garish, ugly, and derelicts have used it for a toilet." {
- t.Fatalf("unexpected g.Edges[0].Label: %#v", g.Edges[0].Attributes.Label)
+ if g.Edges[0].Label.Value != "Well, it's garish, ugly, and derelicts have used it for a toilet." {
+ t.Fatalf("unexpected g.Edges[0].Label: %#v", g.Edges[0].Label)
}
},
},
@@ -756,11 +756,11 @@ x -> y -> z: "The kids will love our inflatable slides"
t.Fatalf("expected g.Edges[1].Dst.ID to be y: %#v", g.Edges[1])
}
- if g.Edges[0].Attributes.Label.Value != "The kids will love our inflatable slides" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label: %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "The kids will love our inflatable slides" {
+ t.Fatalf("unexpected g.Edges[0].Label: %#v", g.Edges[0].Label.Value)
}
- if g.Edges[1].Attributes.Label.Value != "The kids will love our inflatable slides" {
- t.Fatalf("unexpected g.Edges[1].Attributes.Label: %#v", g.Edges[1].Attributes.Label.Value)
+ if g.Edges[1].Label.Value != "The kids will love our inflatable slides" {
+ t.Fatalf("unexpected g.Edges[1].Label: %#v", g.Edges[1].Label.Value)
}
},
},
@@ -797,8 +797,8 @@ x -> y: one
if !g.Edges[0].DstArrow {
t.Fatalf("expected g.Edges[0].DstArrow to be true: %#v", g.Edges[0].DstArrow)
}
- if g.Edges[0].Attributes.Label.Value != "two" {
- t.Fatalf("expected g.Edges[0].Attributes.Label to be two: %#v", g.Edges[0].Attributes.Label)
+ if g.Edges[0].Label.Value != "two" {
+ t.Fatalf("expected g.Edges[0].Label to be two: %#v", g.Edges[0].Label)
}
},
},
@@ -840,8 +840,8 @@ b: {
if !g.Edges[0].DstArrow {
t.Fatalf("expected g.Edges[0].DstArrow to be true: %#v", g.Edges[0].DstArrow)
}
- if g.Edges[0].Attributes.Label.Value != "two" {
- t.Fatalf("expected g.Edges[0].Attributes.Label to be two: %#v", g.Edges[0].Attributes.Label)
+ if g.Edges[0].Label.Value != "two" {
+ t.Fatalf("expected g.Edges[0].Label to be two: %#v", g.Edges[0].Label)
}
},
},
@@ -883,8 +883,8 @@ b.(x -> y)[0]: two
if !g.Edges[0].DstArrow {
t.Fatalf("expected g.Edges[0].DstArrow to be true: %#v", g.Edges[0].DstArrow)
}
- if g.Edges[0].Attributes.Label.Value != "two" {
- t.Fatalf("expected g.Edges[0].Attributes.Label to be two: %#v", g.Edges[0].Attributes.Label)
+ if g.Edges[0].Label.Value != "two" {
+ t.Fatalf("expected g.Edges[0].Label to be two: %#v", g.Edges[0].Label)
}
},
},
@@ -936,8 +936,8 @@ x -> y: {
if g.Edges[0].Dst.ID != "y" {
t.Fatalf("expected g.Edges[0].Dst.ID to be y: %#v", g.Edges[0])
}
- if g.Edges[0].Attributes.Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -950,8 +950,8 @@ x -> y: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Label.Value != "asdf" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "asdf" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -972,7 +972,7 @@ x -> y: {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
+ assert.String(t, "", g.Edges[0].Shape.Value)
// Make sure the DSL didn't change. this is a regression test where it did
exp := `x -> y: {
source-arrowhead: {
@@ -1025,9 +1025,9 @@ x -> y: {
assert.String(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value)
assert.String(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value)
assert.String(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Label.Value)
- assert.JSON(t, nil, g.Edges[0].Attributes.Style.Filled)
+ assert.String(t, "", g.Edges[0].Shape.Value)
+ assert.String(t, "", g.Edges[0].Label.Value)
+ assert.JSON(t, nil, g.Edges[0].Style.Filled)
},
},
{
@@ -1044,7 +1044,7 @@ x -> y: {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
+ assert.String(t, "", g.Edges[0].Shape.Value)
},
},
{
@@ -1061,7 +1061,7 @@ x -> y: {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
assert.String(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
+ assert.String(t, "", g.Edges[0].Shape.Value)
},
},
{
@@ -1087,7 +1087,7 @@ x -> y: {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
assert.String(t, "yo", g.Edges[0].SrcArrowhead.Label.Value)
- assert.String(t, "", g.Edges[0].Attributes.Label.Value)
+ assert.String(t, "", g.Edges[0].Label.Value)
},
},
{
@@ -1106,7 +1106,7 @@ x -> y: {
t.Fatalf("expected 2 objects: %#v", g.Objects)
}
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
+ assert.String(t, "", g.Edges[0].Shape.Value)
},
},
{
@@ -1128,7 +1128,7 @@ x -> y: {
}
assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value)
assert.String(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value)
- assert.String(t, "", g.Edges[0].Attributes.Shape.Value)
+ assert.String(t, "", g.Edges[0].Shape.Value)
},
},
{
@@ -1143,8 +1143,8 @@ x -> y: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Animated.Value != "true" {
- t.Fatalf("Edges[0].Attributes.Style.Animated.Value: %#v", g.Edges[0].Attributes.Style.Animated.Value)
+ if g.Edges[0].Style.Animated.Value != "true" {
+ t.Fatalf("Edges[0].Style.Animated.Value: %#v", g.Edges[0].Style.Animated.Value)
}
},
},
@@ -1201,11 +1201,11 @@ x -> y -> z: {
if len(g.Edges) != 2 {
t.Fatalf("expected 2 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
- if g.Edges[1].Attributes.Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[1].Attributes.Label.Value)
+ if g.Edges[1].Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[1].Label.Value)
}
},
},
@@ -1226,8 +1226,8 @@ x -> y
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "Space: the final frontier. These are the voyages of the starship Enterprise." {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1249,8 +1249,8 @@ x -> y: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
},
},
@@ -1270,11 +1270,11 @@ x -> y: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1293,11 +1293,11 @@ x -> y
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1317,11 +1317,11 @@ x -> y
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1342,11 +1342,11 @@ x.(a -> b)[0].style.opacity: 0.4
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1367,11 +1367,11 @@ x: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1396,11 +1396,11 @@ x: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1423,11 +1423,11 @@ x: {
if len(g.Edges) != 1 {
t.Fatalf("expected 1 edge: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Style.Opacity.Value: %#v", g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatalf("unexpected g.Edges[0].Style.Opacity.Value: %#v", g.Edges[0].Style.Opacity.Value)
}
- if g.Edges[0].Attributes.Label.Value != "" {
- t.Fatalf("unexpected g.Edges[0].Attributes.Label.Value : %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "" {
+ t.Fatalf("unexpected g.Edges[0].Label.Value : %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1452,8 +1452,8 @@ x -> y: {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.Link.Value != "https://google.com" {
- t.Fatal(g.Objects[0].Attributes.Link.Value)
+ if g.Objects[0].Link.Value != "https://google.com" {
+ t.Fatal(g.Objects[0].Link.Value)
}
},
},
@@ -1465,8 +1465,8 @@ x -> y: {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.Tooltip.Value != "https://google.com" {
- t.Fatal(g.Objects[0].Attributes.Tooltip.Value)
+ if g.Objects[0].Tooltip.Value != "https://google.com" {
+ t.Fatal(g.Objects[0].Tooltip.Value)
}
},
},
@@ -1482,12 +1482,12 @@ x -> y: {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.Link.Value != "https://google.com" {
- t.Fatal(g.Objects[0].Attributes.Link.Value)
+ if g.Objects[0].Link.Value != "https://google.com" {
+ t.Fatal(g.Objects[0].Link.Value)
}
- if g.Objects[0].Attributes.Tooltip.Value != "hello world" {
- t.Fatal(g.Objects[0].Attributes.Tooltip.Value)
+ if g.Objects[0].Tooltip.Value != "hello world" {
+ t.Fatal(g.Objects[0].Tooltip.Value)
}
},
},
@@ -1517,8 +1517,8 @@ b: {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.Link.Value != "Overview.Untitled board 7.zzzzz" {
- t.Fatal(g.Objects[0].Attributes.Link.Value)
+ if g.Objects[0].Link.Value != "Overview.Untitled board 7.zzzzz" {
+ t.Fatal(g.Objects[0].Link.Value)
}
},
},
@@ -1558,25 +1558,27 @@ d2/testdata/d2compiler/TestCompile/near-invalid.d2:14:9: near keys cannot be set
`,
expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:9: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`,
},
- {
- name: "near_bad_container",
-
- text: `x: {
- near: top-center
- y
-}
-`,
- expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:2:9: constant near keys cannot be set on shapes with children`,
- },
{
name: "near_bad_connected",
- text: `x: {
- near: top-center
-}
-x -> y
-`,
- expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:2:9: constant near keys cannot be set on connected shapes`,
+ text: `
+ x: {
+ near: top-center
+ }
+ x -> y
+ `,
+ expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:5:5: cannot connect objects from within a container, that has near constant set, to objects outside that container`,
+ },
+ {
+ name: "near_descendant_connect_to_outside",
+ text: `
+ x: {
+ near: top-left
+ y
+ }
+ x.y -> z
+ `,
+ expErr: "d2/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.d2:6:5: cannot connect objects from within a container, that has near constant set, to objects outside that container",
},
{
name: "nested_near_constant",
@@ -1601,20 +1603,20 @@ y
if len(g.Objects) != 2 {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.NearKey == nil {
+ if g.Objects[0].NearKey == nil {
t.Fatal("missing near key")
}
- if g.Objects[0].Attributes.Icon.Path != "orange" {
- t.Fatal(g.Objects[0].Attributes.Icon)
+ if g.Objects[0].Icon.Path != "orange" {
+ t.Fatal(g.Objects[0].Icon)
}
- if g.Objects[0].Attributes.Style.Opacity.Value != "0.5" {
- t.Fatal(g.Objects[0].Attributes.Style.Opacity)
+ if g.Objects[0].Style.Opacity.Value != "0.5" {
+ t.Fatal(g.Objects[0].Style.Opacity)
}
- if g.Objects[0].Attributes.Style.Stroke.Value != "red" {
- t.Fatal(g.Objects[0].Attributes.Style.Stroke)
+ if g.Objects[0].Style.Stroke.Value != "red" {
+ t.Fatal(g.Objects[0].Style.Stroke)
}
- if g.Objects[0].Attributes.Style.Fill.Value != "green" {
- t.Fatal(g.Objects[0].Attributes.Style.Fill)
+ if g.Objects[0].Style.Fill.Value != "green" {
+ t.Fatal(g.Objects[0].Style.Fill)
}
},
},
@@ -1694,7 +1696,7 @@ y -> x.style
}
assert.String(t, `"b\nb"`, g.Objects[0].ID)
assert.String(t, `b
-b`, g.Objects[0].Attributes.Label.Value)
+b`, g.Objects[0].Label.Value)
},
},
{
@@ -1706,7 +1708,7 @@ b`, g.Objects[0].Attributes.Label.Value)
t.Fatal(g.Objects)
}
assert.String(t, "b\rb", g.Objects[0].ID)
- assert.String(t, "b\rb", g.Objects[0].Attributes.Label.Value)
+ assert.String(t, "b\rb", g.Objects[0].Label.Value)
},
},
{
@@ -1728,8 +1730,8 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects[0].Class.Methods) != 0 {
t.Fatal(len(g.Objects[0].Class.Methods))
}
- if g.Objects[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatal(g.Objects[0].Attributes.Style.Opacity.Value)
+ if g.Objects[0].Style.Opacity.Value != "0.4" {
+ t.Fatal(g.Objects[0].Style.Opacity.Value)
}
},
},
@@ -1749,8 +1751,8 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects[0].SQLTable.Columns) != 1 {
t.Fatal(len(g.Objects[0].SQLTable.Columns))
}
- if g.Objects[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatal(g.Objects[0].Attributes.Style.Opacity.Value)
+ if g.Objects[0].Style.Opacity.Value != "0.4" {
+ t.Fatal(g.Objects[0].Style.Opacity.Value)
}
},
},
@@ -1773,8 +1775,8 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects[0].SQLTable.Columns) != 1 {
t.Fatal(len(g.Objects[0].SQLTable.Columns))
}
- if g.Objects[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatal(g.Objects[0].Attributes.Style.Opacity.Value)
+ if g.Objects[0].Style.Opacity.Value != "0.4" {
+ t.Fatal(g.Objects[0].Style.Opacity.Value)
}
},
},
@@ -1794,7 +1796,7 @@ x.y -> a.b: {
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "true", g.Edges[0].Attributes.Style.Animated.Value)
+ tassert.Equal(t, "true", g.Edges[0].Style.Animated.Value)
},
},
{
@@ -1899,7 +1901,7 @@ dst.id <-> src.dst_id
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "sequence_diagram", g.Objects[0].Attributes.Shape.Value)
+ assert.String(t, "sequence_diagram", g.Objects[0].Shape.Value)
},
},
{
@@ -1946,7 +1948,7 @@ b
text: `shape: sequence_diagram
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "sequence_diagram", g.Root.Attributes.Shape.Value)
+ assert.String(t, "sequence_diagram", g.Root.Shape.Value)
},
},
{
@@ -2026,7 +2028,7 @@ ok: {
text: `direction: right`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "right", g.Root.Attributes.Direction.Value)
+ assert.String(t, "right", g.Root.Direction.Value)
},
},
{
@@ -2034,7 +2036,7 @@ ok: {
text: `x`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "", g.Objects[0].Attributes.Direction.Value)
+ assert.String(t, "", g.Objects[0].Direction.Value)
},
},
{
@@ -2044,7 +2046,7 @@ ok: {
direction: left
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "left", g.Objects[0].Attributes.Direction.Value)
+ assert.String(t, "left", g.Objects[0].Direction.Value)
},
},
{
@@ -2055,7 +2057,7 @@ ok: {
constraint: BIZ
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- assert.String(t, "bar", g.Objects[0].Attributes.Label.Value)
+ assert.String(t, "bar", g.Objects[0].Label.Value)
},
},
{
@@ -2149,7 +2151,7 @@ layers: {
}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "root.layers.x", g.Objects[0].Attributes.Link.Value)
+ tassert.Equal(t, "root.layers.x", g.Objects[0].Link.Value)
},
},
{
@@ -2169,8 +2171,8 @@ scenarios: {
}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "root.layers.cat", g.Objects[0].Attributes.Link.Value)
- tassert.Equal(t, "root.layers.cat", g.Scenarios[0].Objects[0].Attributes.Link.Value)
+ tassert.Equal(t, "root.layers.cat", g.Objects[0].Link.Value)
+ tassert.Equal(t, "root.layers.cat", g.Scenarios[0].Objects[0].Link.Value)
},
},
{
@@ -2203,7 +2205,7 @@ layers: {
}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "root.layers.x.layers.x", g.Objects[0].Attributes.Link.Value)
+ tassert.Equal(t, "root.layers.x.layers.x", g.Objects[0].Link.Value)
},
},
{
@@ -2217,7 +2219,7 @@ layers: {
}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.Equal(t, "root.layers.x", g.Objects[1].Attributes.Link.Value)
+ tassert.Equal(t, "root.layers.x", g.Objects[1].Link.Value)
},
},
{
@@ -2235,9 +2237,9 @@ layers: {
}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
- tassert.NotNil(t, g.Layers[0].Layers[0].Objects[0].Attributes.Link.Value)
- tassert.Equal(t, "root.layers.x", g.Layers[0].Layers[0].Objects[0].Attributes.Link.Value)
- tassert.Equal(t, "root.layers.x", g.Layers[0].Layers[0].Objects[1].Attributes.Link.Value)
+ tassert.NotNil(t, g.Layers[0].Layers[0].Objects[0].Link.Value)
+ tassert.Equal(t, "root.layers.x", g.Layers[0].Layers[0].Objects[0].Link.Value)
+ tassert.Equal(t, "root.layers.x", g.Layers[0].Layers[0].Objects[1].Link.Value)
},
},
{
@@ -2263,6 +2265,17 @@ x: {
}`,
expErr: `d2/testdata/d2compiler/TestCompile/border-radius-negative.d2:3:24: expected "border-radius" to be a number greater or equal to 0`,
},
+ {
+ name: "text-transform",
+ text: `direction: right
+x -> y: hi {
+ style: {
+ text-transform: capitalize
+ }
+}
+x.style.text-transform: uppercase
+y.style.text-transform: lowercase`,
+ },
{
name: "near_near_const",
text: `
@@ -2276,6 +2289,160 @@ obj {
`,
expErr: `d2/testdata/d2compiler/TestCompile/near_near_const.d2:7:8: near keys cannot be set to an object with a constant near key`,
},
+ {
+ name: "grid",
+ text: `hey: {
+ grid-rows: 200
+ grid-columns: 230
+}
+`,
+ assertions: func(t *testing.T, g *d2graph.Graph) {
+ tassert.Equal(t, "200", g.Objects[0].GridRows.Value)
+ },
+ },
+ {
+ name: "grid_negative",
+ text: `hey: {
+ grid-rows: 200
+ grid-columns: -200
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/grid_negative.d2:3:16: grid-columns must be a positive integer: "-200"`,
+ },
+ {
+ name: "grid_gap_negative",
+ text: `hey: {
+ horizontal-gap: -200
+ vertical-gap: -30
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/grid_gap_negative.d2:2:18: horizontal-gap must be a non-negative integer: "-200"
+d2/testdata/d2compiler/TestCompile/grid_gap_negative.d2:3:16: vertical-gap must be a non-negative integer: "-30"`,
+ },
+ {
+ name: "grid_edge",
+ text: `hey: {
+ grid-rows: 1
+ a -> b
+}
+ c -> hey.b
+ hey.a -> c
+
+ hey -> c: ok
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/grid_edge.d2:3:2: edges in grid diagrams are not supported yet
+d2/testdata/d2compiler/TestCompile/grid_edge.d2:5:2: edges in grid diagrams are not supported yet
+d2/testdata/d2compiler/TestCompile/grid_edge.d2:6:2: edges in grid diagrams are not supported yet`,
+ },
+ {
+ name: "grid_nested",
+ text: `hey: {
+ grid-rows: 200
+ grid-columns: 200
+
+ a
+ b
+ c
+ d.invalid descendant
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/grid_nested.d2:2:2: "grid-rows" can only be used on containers with one level of nesting right now. ("hey.d" has nested "invalid descendant")
+d2/testdata/d2compiler/TestCompile/grid_nested.d2:3:2: "grid-columns" can only be used on containers with one level of nesting right now. ("hey.d" has nested "invalid descendant")`,
+ },
+ {
+ name: "classes",
+ text: `classes: {
+ dragon_ball: {
+ label: ""
+ shape: circle
+ style.fill: orange
+ }
+ path: {
+ label: "then"
+ style.stroke-width: 4
+ }
+}
+nostar: { class: dragon_ball }
+1star: "*" { class: dragon_ball; style.fill: red }
+2star: { label: "**"; class: dragon_ball }
+
+nostar -> 1star: { class: path }
+`,
+ assertions: func(t *testing.T, g *d2graph.Graph) {
+ tassert.Equal(t, 3, len(g.Objects))
+ tassert.Equal(t, "dragon_ball", g.Objects[0].Classes[0])
+ tassert.Equal(t, "", g.Objects[0].Label.Value)
+ // Class field overrides primary
+ tassert.Equal(t, "", g.Objects[1].Label.Value)
+ tassert.Equal(t, "**", g.Objects[2].Label.Value)
+ tassert.Equal(t, "orange", g.Objects[0].Style.Fill.Value)
+ tassert.Equal(t, "red", g.Objects[1].Style.Fill.Value)
+
+ tassert.Equal(t, "4", g.Edges[0].Style.StrokeWidth.Value)
+ tassert.Equal(t, "then", g.Edges[0].Label.Value)
+ },
+ },
+ {
+ name: "reordered-classes",
+ text: `classes: {
+ x: {
+ shape: circle
+ }
+}
+a.class: x
+classes.x.shape: diamond
+`,
+ assertions: func(t *testing.T, g *d2graph.Graph) {
+ tassert.Equal(t, 1, len(g.Objects))
+ tassert.Equal(t, "diamond", g.Objects[0].Shape.Value)
+ },
+ },
+ {
+ name: "no-class-primary",
+ text: `x.class
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/no-class-primary.d2:1:3: class missing value`,
+ },
+ {
+ name: "no-class-inside-classes",
+ text: `classes: {
+ x: {
+ class: y
+ }
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/no-class-inside-classes.d2:3:5: "class" cannot appear within "classes"`,
+ },
+ {
+ // This is okay
+ name: "missing-class",
+ text: `x.class: yo
+`,
+ },
+ {
+ name: "classes-unreserved",
+ text: `classes: {
+ mango: {
+ seed
+ }
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/classes-unreserved.d2:3:5: seed is an invalid class field, must be reserved keyword`,
+ },
+ {
+ name: "classes-internal-edge",
+ text: `classes: {
+ mango: {
+ width: 100
+ }
+ jango: {
+ height: 100
+ }
+ mango -> jango
+}
+`,
+ expErr: `d2/testdata/d2compiler/TestCompile/classes-internal-edge.d2:8:3: classes cannot contain an edge`,
+ },
}
for _, tc := range testCases {
@@ -2412,6 +2579,19 @@ layers: {
assert.False(t, g.Layers[1].Scenarios[1].IsFolderOnly)
},
},
+ {
+ name: "scenarios_edge_index",
+ run: func(t *testing.T) {
+ assertCompile(t, `a -> x
+
+scenarios: {
+ 1: {
+ (a -> x)[0].style.opacity: 0.1
+ }
+}
+`, "")
+ },
+ },
{
name: "errs/duplicate_board",
run: func(t *testing.T) {
diff --git a/d2exporter/export.go b/d2exporter/export.go
index 8524e7542..fe9be6c46 100644
--- a/d2exporter/export.go
+++ b/d2exporter/export.go
@@ -42,10 +42,10 @@ func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamil
func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Theme) {
shape.Stroke = obj.GetStroke(shape.StrokeDash)
shape.Fill = obj.GetFill()
- if obj.Attributes.Shape.Value == d2target.ShapeText {
+ if obj.Shape.Value == d2target.ShapeText {
shape.Color = color.N1
}
- if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
+ if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
shape.PrimaryAccentColor = color.B2
shape.SecondaryAccentColor = color.AA2
shape.NeutralAccentColor = color.N2
@@ -72,63 +72,64 @@ func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Them
}
func applyStyles(shape *d2target.Shape, obj *d2graph.Object) {
- if obj.Attributes.Style.Opacity != nil {
- shape.Opacity, _ = strconv.ParseFloat(obj.Attributes.Style.Opacity.Value, 64)
+ if obj.Style.Opacity != nil {
+ shape.Opacity, _ = strconv.ParseFloat(obj.Style.Opacity.Value, 64)
}
- if obj.Attributes.Style.StrokeDash != nil {
- shape.StrokeDash, _ = strconv.ParseFloat(obj.Attributes.Style.StrokeDash.Value, 64)
+ if obj.Style.StrokeDash != nil {
+ shape.StrokeDash, _ = strconv.ParseFloat(obj.Style.StrokeDash.Value, 64)
}
- if obj.Attributes.Style.Fill != nil {
- shape.Fill = obj.Attributes.Style.Fill.Value
- } else if obj.Attributes.Shape.Value == d2target.ShapeText {
+ if obj.Style.Fill != nil {
+ shape.Fill = obj.Style.Fill.Value
+ } else if obj.Shape.Value == d2target.ShapeText {
shape.Fill = "transparent"
}
- if obj.Attributes.Style.FillPattern != nil {
- shape.FillPattern = obj.Attributes.Style.FillPattern.Value
+ if obj.Style.FillPattern != nil {
+ shape.FillPattern = obj.Style.FillPattern.Value
}
- if obj.Attributes.Style.Stroke != nil {
- shape.Stroke = obj.Attributes.Style.Stroke.Value
+ if obj.Style.Stroke != nil {
+ shape.Stroke = obj.Style.Stroke.Value
}
- if obj.Attributes.Style.StrokeWidth != nil {
- shape.StrokeWidth, _ = strconv.Atoi(obj.Attributes.Style.StrokeWidth.Value)
+ if obj.Style.StrokeWidth != nil {
+ shape.StrokeWidth, _ = strconv.Atoi(obj.Style.StrokeWidth.Value)
}
- if obj.Attributes.Style.Shadow != nil {
- shape.Shadow, _ = strconv.ParseBool(obj.Attributes.Style.Shadow.Value)
+ if obj.Style.Shadow != nil {
+ shape.Shadow, _ = strconv.ParseBool(obj.Style.Shadow.Value)
}
- if obj.Attributes.Style.ThreeDee != nil {
- shape.ThreeDee, _ = strconv.ParseBool(obj.Attributes.Style.ThreeDee.Value)
+ if obj.Style.ThreeDee != nil {
+ shape.ThreeDee, _ = strconv.ParseBool(obj.Style.ThreeDee.Value)
}
- if obj.Attributes.Style.Multiple != nil {
- shape.Multiple, _ = strconv.ParseBool(obj.Attributes.Style.Multiple.Value)
+ if obj.Style.Multiple != nil {
+ shape.Multiple, _ = strconv.ParseBool(obj.Style.Multiple.Value)
}
- if obj.Attributes.Style.BorderRadius != nil {
- shape.BorderRadius, _ = strconv.Atoi(obj.Attributes.Style.BorderRadius.Value)
+ if obj.Style.BorderRadius != nil {
+ shape.BorderRadius, _ = strconv.Atoi(obj.Style.BorderRadius.Value)
}
- if obj.Attributes.Style.FontColor != nil {
- shape.Color = obj.Attributes.Style.FontColor.Value
+ if obj.Style.FontColor != nil {
+ shape.Color = obj.Style.FontColor.Value
}
- if obj.Attributes.Style.Italic != nil {
- shape.Italic, _ = strconv.ParseBool(obj.Attributes.Style.Italic.Value)
+ if obj.Style.Italic != nil {
+ shape.Italic, _ = strconv.ParseBool(obj.Style.Italic.Value)
}
- if obj.Attributes.Style.Bold != nil {
- shape.Bold, _ = strconv.ParseBool(obj.Attributes.Style.Bold.Value)
+ if obj.Style.Bold != nil {
+ shape.Bold, _ = strconv.ParseBool(obj.Style.Bold.Value)
}
- if obj.Attributes.Style.Underline != nil {
- shape.Underline, _ = strconv.ParseBool(obj.Attributes.Style.Underline.Value)
+ if obj.Style.Underline != nil {
+ shape.Underline, _ = strconv.ParseBool(obj.Style.Underline.Value)
}
- if obj.Attributes.Style.Font != nil {
- shape.FontFamily = obj.Attributes.Style.Font.Value
+ if obj.Style.Font != nil {
+ shape.FontFamily = obj.Style.Font.Value
}
- if obj.Attributes.Style.DoubleBorder != nil {
- shape.DoubleBorder, _ = strconv.ParseBool(obj.Attributes.Style.DoubleBorder.Value)
+ if obj.Style.DoubleBorder != nil {
+ shape.DoubleBorder, _ = strconv.ParseBool(obj.Style.DoubleBorder.Value)
}
}
func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
shape := d2target.BaseShape()
- shape.SetType(obj.Attributes.Shape.Value)
+ shape.SetType(obj.Shape.Value)
shape.ID = obj.AbsID()
+ shape.Classes = obj.Classes
shape.ZIndex = obj.ZIndex
shape.Level = int(obj.Level())
shape.Pos = d2target.NewPoint(int(obj.TopLeft.X), int(obj.TopLeft.Y))
@@ -154,10 +155,10 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
shape.Color = text.GetColor(shape.Italic)
applyStyles(shape, obj)
- switch obj.Attributes.Shape.Value {
+ switch obj.Shape.Value {
case d2target.ShapeCode, d2target.ShapeText:
- shape.Language = obj.Attributes.Language
- shape.Label = obj.Attributes.Label.Value
+ shape.Language = obj.Language
+ shape.Label = obj.Label.Value
case d2target.ShapeClass:
shape.Class = *obj.Class
// The label is the header for classes and tables, which is set in client to be 4 px larger than the object's set font size
@@ -177,13 +178,13 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
}
}
- if obj.Attributes.Tooltip != nil {
- shape.Tooltip = obj.Attributes.Tooltip.Value
+ if obj.Tooltip != nil {
+ shape.Tooltip = obj.Tooltip.Value
}
- if obj.Attributes.Link != nil {
- shape.Link = obj.Attributes.Link.Value
+ if obj.Link != nil {
+ shape.Link = obj.Link.Value
}
- shape.Icon = obj.Attributes.Icon
+ shape.Icon = obj.Icon
if obj.IconPosition != nil {
shape.IconPosition = *obj.IconPosition
}
@@ -194,6 +195,7 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection {
connection := d2target.BaseConnection()
connection.ID = edge.AbsID()
+ connection.Classes = edge.Classes
connection.ZIndex = edge.ZIndex
text := edge.Text()
@@ -211,7 +213,11 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
}
if edge.SrcArrowhead != nil {
if edge.SrcArrowhead.Label.Value != "" {
- connection.SrcLabel = edge.SrcArrowhead.Label.Value
+ connection.SrcLabel = &d2target.Text{
+ Label: edge.SrcArrowhead.Label.Value,
+ LabelWidth: edge.SrcArrowhead.LabelDimensions.Width,
+ LabelHeight: edge.SrcArrowhead.LabelDimensions.Height,
+ }
}
}
if edge.DstArrow {
@@ -228,66 +234,70 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
}
if edge.DstArrowhead != nil {
if edge.DstArrowhead.Label.Value != "" {
- connection.DstLabel = edge.DstArrowhead.Label.Value
+ connection.DstLabel = &d2target.Text{
+ Label: edge.DstArrowhead.Label.Value,
+ LabelWidth: edge.DstArrowhead.LabelDimensions.Width,
+ LabelHeight: edge.DstArrowhead.LabelDimensions.Height,
+ }
}
}
if theme != nil && theme.SpecialRules.NoCornerRadius {
connection.BorderRadius = 0
}
- if edge.Attributes.Style.BorderRadius != nil {
- connection.BorderRadius, _ = strconv.ParseFloat(edge.Attributes.Style.BorderRadius.Value, 64)
+ if edge.Style.BorderRadius != nil {
+ connection.BorderRadius, _ = strconv.ParseFloat(edge.Style.BorderRadius.Value, 64)
}
- if edge.Attributes.Style.Opacity != nil {
- connection.Opacity, _ = strconv.ParseFloat(edge.Attributes.Style.Opacity.Value, 64)
+ if edge.Style.Opacity != nil {
+ connection.Opacity, _ = strconv.ParseFloat(edge.Style.Opacity.Value, 64)
}
- if edge.Attributes.Style.StrokeDash != nil {
- connection.StrokeDash, _ = strconv.ParseFloat(edge.Attributes.Style.StrokeDash.Value, 64)
+ if edge.Style.StrokeDash != nil {
+ connection.StrokeDash, _ = strconv.ParseFloat(edge.Style.StrokeDash.Value, 64)
}
connection.Stroke = edge.GetStroke(connection.StrokeDash)
- if edge.Attributes.Style.Stroke != nil {
- connection.Stroke = edge.Attributes.Style.Stroke.Value
+ if edge.Style.Stroke != nil {
+ connection.Stroke = edge.Style.Stroke.Value
}
- if edge.Attributes.Style.StrokeWidth != nil {
- connection.StrokeWidth, _ = strconv.Atoi(edge.Attributes.Style.StrokeWidth.Value)
+ if edge.Style.StrokeWidth != nil {
+ connection.StrokeWidth, _ = strconv.Atoi(edge.Style.StrokeWidth.Value)
}
- if edge.Attributes.Style.Fill != nil {
- connection.Fill = edge.Attributes.Style.Fill.Value
+ if edge.Style.Fill != nil {
+ connection.Fill = edge.Style.Fill.Value
}
connection.FontSize = text.FontSize
- if edge.Attributes.Style.FontSize != nil {
- connection.FontSize, _ = strconv.Atoi(edge.Attributes.Style.FontSize.Value)
+ if edge.Style.FontSize != nil {
+ connection.FontSize, _ = strconv.Atoi(edge.Style.FontSize.Value)
}
- if edge.Attributes.Style.Animated != nil {
- connection.Animated, _ = strconv.ParseBool(edge.Attributes.Style.Animated.Value)
+ if edge.Style.Animated != nil {
+ connection.Animated, _ = strconv.ParseBool(edge.Style.Animated.Value)
}
- if edge.Attributes.Tooltip != nil {
- connection.Tooltip = edge.Attributes.Tooltip.Value
+ if edge.Tooltip != nil {
+ connection.Tooltip = edge.Tooltip.Value
}
- connection.Icon = edge.Attributes.Icon
+ connection.Icon = edge.Icon
- if edge.Attributes.Style.Italic != nil {
- connection.Italic, _ = strconv.ParseBool(edge.Attributes.Style.Italic.Value)
+ if edge.Style.Italic != nil {
+ connection.Italic, _ = strconv.ParseBool(edge.Style.Italic.Value)
}
connection.Color = text.GetColor(connection.Italic)
- if edge.Attributes.Style.FontColor != nil {
- connection.Color = edge.Attributes.Style.FontColor.Value
+ if edge.Style.FontColor != nil {
+ connection.Color = edge.Style.FontColor.Value
}
- if edge.Attributes.Style.Bold != nil {
- connection.Bold, _ = strconv.ParseBool(edge.Attributes.Style.Bold.Value)
+ if edge.Style.Bold != nil {
+ connection.Bold, _ = strconv.ParseBool(edge.Style.Bold.Value)
}
if theme != nil && theme.SpecialRules.Mono {
connection.FontFamily = "mono"
}
- if edge.Attributes.Style.Font != nil {
- connection.FontFamily = edge.Attributes.Style.Font.Value
+ if edge.Style.Font != nil {
+ connection.FontFamily = edge.Style.Font.Value
}
connection.Label = text.Text
connection.LabelWidth = text.Dimensions.Width
diff --git a/d2exporter/export_test.go b/d2exporter/export_test.go
index effcc07a8..bc6f47010 100644
--- a/d2exporter/export_test.go
+++ b/d2exporter/export_test.go
@@ -16,6 +16,7 @@ import (
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2exporter"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
+ "oss.terrastruct.com/d2/d2layouts/d2grid"
"oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/geo"
@@ -231,7 +232,7 @@ func run(t *testing.T, tc testCase) {
err = g.SetDimensions(nil, ruler, nil)
assert.JSON(t, nil, err)
- err = d2sequence.Layout(ctx, g, d2dagrelayout.DefaultLayout)
+ err = d2sequence.Layout(ctx, g, d2grid.Layout(ctx, g, d2dagrelayout.DefaultLayout))
if err != nil {
t.Fatal(err)
}
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index 0f0c4daf0..f80a35b21 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -1,6 +1,7 @@
package d2graph
import (
+ "context"
"errors"
"fmt"
"math"
@@ -9,6 +10,9 @@ import (
"strconv"
"strings"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast"
@@ -52,10 +56,9 @@ type Graph struct {
func NewGraph() *Graph {
d := &Graph{}
d.Root = &Object{
- Graph: d,
- Parent: nil,
- Children: make(map[string]*Object),
- Attributes: &Attributes{},
+ Graph: d,
+ Parent: nil,
+ Children: make(map[string]*Object),
}
return d
}
@@ -67,6 +70,8 @@ func (g *Graph) RootBoard() *Graph {
return g
}
+type LayoutGraph func(context.Context, *Graph) error
+
// TODO consider having different Scalar types
// Right now we'll hold any types in Value and just convert, e.g. floats
type Scalar struct {
@@ -84,16 +89,13 @@ type Object struct {
// IDVal: yes'"
//
// ID allows joining on . naively and construct a valid D2 key path
- ID string `json:"id"`
- IDVal string `json:"id_val"`
- Map *d2ast.Map `json:"-"`
- LabelDimensions d2target.TextDimensions `json:"label_dimensions"`
- References []Reference `json:"references,omitempty"`
+ ID string `json:"id"`
+ IDVal string `json:"id_val"`
+ Map *d2ast.Map `json:"-"`
+ References []Reference `json:"references,omitempty"`
*geo.Box `json:"box,omitempty"`
LabelPosition *string `json:"labelPosition,omitempty"`
- LabelWidth *int `json:"labelWidth,omitempty"`
- LabelHeight *int `json:"labelHeight,omitempty"`
IconPosition *string `json:"iconPosition,omitempty"`
Class *d2target.Class `json:"class,omitempty"`
@@ -102,20 +104,22 @@ type Object struct {
Children map[string]*Object `json:"-"`
ChildrenArray []*Object `json:"-"`
- Attributes *Attributes `json:"attributes,omitempty"`
+ Attributes `json:"attributes"`
ZIndex int `json:"zIndex"`
}
type Attributes struct {
- Label Scalar `json:"label"`
+ Label Scalar `json:"label"`
+ LabelDimensions d2target.TextDimensions `json:"labelDimensions"`
+
Style Style `json:"style"`
Icon *url.URL `json:"icon,omitempty"`
Tooltip *Scalar `json:"tooltip,omitempty"`
Link *Scalar `json:"link,omitempty"`
- Width *Scalar `json:"width,omitempty"`
- Height *Scalar `json:"height,omitempty"`
+ WidthAttr *Scalar `json:"width,omitempty"`
+ HeightAttr *Scalar `json:"height,omitempty"`
Top *Scalar `json:"top,omitempty"`
Left *Scalar `json:"left,omitempty"`
@@ -129,6 +133,35 @@ type Attributes struct {
Direction Scalar `json:"direction"`
Constraint Scalar `json:"constraint"`
+
+ GridRows *Scalar `json:"gridRows,omitempty"`
+ GridColumns *Scalar `json:"gridColumns,omitempty"`
+ GridGap *Scalar `json:"gridGap,omitempty"`
+ VerticalGap *Scalar `json:"verticalGap,omitempty"`
+ HorizontalGap *Scalar `json:"horizontalGap,omitempty"`
+
+ // These names are attached to the rendered elements in SVG
+ // so that users can target them however they like outside of D2
+ Classes []string `json:"classes,omitempty"`
+}
+
+// ApplyTextTransform will alter the `Label.Value` of the current object based
+// on the specification of the `text-transform` styling option. This function
+// has side-effects!
+func (a *Attributes) ApplyTextTransform() {
+ if a.Style.NoneTextTransform() {
+ return
+ }
+
+ if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "uppercase" {
+ a.Label.Value = strings.ToUpper(a.Label.Value)
+ }
+ if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "lowercase" {
+ a.Label.Value = strings.ToLower(a.Label.Value)
+ }
+ if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "capitalize" {
+ a.Label.Value = cases.Title(language.Und).String(a.Label.Value)
+ }
}
// TODO references at the root scope should have their Scope set to root graph AST
@@ -151,25 +184,33 @@ func (r Reference) InEdge() bool {
}
type Style struct {
- Opacity *Scalar `json:"opacity,omitempty"`
- Stroke *Scalar `json:"stroke,omitempty"`
- Fill *Scalar `json:"fill,omitempty"`
- FillPattern *Scalar `json:"fillPattern,omitempty"`
- StrokeWidth *Scalar `json:"strokeWidth,omitempty"`
- StrokeDash *Scalar `json:"strokeDash,omitempty"`
- BorderRadius *Scalar `json:"borderRadius,omitempty"`
- Shadow *Scalar `json:"shadow,omitempty"`
- ThreeDee *Scalar `json:"3d,omitempty"`
- Multiple *Scalar `json:"multiple,omitempty"`
- Font *Scalar `json:"font,omitempty"`
- FontSize *Scalar `json:"fontSize,omitempty"`
- FontColor *Scalar `json:"fontColor,omitempty"`
- Animated *Scalar `json:"animated,omitempty"`
- Bold *Scalar `json:"bold,omitempty"`
- Italic *Scalar `json:"italic,omitempty"`
- Underline *Scalar `json:"underline,omitempty"`
- Filled *Scalar `json:"filled,omitempty"`
- DoubleBorder *Scalar `json:"doubleBorder,omitempty"`
+ Opacity *Scalar `json:"opacity,omitempty"`
+ Stroke *Scalar `json:"stroke,omitempty"`
+ Fill *Scalar `json:"fill,omitempty"`
+ FillPattern *Scalar `json:"fillPattern,omitempty"`
+ StrokeWidth *Scalar `json:"strokeWidth,omitempty"`
+ StrokeDash *Scalar `json:"strokeDash,omitempty"`
+ BorderRadius *Scalar `json:"borderRadius,omitempty"`
+ Shadow *Scalar `json:"shadow,omitempty"`
+ ThreeDee *Scalar `json:"3d,omitempty"`
+ Multiple *Scalar `json:"multiple,omitempty"`
+ Font *Scalar `json:"font,omitempty"`
+ FontSize *Scalar `json:"fontSize,omitempty"`
+ FontColor *Scalar `json:"fontColor,omitempty"`
+ Animated *Scalar `json:"animated,omitempty"`
+ Bold *Scalar `json:"bold,omitempty"`
+ Italic *Scalar `json:"italic,omitempty"`
+ Underline *Scalar `json:"underline,omitempty"`
+ Filled *Scalar `json:"filled,omitempty"`
+ DoubleBorder *Scalar `json:"doubleBorder,omitempty"`
+ TextTransform *Scalar `json:"textTransform,omitempty"`
+}
+
+// NoneTextTransform will return a boolean if the text should not have any
+// transformation applied. This should overwrite theme specific transformations
+// like `CapsLock` from the `terminal` theme.
+func (s Style) NoneTextTransform() bool {
+ return s.TextTransform != nil && s.TextTransform.Value == "none"
}
func (s *Style) Apply(key, value string) error {
@@ -340,6 +381,14 @@ func (s *Style) Apply(key, value string) error {
return errors.New(`expected "double-border" to be true or false`)
}
s.DoubleBorder.Value = value
+ case "text-transform":
+ if s.TextTransform == nil {
+ break
+ }
+ if !go2.Contains(textTransforms, strings.ToLower(value)) {
+ return fmt.Errorf(`expected "text-transform" to be one of (%s)`, strings.Join(textTransforms, ", "))
+ }
+ s.TextTransform.Value = value
default:
return fmt.Errorf("unknown style key: %s", key)
}
@@ -363,7 +412,7 @@ func (l ContainerLevel) LabelSize() int {
func (obj *Object) GetFill() string {
level := int(obj.Level())
- shape := obj.Attributes.Shape.Value
+ shape := obj.Shape.Value
if strings.EqualFold(shape, d2target.ShapeSQLTable) || strings.EqualFold(shape, d2target.ShapeClass) {
return color.N1
@@ -442,7 +491,7 @@ func (obj *Object) GetFill() string {
}
func (obj *Object) GetStroke(dashGapSize interface{}) string {
- shape := obj.Attributes.Shape.Value
+ shape := obj.Shape.Value
if strings.EqualFold(shape, d2target.ShapeCode) ||
strings.EqualFold(shape, d2target.ShapeText) {
return color.N1
@@ -469,10 +518,10 @@ func (obj *Object) IsContainer() bool {
}
func (obj *Object) HasOutsideBottomLabel() bool {
- if obj == nil || obj.Attributes == nil {
+ if obj == nil {
return false
}
- switch obj.Attributes.Shape.Value {
+ switch obj.Shape.Value {
case d2target.ShapeImage, d2target.ShapePerson:
return true
default:
@@ -480,6 +529,18 @@ func (obj *Object) HasOutsideBottomLabel() bool {
}
}
+func (obj *Object) HasLabel() bool {
+ if obj == nil {
+ return false
+ }
+ switch obj.Shape.Value {
+ case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode:
+ return false
+ default:
+ return obj.Label.Value != ""
+ }
+}
+
func (obj *Object) AbsID() string {
if obj.Parent != nil && obj.Parent.ID != "" {
return obj.Parent.AbsID() + "." + obj.ID
@@ -495,12 +556,12 @@ func (obj *Object) AbsIDArray() []string {
}
func (obj *Object) Text() *d2target.MText {
- isBold := !obj.IsContainer() && obj.Attributes.Shape.Value != "text"
+ isBold := !obj.IsContainer() && obj.Shape.Value != "text"
isItalic := false
- if obj.Attributes.Style.Bold != nil && obj.Attributes.Style.Bold.Value == "true" {
+ if obj.Style.Bold != nil && obj.Style.Bold.Value == "true" {
isBold = true
}
- if obj.Attributes.Style.Italic != nil && obj.Attributes.Style.Italic.Value == "true" {
+ if obj.Style.Italic != nil && obj.Style.Italic.Value == "true" {
isItalic = true
}
fontSize := d2fonts.FONT_SIZE_M
@@ -510,14 +571,14 @@ func (obj *Object) Text() *d2target.MText {
}
if obj.OuterSequenceDiagram() == nil {
- if obj.IsContainer() {
+ if obj.IsContainer() && obj.Shape.Value != "text" {
fontSize = obj.Level().LabelSize()
}
} else {
isBold = false
}
- if obj.Attributes.Style.FontSize != nil {
- fontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ if obj.Style.FontSize != nil {
+ fontSize, _ = strconv.Atoi(obj.Style.FontSize.Value)
}
// Class and Table objects have Label set to header
if obj.Class != nil || obj.SQLTable != nil {
@@ -527,12 +588,12 @@ func (obj *Object) Text() *d2target.MText {
isBold = false
}
return &d2target.MText{
- Text: obj.Attributes.Label.Value,
+ Text: obj.Label.Value,
FontSize: fontSize,
IsBold: isBold,
IsItalic: isItalic,
- Language: obj.Attributes.Language,
- Shape: obj.Attributes.Shape.Value,
+ Language: obj.Language,
+ Shape: obj.Shape.Value,
Dimensions: obj.LabelDimensions,
}
@@ -547,7 +608,7 @@ func (obj *Object) newObject(id string) *Object {
child := &Object{
ID: id,
IDVal: idval,
- Attributes: &Attributes{
+ Attributes: Attributes{
Label: Scalar{
Value: idval,
},
@@ -725,7 +786,7 @@ func (obj *Object) FindEdges(mk *d2ast.Key) ([]*Edge, bool) {
func (obj *Object) ensureChildEdge(ida []string) *Object {
for i := range ida {
- switch obj.Attributes.Shape.Value {
+ switch obj.Shape.Value {
case d2target.ShapeClass, d2target.ShapeSQLTable:
// This will only be called for connecting edges where we want to truncate to the
// container.
@@ -804,23 +865,23 @@ func (obj *Object) AppendReferences(ida []string, ref Reference, unresolvedObj *
}
func (obj *Object) GetLabelSize(mtexts []*d2target.MText, ruler *textmeasure.Ruler, fontFamily *d2fonts.FontFamily) (*d2target.TextDimensions, error) {
- shapeType := strings.ToLower(obj.Attributes.Shape.Value)
+ shapeType := strings.ToLower(obj.Shape.Value)
- if obj.Attributes.Style.Font != nil {
- f := d2fonts.D2_FONT_TO_FAMILY[obj.Attributes.Style.Font.Value]
+ if obj.Style.Font != nil {
+ f := d2fonts.D2_FONT_TO_FAMILY[obj.Style.Font.Value]
fontFamily = &f
}
var dims *d2target.TextDimensions
switch shapeType {
case d2target.ShapeText:
- if obj.Attributes.Language == "latex" {
+ if obj.Language == "latex" {
width, height, err := d2latex.Measure(obj.Text().Text)
if err != nil {
return nil, err
}
dims = d2target.NewTextDimensions(width, height)
- } else if obj.Attributes.Language != "" {
+ } else if obj.Language != "" {
var err error
dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text(), fontFamily)
if err != nil {
@@ -837,7 +898,7 @@ func (obj *Object) GetLabelSize(mtexts []*d2target.MText, ruler *textmeasure.Rul
dims = GetTextDimensions(mtexts, ruler, obj.Text(), fontFamily)
}
- if shapeType == d2target.ShapeSQLTable && obj.Attributes.Label.Value == "" {
+ if shapeType == d2target.ShapeSQLTable && obj.Label.Value == "" {
// measure with placeholder text to determine height
placeholder := *obj.Text()
placeholder.Text = "Table"
@@ -866,10 +927,21 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
labelDims.Height += INNER_LABEL_PADDING
}
- switch strings.ToLower(obj.Attributes.Shape.Value) {
+ switch strings.ToLower(obj.Shape.Value) {
default:
return d2target.NewTextDimensions(labelDims.Width, labelDims.Height), nil
+ case d2target.ShapeText:
+ w := labelDims.Width
+ if w < MIN_SHAPE_SIZE {
+ w = MIN_SHAPE_SIZE
+ }
+ h := labelDims.Height
+ if h < MIN_SHAPE_SIZE {
+ h = MIN_SHAPE_SIZE
+ }
+ return d2target.NewTextDimensions(w, h), nil
+
case d2target.ShapeImage:
return d2target.NewTextDimensions(128, 128), nil
@@ -877,8 +949,8 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
maxWidth := go2.Max(12, labelDims.Width)
fontSize := d2fonts.FONT_SIZE_L
- if obj.Attributes.Style.FontSize != nil {
- fontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ if obj.Style.FontSize != nil {
+ fontSize, _ = strconv.Atoi(obj.Style.FontSize.Value)
}
for _, f := range obj.Class.Fields {
@@ -923,8 +995,8 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
constraintWidth := 0
colFontSize := d2fonts.FONT_SIZE_L
- if obj.Attributes.Style.FontSize != nil {
- colFontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ if obj.Style.FontSize != nil {
+ colFontSize, _ = strconv.Atoi(obj.Style.FontSize.Value)
}
for i := range obj.SQLTable.Columns {
@@ -968,18 +1040,24 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
return &dims, nil
}
+func (obj *Object) OuterNearContainer() *Object {
+ for obj != nil {
+ if obj.NearKey != nil {
+ return obj
+ }
+ obj = obj.Parent
+ }
+ return nil
+}
+
type Edge struct {
Index int `json:"index"`
- MinWidth int `json:"minWidth"`
- MinHeight int `json:"minHeight"`
-
SrcTableColumnIndex *int `json:"srcTableColumnIndex,omitempty"`
DstTableColumnIndex *int `json:"dstTableColumnIndex,omitempty"`
- LabelDimensions d2target.TextDimensions `json:"label_dimensions"`
- LabelPosition *string `json:"labelPosition,omitempty"`
- LabelPercentage *float64 `json:"labelPercentage,omitempty"`
+ LabelPosition *string `json:"labelPosition,omitempty"`
+ LabelPercentage *float64 `json:"labelPercentage,omitempty"`
IsCurve bool `json:"isCurve"`
Route []*geo.Point `json:"route,omitempty"`
@@ -993,7 +1071,7 @@ type Edge struct {
DstArrowhead *Attributes `json:"dstArrowhead,omitempty"`
References []EdgeReference `json:"references,omitempty"`
- Attributes *Attributes `json:"attributes,omitempty"`
+ Attributes `json:"attributes,omitempty"`
ZIndex int `json:"zIndex"`
}
@@ -1007,6 +1085,10 @@ type EdgeReference struct {
ScopeObj *Object `json:"-"`
}
+func (e *Edge) GetAstEdge() *d2ast.Edge {
+ return e.References[0].Edge
+}
+
func (e *Edge) GetStroke(dashGapSize interface{}) string {
if dashGapSize != 0.0 {
return color.B2
@@ -1029,15 +1111,15 @@ func (e *Edge) ArrowString() string {
func (e *Edge) Text() *d2target.MText {
fontSize := d2fonts.FONT_SIZE_M
- if e.Attributes.Style.FontSize != nil {
- fontSize, _ = strconv.Atoi(e.Attributes.Style.FontSize.Value)
+ if e.Style.FontSize != nil {
+ fontSize, _ = strconv.Atoi(e.Style.FontSize.Value)
}
isBold := false
- if e.Attributes.Style.Bold != nil {
- isBold, _ = strconv.ParseBool(e.Attributes.Style.Bold.Value)
+ if e.Style.Bold != nil {
+ isBold, _ = strconv.ParseBool(e.Style.Bold.Value)
}
return &d2target.MText{
- Text: e.Attributes.Label.Value,
+ Text: e.Label.Value,
FontSize: fontSize,
IsBold: isBold,
IsItalic: true,
@@ -1085,7 +1167,7 @@ func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label
}
e := &Edge{
- Attributes: &Attributes{
+ Attributes: Attributes{
Label: Scalar{
Value: label,
},
@@ -1104,7 +1186,7 @@ func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label
}
func addSQLTableColumnIndices(e *Edge, srcID, dstID []string, obj, src, dst *Object) {
- if src.Attributes.Shape.Value == d2target.ShapeSQLTable {
+ if src.Shape.Value == d2target.ShapeSQLTable {
if src == dst {
// Ignore edge to column inside table.
return
@@ -1122,7 +1204,7 @@ func addSQLTableColumnIndices(e *Edge, srcID, dstID []string, obj, src, dst *Obj
}
}
}
- if dst.Attributes.Shape.Value == d2target.ShapeSQLTable {
+ if dst.Shape.Value == d2target.ShapeSQLTable {
objAbsID := obj.AbsIDArray()
dstAbsID := dst.AbsIDArray()
if len(objAbsID)+len(dstID) > len(dstAbsID) {
@@ -1179,7 +1261,7 @@ func getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t
}
if ruler != nil {
- width, height, err := textmeasure.MeasureMarkdown(t.Text, ruler, fontFamily)
+ width, height, err := textmeasure.MeasureMarkdown(t.Text, ruler, fontFamily, t.FontSize)
if err != nil {
return nil, err
}
@@ -1269,16 +1351,16 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
var desiredWidth int
var desiredHeight int
- if obj.Attributes.Width != nil {
- desiredWidth, _ = strconv.Atoi(obj.Attributes.Width.Value)
+ if obj.WidthAttr != nil {
+ desiredWidth, _ = strconv.Atoi(obj.WidthAttr.Value)
}
- if obj.Attributes.Height != nil {
- desiredHeight, _ = strconv.Atoi(obj.Attributes.Height.Value)
+ if obj.HeightAttr != nil {
+ desiredHeight, _ = strconv.Atoi(obj.HeightAttr.Value)
}
- dslShape := strings.ToLower(obj.Attributes.Shape.Value)
+ dslShape := strings.ToLower(obj.Shape.Value)
- if obj.Attributes.Label.Value == "" &&
+ if obj.Label.Value == "" &&
dslShape != d2target.ShapeImage &&
dslShape != d2target.ShapeSQLTable &&
dslShape != d2target.ShapeClass {
@@ -1304,11 +1386,12 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
continue
}
- if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) {
- if obj.Attributes.Language != "latex" {
- obj.Attributes.Label.Value = strings.ToUpper(obj.Attributes.Label.Value)
+ if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !strings.EqualFold(obj.Shape.Value, d2target.ShapeCode) {
+ if obj.Language != "latex" && !obj.Style.NoneTextTransform() {
+ obj.Label.Value = strings.ToUpper(obj.Label.Value)
}
}
+ obj.ApplyTextTransform()
labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily)
if err != nil {
@@ -1316,19 +1399,9 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
obj.LabelDimensions = *labelDims
- switch dslShape {
- case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode:
- // no labels
- default:
- if obj.Attributes.Label.Value != "" {
- obj.LabelWidth = go2.Pointer(labelDims.Width)
- obj.LabelHeight = go2.Pointer(labelDims.Height)
- }
- }
-
// if there is a desired width or height, fit to content box without inner label padding for smallest minimum size
withInnerLabelPadding := desiredWidth == 0 && desiredHeight == 0 &&
- dslShape != d2target.ShapeText && obj.Attributes.Label.Value != ""
+ dslShape != d2target.ShapeText && obj.Label.Value != ""
defaultDims, err := obj.GetDefaultSize(mtexts, ruler, fontFamily, *labelDims, withInnerLabelPadding)
if err != nil {
return err
@@ -1360,7 +1433,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
// give shapes with icons extra padding to fit their label
- if obj.Attributes.Icon != nil {
+ if obj.Icon != nil {
labelHeight := float64(labelDims.Height + INNER_LABEL_PADDING)
// Evenly pad enough to fit label above icon
if desiredWidth == 0 {
@@ -1374,10 +1447,10 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
switch shapeType {
case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE, shape.IMAGE_TYPE:
default:
- if obj.Attributes.Link != nil {
+ if obj.Link != nil {
paddingX += 32
}
- if obj.Attributes.Tooltip != nil {
+ if obj.Tooltip != nil {
paddingX += 32
}
}
@@ -1406,35 +1479,33 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
}
for _, edge := range g.Edges {
- endpointLabels := []string{}
+ usedFont := fontFamily
+ if edge.Style.Font != nil {
+ f := d2fonts.D2_FONT_TO_FAMILY[edge.Style.Font.Value]
+ usedFont = &f
+ }
+
if edge.SrcArrowhead != nil && edge.SrcArrowhead.Label.Value != "" {
- endpointLabels = append(endpointLabels, edge.SrcArrowhead.Label.Value)
+ t := edge.Text()
+ t.Text = edge.SrcArrowhead.Label.Value
+ dims := GetTextDimensions(mtexts, ruler, t, usedFont)
+ edge.SrcArrowhead.LabelDimensions = *dims
}
if edge.DstArrowhead != nil && edge.DstArrowhead.Label.Value != "" {
- endpointLabels = append(endpointLabels, edge.DstArrowhead.Label.Value)
- }
-
- for _, label := range endpointLabels {
t := edge.Text()
- t.Text = label
- dims := GetTextDimensions(mtexts, ruler, t, fontFamily)
- edge.MinWidth += dims.Width
- // Some padding as it's not totally near the end
- edge.MinHeight += dims.Height + 5
+ t.Text = edge.DstArrowhead.Label.Value
+ dims := GetTextDimensions(mtexts, ruler, t, usedFont)
+ edge.DstArrowhead.LabelDimensions = *dims
}
- if edge.Attributes.Label.Value == "" {
+ if edge.Label.Value == "" {
continue
}
- if g.Theme != nil && g.Theme.SpecialRules.CapsLock {
- edge.Attributes.Label.Value = strings.ToUpper(edge.Attributes.Label.Value)
- }
- usedFont := fontFamily
- if edge.Attributes.Style.Font != nil {
- f := d2fonts.D2_FONT_TO_FAMILY[edge.Attributes.Style.Font.Value]
- usedFont = &f
+ if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !edge.Style.NoneTextTransform() {
+ edge.Label.Value = strings.ToUpper(edge.Label.Value)
}
+ edge.ApplyTextTransform()
dims := GetTextDimensions(mtexts, ruler, edge.Text(), usedFont)
if dims == nil {
@@ -1442,8 +1513,6 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
edge.LabelDimensions = *dims
- edge.MinWidth += dims.Width
- edge.MinHeight += dims.Height
}
return nil
}
@@ -1454,10 +1523,11 @@ func (g *Graph) Texts() []*d2target.MText {
capsLock := g.Theme != nil && g.Theme.SpecialRules.CapsLock
for _, obj := range g.Objects {
- if obj.Attributes.Label.Value != "" {
+ if obj.Label.Value != "" {
+ obj.ApplyTextTransform()
text := obj.Text()
- if capsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) {
- if obj.Attributes.Language != "latex" {
+ if capsLock && !strings.EqualFold(obj.Shape.Value, d2target.ShapeCode) {
+ if obj.Language != "latex" && !obj.Style.NoneTextTransform() {
text.Text = strings.ToUpper(text.Text)
}
}
@@ -1465,8 +1535,8 @@ func (g *Graph) Texts() []*d2target.MText {
}
if obj.Class != nil {
fontSize := d2fonts.FONT_SIZE_L
- if obj.Attributes.Style.FontSize != nil {
- fontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ if obj.Style.FontSize != nil {
+ fontSize, _ = strconv.Atoi(obj.Style.FontSize.Value)
}
for _, field := range obj.Class.Fields {
texts = appendTextDedup(texts, field.Text(fontSize))
@@ -1476,8 +1546,8 @@ func (g *Graph) Texts() []*d2target.MText {
}
} else if obj.SQLTable != nil {
colFontSize := d2fonts.FONT_SIZE_L
- if obj.Attributes.Style.FontSize != nil {
- colFontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ if obj.Style.FontSize != nil {
+ colFontSize, _ = strconv.Atoi(obj.Style.FontSize.Value)
}
for _, column := range obj.SQLTable.Columns {
for _, t := range column.Texts(colFontSize) {
@@ -1487,9 +1557,10 @@ func (g *Graph) Texts() []*d2target.MText {
}
}
for _, edge := range g.Edges {
- if edge.Attributes.Label.Value != "" {
+ if edge.Label.Value != "" {
+ edge.ApplyTextTransform()
text := edge.Text()
- if capsLock {
+ if capsLock && !edge.Style.NoneTextTransform() {
text.Text = strings.ToUpper(text.Text)
}
texts = appendTextDedup(texts, text)
@@ -1521,19 +1592,26 @@ var ReservedKeywords2 map[string]struct{}
// Non Style/Holder keywords.
var SimpleReservedKeywords = map[string]struct{}{
- "label": {},
- "desc": {},
- "shape": {},
- "icon": {},
- "constraint": {},
- "tooltip": {},
- "link": {},
- "near": {},
- "width": {},
- "height": {},
- "direction": {},
- "top": {},
- "left": {},
+ "label": {},
+ "desc": {},
+ "shape": {},
+ "icon": {},
+ "constraint": {},
+ "tooltip": {},
+ "link": {},
+ "near": {},
+ "width": {},
+ "height": {},
+ "direction": {},
+ "top": {},
+ "left": {},
+ "grid-rows": {},
+ "grid-columns": {},
+ "grid-gap": {},
+ "vertical-gap": {},
+ "horizontal-gap": {},
+ "class": {},
+ "classes": {},
}
// ReservedKeywordHolders are reserved keywords that are meaningless on its own and exist solely to hold a set of reserved keywords
@@ -1554,12 +1632,13 @@ var StyleKeywords = map[string]struct{}{
"border-radius": {},
// Only for text
- "font": {},
- "font-size": {},
- "font-color": {},
- "bold": {},
- "italic": {},
- "underline": {},
+ "font": {},
+ "font-size": {},
+ "font-color": {},
+ "bold": {},
+ "italic": {},
+ "underline": {},
+ "text-transform": {},
// Only for shapes
"shadow": {},
@@ -1597,6 +1676,8 @@ var FillPatterns = []string{
"paper",
}
+var textTransforms = []string{"none", "uppercase", "lowercase", "capitalize"}
+
// BoardKeywords contains the keywords that create new boards.
var BoardKeywords = map[string]struct{}{
"layers": {},
diff --git a/d2graph/grid_diagram.go b/d2graph/grid_diagram.go
new file mode 100644
index 000000000..b6968e8d4
--- /dev/null
+++ b/d2graph/grid_diagram.go
@@ -0,0 +1,16 @@
+package d2graph
+
+func (obj *Object) IsGridDiagram() bool {
+ return obj != nil &&
+ (obj.GridRows != nil || obj.GridColumns != nil)
+}
+
+func (obj *Object) ClosestGridDiagram() *Object {
+ if obj == nil {
+ return nil
+ }
+ if obj.IsGridDiagram() {
+ return obj
+ }
+ return obj.Parent.ClosestGridDiagram()
+}
diff --git a/d2graph/seqdiagram.go b/d2graph/seqdiagram.go
index b9cddac8d..3cd2cf171 100644
--- a/d2graph/seqdiagram.go
+++ b/d2graph/seqdiagram.go
@@ -3,7 +3,7 @@ package d2graph
import "oss.terrastruct.com/d2/d2target"
func (obj *Object) IsSequenceDiagram() bool {
- return obj != nil && obj.Attributes != nil && obj.Attributes.Shape.Value == d2target.ShapeSequenceDiagram
+ return obj != nil && obj.Shape.Value == d2target.ShapeSequenceDiagram
}
func (obj *Object) OuterSequenceDiagram() *Object {
diff --git a/d2graph/serde.go b/d2graph/serde.go
index 0b7c49ca1..61aa17e4e 100644
--- a/d2graph/serde.go
+++ b/d2graph/serde.go
@@ -265,57 +265,67 @@ func CompareSerializedObject(obj, other *Object) error {
}
}
- if obj.Attributes != nil && other.Attributes == nil {
- return fmt.Errorf("other should have attributes")
- } else if obj.Attributes == nil && other.Attributes != nil {
- return fmt.Errorf("other should not have attributes")
- } else if obj.Attributes != nil {
- if d2target.IsShape(obj.Attributes.Shape.Value) != d2target.IsShape(other.Attributes.Shape.Value) {
+ if d2target.IsShape(obj.Shape.Value) != d2target.IsShape(other.Shape.Value) {
+ return fmt.Errorf(
+ "shapes differ: obj=%s, other=%s",
+ obj.Shape.Value,
+ other.Shape.Value,
+ )
+ }
+
+ if obj.Icon == nil && other.Icon != nil {
+ return fmt.Errorf("other does not have an icon")
+ } else if obj.Icon != nil && other.Icon == nil {
+ return fmt.Errorf("obj does not have an icon")
+ }
+
+ if obj.Direction.Value != other.Direction.Value {
+ return fmt.Errorf(
+ "directions differ: obj=%s, other=%s",
+ obj.Direction.Value,
+ other.Direction.Value,
+ )
+ }
+
+ if obj.Label.Value != other.Label.Value {
+ return fmt.Errorf(
+ "labels differ: obj=%s, other=%s",
+ obj.Label.Value,
+ other.Label.Value,
+ )
+ }
+
+ if obj.NearKey != nil {
+ if other.NearKey == nil {
+ return fmt.Errorf("other does not have near")
+ }
+ objKey := strings.Join(Key(obj.NearKey), ".")
+ deserKey := strings.Join(Key(other.NearKey), ".")
+ if objKey != deserKey {
return fmt.Errorf(
- "shapes differ: obj=%s, other=%s",
- obj.Attributes.Shape.Value,
- other.Attributes.Shape.Value,
+ "near differs: obj=%s, other=%s",
+ objKey,
+ deserKey,
)
}
+ } else if other.NearKey != nil {
+ return fmt.Errorf("other should not have near")
+ }
- if obj.Attributes.Icon == nil && other.Attributes.Icon != nil {
- return fmt.Errorf("other does not have an icon")
- } else if obj.Attributes.Icon != nil && other.Attributes.Icon == nil {
- return fmt.Errorf("obj does not have an icon")
- }
+ if obj.LabelDimensions.Width != other.LabelDimensions.Width {
+ return fmt.Errorf(
+ "label width differs: obj=%d, other=%d",
+ obj.LabelDimensions.Width,
+ other.LabelDimensions.Width,
+ )
+ }
- if obj.Attributes.Direction.Value != other.Attributes.Direction.Value {
- return fmt.Errorf(
- "directions differ: obj=%s, other=%s",
- obj.Attributes.Direction.Value,
- other.Attributes.Direction.Value,
- )
- }
-
- if obj.Attributes.Label.Value != other.Attributes.Label.Value {
- return fmt.Errorf(
- "labels differ: obj=%s, other=%s",
- obj.Attributes.Label.Value,
- other.Attributes.Label.Value,
- )
- }
-
- if obj.Attributes.NearKey != nil {
- if other.Attributes.NearKey == nil {
- return fmt.Errorf("other does not have near")
- }
- objKey := strings.Join(Key(obj.Attributes.NearKey), ".")
- deserKey := strings.Join(Key(other.Attributes.NearKey), ".")
- if objKey != deserKey {
- return fmt.Errorf(
- "near differs: obj=%s, other=%s",
- objKey,
- deserKey,
- )
- }
- } else if other.Attributes.NearKey != nil {
- return fmt.Errorf("other should not have near")
- }
+ if obj.LabelDimensions.Height != other.LabelDimensions.Height {
+ return fmt.Errorf(
+ "label height differs: obj=%d, other=%d",
+ obj.LabelDimensions.Height,
+ other.LabelDimensions.Height,
+ )
}
if obj.SQLTable == nil && other.SQLTable != nil {
@@ -334,36 +344,6 @@ func CompareSerializedObject(obj, other *Object) error {
}
}
- if obj.LabelWidth != nil {
- if other.LabelWidth == nil {
- return fmt.Errorf("other does not have a label width")
- }
- if *obj.LabelWidth != *other.LabelWidth {
- return fmt.Errorf(
- "label widths differ: obj=%d, other=%d",
- *obj.LabelWidth,
- *other.LabelWidth,
- )
- }
- } else if other.LabelWidth != nil {
- return fmt.Errorf("other should not have label width")
- }
-
- if obj.LabelHeight != nil {
- if other.LabelHeight == nil {
- return fmt.Errorf("other does not have a label height")
- }
- if *obj.LabelHeight != *other.LabelHeight {
- return fmt.Errorf(
- "label heights differ: obj=%d, other=%d",
- *obj.LabelHeight,
- *other.LabelHeight,
- )
- }
- } else if other.LabelHeight != nil {
- return fmt.Errorf("other should not have label height")
- }
-
return nil
}
@@ -408,27 +388,11 @@ func CompareSerializedEdge(edge, other *Edge) error {
)
}
- if edge.MinWidth != other.MinWidth {
- return fmt.Errorf(
- "min width differs: edge=%d, other=%d",
- edge.MinWidth,
- other.MinWidth,
- )
- }
-
- if edge.MinHeight != other.MinHeight {
- return fmt.Errorf(
- "min height differs: edge=%d, other=%d",
- edge.MinHeight,
- other.MinHeight,
- )
- }
-
- if edge.Attributes.Label.Value != other.Attributes.Label.Value {
+ if edge.Label.Value != other.Label.Value {
return fmt.Errorf(
"labels differ: edge=%s, other=%s",
- edge.Attributes.Label.Value,
- other.Attributes.Label.Value,
+ edge.Label.Value,
+ other.Label.Value,
)
}
@@ -442,7 +406,7 @@ func CompareSerializedEdge(edge, other *Edge) error {
if edge.LabelDimensions.Height != other.LabelDimensions.Height {
return fmt.Errorf(
- "label hieght differs: edge=%d, other=%d",
+ "label height differs: edge=%d, other=%d",
edge.LabelDimensions.Height,
other.LabelDimensions.Height,
)
diff --git a/d2ir/compile.go b/d2ir/compile.go
index fdd638a19..a1723464b 100644
--- a/d2ir/compile.go
+++ b/d2ir/compile.go
@@ -22,62 +22,58 @@ func Compile(ast *d2ast.Map) (*Map, error) {
m.initRoot()
m.parent.(*Field).References[0].Context.Scope = ast
c.compileMap(m, ast)
- c.compileScenarios(m)
- c.compileSteps(m)
+ c.compileClasses(m)
if !c.err.Empty() {
return nil, c.err
}
return m, nil
}
-func (c *compiler) compileScenarios(m *Map) {
- scenariosf := m.GetField("scenarios")
- if scenariosf == nil {
- return
- }
- scenarios := scenariosf.Map()
- if scenarios == nil {
+func (c *compiler) compileClasses(m *Map) {
+ classes := m.GetField("classes")
+ if classes == nil || classes.Map() == nil {
return
}
- for _, sf := range scenarios.Fields {
- if sf.Map() == nil || sf.Primary() != nil {
- c.errorf(sf.References[0].Context.Key, "invalid scenario")
+ layersField := m.GetField("layers")
+ if layersField == nil {
+ return
+ }
+ layers := layersField.Map()
+ if layers == nil {
+ return
+ }
+
+ for _, lf := range layers.Fields {
+ if lf.Map() == nil || lf.Primary() != nil {
+ c.errorf(lf.References[0].Context.Key, "invalid layer")
continue
}
- base := m.CopyBase(sf)
- OverlayMap(base, sf.Map())
- sf.Composite = base
- c.compileScenarios(sf.Map())
- c.compileSteps(sf.Map())
+ l := lf.Map()
+ lClasses := l.GetField("classes")
+
+ if lClasses == nil {
+ lClasses = classes.Copy(l).(*Field)
+ l.Fields = append(l.Fields, lClasses)
+ } else {
+ base := classes.Copy(l).(*Field)
+ OverlayMap(base.Map(), lClasses.Map())
+ l.DeleteField("classes")
+ l.Fields = append(l.Fields, base)
+ }
+
+ c.compileClasses(l)
}
}
-func (c *compiler) compileSteps(m *Map) {
- stepsf := m.GetField("steps")
- if stepsf == nil {
+func (c *compiler) overlay(base *Map, f *Field) {
+ if f.Map() == nil || f.Primary() != nil {
+ c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f))
return
}
- steps := stepsf.Map()
- if steps == nil {
- return
- }
- for i, sf := range steps.Fields {
- if sf.Map() == nil || sf.Primary() != nil {
- c.errorf(sf.References[0].Context.Key, "invalid step")
- break
- }
- var base *Map
- if i == 0 {
- base = m.CopyBase(sf)
- } else {
- base = steps.Fields[i-1].Map().CopyBase(sf)
- }
- OverlayMap(base, sf.Map())
- sf.Composite = base
- c.compileScenarios(sf.Map())
- c.compileSteps(sf.Map())
- }
+ base = base.CopyBase(f)
+ OverlayMap(base, f.Map())
+ f.Composite = base
}
func (c *compiler) compileMap(dst *Map, ast *d2ast.Map) {
@@ -128,7 +124,27 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
parent: f,
}
}
+ switch NodeBoardKind(f) {
+ case BoardScenario:
+ c.overlay(ParentBoard(f).Map(), f)
+ case BoardStep:
+ stepsMap := ParentMap(f)
+ for i := range stepsMap.Fields {
+ if stepsMap.Fields[i] == f {
+ if i == 0 {
+ c.overlay(ParentBoard(f).Map(), f)
+ } else {
+ c.overlay(stepsMap.Fields[i-1].Map(), f)
+ }
+ break
+ }
+ }
+ }
c.compileMap(f.Map(), refctx.Key.Value.Map)
+ switch NodeBoardKind(f) {
+ case BoardScenario, BoardStep:
+ c.compileClasses(f.Map())
+ }
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
// If the link is a board, we need to transform it into an absolute path.
if f.Name == "link" {
diff --git a/d2ir/compile_test.go b/d2ir/compile_test.go
index 71d46c924..b9fc2250d 100644
--- a/d2ir/compile_test.go
+++ b/d2ir/compile_test.go
@@ -19,6 +19,7 @@ func TestCompile(t *testing.T) {
t.Parallel()
t.Run("fields", testCompileFields)
+ t.Run("classes", testCompileClasses)
t.Run("edges", testCompileEdges)
t.Run("layers", testCompileLayers)
t.Run("scenarios", testCompileScenarios)
@@ -101,10 +102,12 @@ func makeScalar(v interface{}) *d2ir.Scalar {
bv := &big.Rat{}
bv.SetFloat64(v)
s.Value = &d2ast.Number{
+ Raw: fmt.Sprint(v),
Value: bv,
}
case int:
s.Value = &d2ast.Number{
+ Raw: fmt.Sprint(v),
Value: big.NewRat(int64(v), 1),
}
case string:
@@ -379,6 +382,20 @@ scenarios: {
assertQuery(t, m, 0, 0, nil, "scenarios.nuclear.quiche")
},
},
+ {
+ name: "edge",
+ run: func(t testing.TB) {
+ m, err := compile(t, `a -> b
+scenarios: {
+ 1: {
+ (a -> b)[0].style.opacity: 0.1
+ }
+}`)
+ assert.Success(t, err)
+
+ assertQuery(t, m, 0, 0, nil, "(a -> b)[0]")
+ },
+ },
}
runa(t, tca)
}
@@ -431,9 +448,8 @@ scenarios: {
shape: sql_table
hey: int {constraint: primary_key}
}`)
- assert.ErrorString(t, err, `TestCompile/steps/steps_panic.d2:6:3: invalid scenario
-TestCompile/steps/steps_panic.d2:7:3: invalid scenario
-TestCompile/steps/steps_panic.d2:2:3: invalid step`)
+ assert.ErrorString(t, err, `TestCompile/steps/steps_panic.d2:3:3: invalid step
+TestCompile/steps/steps_panic.d2:7:3: invalid scenario`)
},
},
{
@@ -490,3 +506,154 @@ steps: {
}
runa(t, tca)
}
+
+func testCompileClasses(t *testing.T) {
+ t.Parallel()
+ tca := []testCase{
+ {
+ name: "basic",
+ run: func(t testing.TB) {
+ _, err := compile(t, `x
+classes: {
+ mango: {
+ style.fill: orange
+ }
+}
+`)
+ assert.Success(t, err)
+ },
+ },
+ {
+ name: "nonroot",
+ run: func(t testing.TB) {
+ _, err := compile(t, `x: {
+ classes: {
+ mango: {
+ style.fill: orange
+ }
+ }
+}
+`)
+ assert.ErrorString(t, err, `TestCompile/classes/nonroot.d2:2:3: classes is only allowed at a board root`)
+ },
+ },
+ {
+ name: "merge",
+ run: func(t testing.TB) {
+ m, err := compile(t, `classes: {
+ mango: {
+ style.fill: orange
+ width: 10
+ }
+}
+layers: {
+ hawaii: {
+ classes: {
+ mango: {
+ width: 9000
+ }
+ }
+ }
+}
+`)
+ assert.Success(t, err)
+ assertQuery(t, m, 3, 0, nil, "layers.hawaii.classes.mango")
+ assertQuery(t, m, 0, 0, "orange", "layers.hawaii.classes.mango.style.fill")
+ assertQuery(t, m, 0, 0, 9000, "layers.hawaii.classes.mango.width")
+ },
+ },
+ {
+ name: "nested",
+ run: func(t testing.TB) {
+ m, err := compile(t, `classes: {
+ mango: {
+ style.fill: orange
+ }
+}
+layers: {
+ hawaii: {
+ layers: {
+ maui: {
+ x
+ }
+ }
+ }
+}
+`)
+ assert.Success(t, err)
+ assertQuery(t, m, 3, 0, nil, "layers.hawaii.classes")
+ assertQuery(t, m, 3, 0, nil, "layers.hawaii.layers.maui.classes")
+ },
+ },
+ {
+ name: "inherited",
+ run: func(t testing.TB) {
+ m, err := compile(t, `classes: {
+ mango: {
+ style.fill: orange
+ }
+}
+scenarios: {
+ hawaii: {
+ steps: {
+ 1: {
+ classes: {
+ cherry: {
+ style.fill: red
+ }
+ }
+ x
+ }
+ 2: {
+ y
+ }
+ 3: {
+ classes: {
+ cherry: {
+ style.fill: blue
+ }
+ }
+ y
+ }
+ 4: {
+ layers: {
+ deep: {
+ x
+ }
+ }
+ x
+ }
+ }
+ }
+}
+`)
+ assert.Success(t, err)
+ assertQuery(t, m, 3, 0, nil, "scenarios.hawaii.classes")
+ assertQuery(t, m, 2, 0, nil, "scenarios.hawaii.steps.2.classes.mango")
+ assertQuery(t, m, 2, 0, nil, "scenarios.hawaii.steps.2.classes.cherry")
+ assertQuery(t, m, 0, 0, "blue", "scenarios.hawaii.steps.4.classes.cherry.style.fill")
+ assertQuery(t, m, 0, 0, "blue", "scenarios.hawaii.steps.4.layers.deep.classes.cherry.style.fill")
+ },
+ },
+ {
+ name: "layer-modify",
+ run: func(t testing.TB) {
+ m, err := compile(t, `classes: {
+ orb: {
+ style.fill: yellow
+ }
+}
+layers: {
+ x: {
+ classes.orb.style.stroke: red
+ }
+}
+`)
+ assert.Success(t, err)
+ assertQuery(t, m, 0, 0, "yellow", "layers.x.classes.orb.style.fill")
+ assertQuery(t, m, 0, 0, "red", "layers.x.classes.orb.style.stroke")
+ },
+ },
+ }
+ runa(t, tca)
+}
diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go
index 6fc8c34ca..56a5f7550 100644
--- a/d2ir/d2ir.go
+++ b/d2ir/d2ir.go
@@ -601,6 +601,18 @@ func (m *Map) EdgeCountRecursive() int {
return acc
}
+func (m *Map) GetClassMap(name string) *Map {
+ root := RootMap(m)
+ classes := root.Map().GetField("classes")
+ if classes != nil && classes.Map() != nil {
+ class := classes.Map().GetField(name)
+ if class != nil && class.Map() != nil {
+ return class.Map()
+ }
+ }
+ return nil
+}
+
func (m *Map) GetField(ida ...string) *Field {
for len(ida) > 0 && ida[0] == "_" {
m = ParentMap(m)
@@ -663,6 +675,10 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
return nil, d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`)
}
+ if head == "classes" && NodeBoardKind(m) == "" {
+ return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
+ }
+
if findBoardKeyword(head) != -1 && NodeBoardKind(m) == "" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
}
@@ -935,6 +951,13 @@ func (m *Map) appendFieldReferences(i int, kp *d2ast.KeyPath, refctx *RefContext
}
}
+func RootMap(m *Map) *Map {
+ if m.Root() {
+ return m
+ }
+ return RootMap(ParentMap(m))
+}
+
func ParentMap(n Node) *Map {
for {
n = n.Parent()
@@ -1154,3 +1177,27 @@ func (m *Map) Equal(n2 Node) bool {
return true
}
+
+func (m *Map) InClass(key *d2ast.Key) bool {
+ classes := m.Map().GetField("classes")
+ if classes == nil || classes.Map() == nil {
+ return false
+ }
+
+ for _, class := range classes.Map().Fields {
+ if class.Map() == nil {
+ continue
+ }
+ classF := class.Map().GetField(key.Key.IDA()...)
+ if classF == nil {
+ continue
+ }
+
+ for _, ref := range classF.References {
+ if ref.Context.Key == key {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go
index afa676dc0..b4c26c655 100644
--- a/d2layouts/d2dagrelayout/layout.go
+++ b/d2layouts/d2dagrelayout/layout.go
@@ -93,7 +93,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
},
}
isHorizontal := false
- switch g.Root.Attributes.Direction.Value {
+ switch g.Root.Direction.Value {
case "down":
rootAttrs.rankdir = "TB"
case "right":
@@ -114,13 +114,13 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if len(obj.ChildrenArray) == 0 || obj.Parent == g.Root {
continue
}
- if obj.LabelHeight != nil {
- maxContainerLabelHeight = go2.Max(maxContainerLabelHeight, *obj.LabelHeight+label.PADDING)
+ if obj.HasLabel() {
+ maxContainerLabelHeight = go2.Max(maxContainerLabelHeight, obj.LabelDimensions.Height+label.PADDING)
}
- if obj.Attributes.Icon != nil && obj.Attributes.Shape.Value != d2target.ShapeImage {
+ if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(obj.Width), float64(obj.Height))
- shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Attributes.Shape.Value]
+ shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
iconSize := d2target.GetIconSize(s.GetInnerBox(), string(label.InsideTopLeft))
// Since dagre container labels are pushed up, we don't want a child container to collide
@@ -160,12 +160,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
idToObj[id] = obj
height := obj.Height
- if obj.LabelWidth != nil && obj.LabelHeight != nil {
- if obj.HasOutsideBottomLabel() || obj.Attributes.Icon != nil {
- height += float64(*obj.LabelHeight) + label.PADDING
+ if obj.HasLabel() {
+ if obj.HasOutsideBottomLabel() || obj.Icon != nil {
+ height += float64(obj.LabelDimensions.Height) + label.PADDING
}
if len(obj.ChildrenArray) > 0 {
- height += float64(*obj.LabelHeight) + label.PADDING
+ height += float64(obj.LabelDimensions.Height) + label.PADDING
}
}
loadScript += generateAddNodeLine(id, int(obj.Width), int(height))
@@ -189,7 +189,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
// We want to leave some gap between multiple edges
if numEdges > 1 {
- switch g.Root.Attributes.Direction.Value {
+ switch g.Root.Direction.Value {
case "down", "up", "":
width += EDGE_LABEL_GAP
case "left", "right":
@@ -235,20 +235,20 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
obj.Width = dn.Width
obj.Height = dn.Height
- if obj.LabelWidth != nil && obj.LabelHeight != nil {
+ if obj.HasLabel() {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.OutsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
// remove the extra height we added to the node when passing to dagre
- obj.Height -= float64(*obj.LabelHeight) + label.PADDING
- } else if obj.Attributes.Icon != nil {
+ obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
+ } else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
- if obj.Attributes.Icon != nil {
+ if obj.Icon != nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.OutsideTopLeft))
obj.LabelPosition = go2.Pointer(string(label.OutsideTopRight))
@@ -307,14 +307,14 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
for _, obj := range g.Objects {
- if obj.LabelHeight == nil || len(obj.ChildrenArray) == 0 {
+ if !obj.HasLabel() || len(obj.ChildrenArray) == 0 {
continue
}
// usually you don't want to take away here more than what was added, which is the label height
// however, if the label height is more than the ranksep/2, we'll have no padding around children anymore
// so cap the amount taken off at ranksep/2
- subtract := float64(go2.Min(rootAttrs.ranksep/2, *obj.LabelHeight+label.PADDING))
+ subtract := float64(go2.Min(rootAttrs.ranksep/2, obj.LabelDimensions.Height+label.PADDING))
obj.Height -= subtract
@@ -373,7 +373,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
// Don't move src points on side of container
if almostEqual(e.Route[0].X, obj.TopLeft.X) || almostEqual(e.Route[0].X, obj.TopLeft.X+obj.Width) {
// Unless the dst is also on a container
- if e.Dst.LabelHeight == nil || len(e.Dst.ChildrenArray) <= 0 {
+ if !e.Dst.HasLabel() || len(e.Dst.ChildrenArray) <= 0 {
continue
}
}
@@ -453,18 +453,18 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
- srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Attributes.Shape.Value)], edge.Src.Box)
- dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Attributes.Shape.Value)], edge.Dst.Box)
+ srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Shape.Value)], edge.Src.Box)
+ dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Shape.Value)], edge.Dst.Box)
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, start, points[startIndex+1])
// if an edge to a container runs into its label, stop the edge at the label instead
overlapsContainerLabel := false
- if edge.Dst.IsContainer() && edge.Dst.Attributes.Label.Value != "" && !dstShape.Is(shape.TEXT_TYPE) {
+ if edge.Dst.IsContainer() && edge.Dst.Label.Value != "" && !dstShape.Is(shape.TEXT_TYPE) {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
- labelWidth := float64(*edge.Dst.LabelWidth)
- labelHeight := float64(*edge.Dst.LabelHeight)
+ labelWidth := float64(edge.Dst.LabelDimensions.Width)
+ labelHeight := float64(edge.Dst.LabelDimensions.Height)
labelTL := label.Position(*edge.Dst.LabelPosition).
GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight)
@@ -514,7 +514,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
edge.Route = path
// compile needs to assign edge label positions
- if edge.Attributes.Label.Value != "" {
+ if edge.Label.Value != "" {
edge.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go
index 953ee6604..e6a42c762 100644
--- a/d2layouts/d2elklayout/layout.go
+++ b/d2layouts/d2elklayout/layout.go
@@ -109,6 +109,8 @@ type elkOpts struct {
ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"`
ConsiderModelOrder string `json:"elk.layered.considerModelOrder.strategy,omitempty"`
+ SelfLoopDistribution string `json:"elk.layered.edgeRouting.selfLoopDistribution,omitempty"`
+
NodeSizeConstraints string `json:"elk.nodeSize.constraints,omitempty"`
ContentAlignment string `json:"elk.contentAlignment,omitempty"`
NodeSizeMinimum string `json:"elk.nodeSize.minimum,omitempty"`
@@ -159,7 +161,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
},
},
}
- switch g.Root.Attributes.Direction.Value {
+ if elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing == DefaultOpts.SelfLoopSpacing {
+ // +5 for a tiny bit of padding
+ elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(g.Root, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+5)
+ }
+ switch g.Root.Direction.Value {
case "down":
elkGraph.LayoutOptions.Direction = "DOWN"
case "up":
@@ -198,7 +204,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
if incoming >= 2 || outgoing >= 2 {
- switch g.Root.Attributes.Direction.Value {
+ switch g.Root.Direction.Value {
case "right", "left":
obj.Height = math.Max(obj.Height, math.Max(incoming, outgoing)*port_spacing)
default:
@@ -208,11 +214,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
height := obj.Height
width := obj.Width
- if obj.LabelWidth != nil && obj.LabelHeight != nil {
- if obj.HasOutsideBottomLabel() || obj.Attributes.Icon != nil {
- height += float64(*obj.LabelHeight) + label.PADDING
+ if obj.HasLabel() {
+ if obj.HasOutsideBottomLabel() || obj.Icon != nil {
+ height += float64(obj.LabelDimensions.Height) + label.PADDING
}
- width = go2.Max(width, float64(*obj.LabelWidth))
+ width = go2.Max(width, float64(obj.LabelDimensions.Width))
}
n := &ELKNode{
@@ -239,6 +245,9 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
Padding: opts.Padding,
},
}
+ if n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing == DefaultOpts.SelfLoopSpacing {
+ n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(obj, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+5)
+ }
switch elkGraph.LayoutOptions.Direction {
case "DOWN", "UP":
@@ -249,14 +258,14 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if n.LayoutOptions.Padding == DefaultOpts.Padding {
labelHeight := 0
- if obj.LabelHeight != nil {
- labelHeight = *obj.LabelHeight + label.PADDING
+ if obj.HasLabel() {
+ labelHeight = obj.LabelDimensions.Height + label.PADDING
}
n.Height += 100 + float64(labelHeight)
n.Width += 100
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(n.Width), float64(n.Height))
- shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Attributes.Shape.Value]
+ shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
paddingTop := n.Height - s.GetInnerBox().Height
@@ -264,7 +273,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
n.Width -= 100
iconHeight := 0
- if obj.Attributes.Icon != nil && obj.Attributes.Shape.Value != d2target.ShapeImage {
+ if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
iconHeight = d2target.GetIconSize(s.GetInnerBox(), string(label.InsideTopLeft)) + label.PADDING*2
}
@@ -277,15 +286,15 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
} else {
n.LayoutOptions = &elkOpts{
- // Margins: "[top=100,left=100,bottom=100,right=100]",
+ SelfLoopDistribution: "EQUALLY",
}
}
- if obj.LabelWidth != nil && obj.LabelHeight != nil {
+ if obj.HasLabel() {
n.Labels = append(n.Labels, &ELKLabel{
- Text: obj.Attributes.Label.Value,
- Width: float64(*obj.LabelWidth),
- Height: float64(*obj.LabelHeight),
+ Text: obj.Label.Value,
+ Width: float64(obj.LabelDimensions.Width),
+ Height: float64(obj.LabelDimensions.Height),
})
}
@@ -303,9 +312,9 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
Sources: []string{edge.Src.AbsID()},
Targets: []string{edge.Dst.AbsID()},
}
- if edge.Attributes.Label.Value != "" {
+ if edge.Label.Value != "" {
e.Labels = append(e.Labels, &ELKLabel{
- Text: edge.Attributes.Label.Value,
+ Text: edge.Label.Value,
Width: float64(edge.LabelDimensions.Width),
Height: float64(edge.LabelDimensions.Height),
LayoutOptions: &elkOpts{
@@ -391,19 +400,19 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
obj.Width = n.Width
obj.Height = n.Height
- if obj.LabelWidth != nil && obj.LabelHeight != nil {
+ if obj.HasLabel() {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
- obj.Height -= float64(*obj.LabelHeight) + label.PADDING
- } else if obj.Attributes.Icon != nil {
+ obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
+ } else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
- if obj.Attributes.Icon != nil {
+ if obj.Icon != nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.InsideTopLeft))
obj.LabelPosition = go2.Pointer(string(label.InsideTopRight))
@@ -444,14 +453,14 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
startIndex, endIndex := 0, len(points)-1
- srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Attributes.Shape.Value)], edge.Src.Box)
- dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Attributes.Shape.Value)], edge.Dst.Box)
+ srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Shape.Value)], edge.Src.Box)
+ dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Shape.Value)], edge.Dst.Box)
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1])
points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1])
- if edge.Attributes.Label.Value != "" {
+ if edge.Label.Value != "" {
edge.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
@@ -519,7 +528,7 @@ func deleteBends(g *d2graph.Graph) {
newStart = geo.NewPoint(end.X, start.Y)
}
- endpointShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(endpoint.Attributes.Shape.Value)], endpoint.Box)
+ endpointShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(endpoint.Shape.Value)], endpoint.Box)
newStart = shape.TraceToShapeBorder(endpointShape, newStart, end)
// Check that the new segment doesn't collide with anything new
@@ -725,3 +734,20 @@ func countEdgeIntersects(g *d2graph.Graph, sEdge *d2graph.Edge, s geo.Segment) (
}
return crossingsCount, overlapsCount, closeOverlapsCount, touchingCount
}
+
+func childrenMaxSelfLoop(parent *d2graph.Object, isWidth bool) int {
+ max := 0
+ for _, ch := range parent.Children {
+ for _, e := range parent.Graph.Edges {
+ if e.Src == e.Dst && e.Src == ch && e.Label.Value != "" {
+ if isWidth {
+ max = go2.Max(max, e.LabelDimensions.Width)
+ } else {
+ max = go2.Max(max, e.LabelDimensions.Height)
+ }
+ }
+ }
+ }
+
+ return max
+}
diff --git a/d2layouts/d2grid/grid_diagram.go b/d2layouts/d2grid/grid_diagram.go
new file mode 100644
index 000000000..c8b0c28a3
--- /dev/null
+++ b/d2layouts/d2grid/grid_diagram.go
@@ -0,0 +1,116 @@
+package d2grid
+
+import (
+ "strconv"
+ "strings"
+
+ "oss.terrastruct.com/d2/d2graph"
+)
+
+type gridDiagram struct {
+ root *d2graph.Object
+ objects []*d2graph.Object
+ rows int
+ columns int
+
+ // if true, place objects left to right along rows
+ // if false, place objects top to bottom along columns
+ rowDirected bool
+
+ width float64
+ height float64
+
+ verticalGap int
+ horizontalGap int
+}
+
+func newGridDiagram(root *d2graph.Object) *gridDiagram {
+ gd := gridDiagram{
+ root: root,
+ objects: root.ChildrenArray,
+ verticalGap: DEFAULT_GAP,
+ horizontalGap: DEFAULT_GAP,
+ }
+
+ if root.GridRows != nil {
+ gd.rows, _ = strconv.Atoi(root.GridRows.Value)
+ }
+ if root.GridColumns != nil {
+ gd.columns, _ = strconv.Atoi(root.GridColumns.Value)
+ }
+
+ if gd.rows != 0 && gd.columns != 0 {
+ // . row-directed column-directed
+ // . ┌───────┐ ┌───────┐
+ // . │ a b c │ │ a d g │
+ // . │ d e f │ │ b e h │
+ // . │ g h i │ │ c f i │
+ // . └───────┘ └───────┘
+ // if keyword rows is first, make it row-directed, if columns is first it is column-directed
+ if root.GridRows.MapKey.Range.Before(root.GridColumns.MapKey.Range) {
+ gd.rowDirected = true
+ }
+
+ // rows and columns specified, but we want to continue naturally if user enters more objects
+ // e.g. 2 rows, 3 columns specified + g added: │ with 3 columns, 2 rows:
+ // . original add row add column │ original add row add column
+ // . ┌───────┐ ┌───────┐ ┌─────────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
+ // . │ a b c │ │ a b c │ │ a b c d │ │ │ a c e │ │ a d g │ │ a c e g │
+ // . │ d e f │ │ d e f │ │ e f g │ │ │ b d f │ │ b e │ │ b d f │
+ // . └───────┘ │ g │ └─────────┘ │ └───────┘ │ c f │ └─────────┘
+ // . └───────┘ ▲ │ └───────┘ ▲
+ // . ▲ └─existing objects modified│ ▲ └─existing columns preserved
+ // . └─existing rows preserved │ └─existing objects modified
+ capacity := gd.rows * gd.columns
+ for capacity < len(gd.objects) {
+ if gd.rowDirected {
+ gd.rows++
+ capacity += gd.columns
+ } else {
+ gd.columns++
+ capacity += gd.rows
+ }
+ }
+ } else if gd.columns == 0 {
+ gd.rowDirected = true
+ // we can only make N rows with N objects
+ if len(gd.objects) < gd.rows {
+ gd.rows = len(gd.objects)
+ }
+ } else {
+ if len(gd.objects) < gd.columns {
+ gd.columns = len(gd.objects)
+ }
+ }
+
+ // grid gap sets both, but can be overridden
+ if root.GridGap != nil {
+ gd.verticalGap, _ = strconv.Atoi(root.GridGap.Value)
+ gd.horizontalGap = gd.verticalGap
+ }
+ if root.VerticalGap != nil {
+ gd.verticalGap, _ = strconv.Atoi(root.VerticalGap.Value)
+ }
+ if root.HorizontalGap != nil {
+ gd.horizontalGap, _ = strconv.Atoi(root.HorizontalGap.Value)
+ }
+
+ return &gd
+}
+
+func (gd *gridDiagram) shift(dx, dy float64) {
+ for _, obj := range gd.objects {
+ obj.TopLeft.X += dx
+ obj.TopLeft.Y += dy
+ }
+}
+
+func (gd *gridDiagram) cleanup(obj *d2graph.Object, graph *d2graph.Graph) {
+ obj.Children = make(map[string]*d2graph.Object)
+ obj.ChildrenArray = make([]*d2graph.Object, 0)
+ for _, child := range gd.objects {
+ obj.Children[strings.ToLower(child.ID)] = child
+ obj.ChildrenArray = append(obj.ChildrenArray, child)
+ }
+ graph.Objects = append(graph.Objects, gd.objects...)
+}
diff --git a/d2layouts/d2grid/layout.go b/d2layouts/d2grid/layout.go
new file mode 100644
index 000000000..7d0834edd
--- /dev/null
+++ b/d2layouts/d2grid/layout.go
@@ -0,0 +1,583 @@
+package d2grid
+
+import (
+ "context"
+ "math"
+ "sort"
+
+ "oss.terrastruct.com/d2/d2graph"
+ "oss.terrastruct.com/d2/lib/geo"
+ "oss.terrastruct.com/d2/lib/label"
+ "oss.terrastruct.com/util-go/go2"
+)
+
+const (
+ CONTAINER_PADDING = 60
+ DEFAULT_GAP = 40
+)
+
+// Layout runs the grid layout on containers with rows/columns
+// Note: children are not allowed edges or descendants
+//
+// 1. Traverse graph from root, skip objects with no rows/columns
+// 2. Construct a grid with the container children
+// 3. Remove the children from the main graph
+// 4. Run grid layout
+// 5. Set the resulting dimensions to the main graph shape
+// 6. Run core layouts (without grid children)
+// 7. Put grid children back in correct location
+func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) d2graph.LayoutGraph {
+ return func(ctx context.Context, g *d2graph.Graph) error {
+ gridDiagrams, objectOrder, err := withoutGridDiagrams(ctx, g)
+ if err != nil {
+ return err
+ }
+
+ if g.Root.IsGridDiagram() && len(g.Root.ChildrenArray) != 0 {
+ g.Root.TopLeft = geo.NewPoint(0, 0)
+ } else if err := layout(ctx, g); err != nil {
+ return err
+ }
+
+ cleanup(g, gridDiagrams, objectOrder)
+ return nil
+ }
+}
+
+func withoutGridDiagrams(ctx context.Context, g *d2graph.Graph) (gridDiagrams map[string]*gridDiagram, objectOrder map[string]int, err error) {
+ toRemove := make(map[*d2graph.Object]struct{})
+ gridDiagrams = make(map[string]*gridDiagram)
+
+ if len(g.Objects) > 0 {
+ queue := make([]*d2graph.Object, 1, len(g.Objects))
+ queue[0] = g.Root
+ for len(queue) > 0 {
+ obj := queue[0]
+ queue = queue[1:]
+ if len(obj.ChildrenArray) == 0 {
+ continue
+ }
+ if !obj.IsGridDiagram() {
+ queue = append(queue, obj.ChildrenArray...)
+ continue
+ }
+
+ gd, err := layoutGrid(g, obj)
+ if err != nil {
+ return nil, nil, err
+ }
+ obj.Children = make(map[string]*d2graph.Object)
+ obj.ChildrenArray = nil
+
+ var dx, dy float64
+ width := gd.width + 2*CONTAINER_PADDING
+ labelWidth := float64(obj.LabelDimensions.Width) + 2*label.PADDING
+ if labelWidth > width {
+ dx = (labelWidth - width) / 2
+ width = labelWidth
+ }
+ height := gd.height + 2*CONTAINER_PADDING
+ labelHeight := float64(obj.LabelDimensions.Height) + 2*label.PADDING
+ if labelHeight > CONTAINER_PADDING {
+ // if the label doesn't fit within the padding, we need to add more
+ grow := labelHeight - CONTAINER_PADDING
+ dy = grow / 2
+ height += grow
+ }
+ // we need to center children if we have to expand to fit the container label
+ if dx != 0 || dy != 0 {
+ gd.shift(dx, dy)
+ }
+ obj.Box = geo.NewBox(nil, width, height)
+
+ obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
+ gridDiagrams[obj.AbsID()] = gd
+
+ for _, o := range gd.objects {
+ toRemove[o] = struct{}{}
+ }
+ }
+ }
+
+ objectOrder = make(map[string]int)
+ layoutObjects := make([]*d2graph.Object, 0, len(toRemove))
+ for i, obj := range g.Objects {
+ objectOrder[obj.AbsID()] = i
+ if _, exists := toRemove[obj]; !exists {
+ layoutObjects = append(layoutObjects, obj)
+ }
+ }
+ g.Objects = layoutObjects
+
+ return gridDiagrams, objectOrder, nil
+}
+
+func layoutGrid(g *d2graph.Graph, obj *d2graph.Object) (*gridDiagram, error) {
+ gd := newGridDiagram(obj)
+
+ if gd.rows != 0 && gd.columns != 0 {
+ gd.layoutEvenly(g, obj)
+ } else {
+ gd.layoutDynamic(g, obj)
+ }
+
+ // position labels and icons
+ for _, o := range gd.objects {
+ if o.Icon != nil {
+ o.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
+ o.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
+ } else {
+ o.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
+ }
+ }
+
+ return gd, nil
+}
+
+func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
+ // layout objects in a grid with these 2 properties:
+ // all objects in the same row should have the same height
+ // all objects in the same column should have the same width
+
+ getObject := func(rowIndex, columnIndex int) *d2graph.Object {
+ var index int
+ if gd.rowDirected {
+ index = rowIndex*gd.columns + columnIndex
+ } else {
+ index = columnIndex*gd.rows + rowIndex
+ }
+ if index < len(gd.objects) {
+ return gd.objects[index]
+ }
+ return nil
+ }
+
+ rowHeights := make([]float64, 0, gd.rows)
+ colWidths := make([]float64, 0, gd.columns)
+ for i := 0; i < gd.rows; i++ {
+ rowHeight := 0.
+ for j := 0; j < gd.columns; j++ {
+ o := getObject(i, j)
+ if o == nil {
+ break
+ }
+ rowHeight = math.Max(rowHeight, o.Height)
+ }
+ rowHeights = append(rowHeights, rowHeight)
+ }
+ for j := 0; j < gd.columns; j++ {
+ columnWidth := 0.
+ for i := 0; i < gd.rows; i++ {
+ o := getObject(i, j)
+ if o == nil {
+ break
+ }
+ columnWidth = math.Max(columnWidth, o.Width)
+ }
+ colWidths = append(colWidths, columnWidth)
+ }
+
+ horizontalGap := float64(gd.horizontalGap)
+ verticalGap := float64(gd.verticalGap)
+
+ cursor := geo.NewPoint(0, 0)
+ if gd.rowDirected {
+ for i := 0; i < gd.rows; i++ {
+ for j := 0; j < gd.columns; j++ {
+ o := getObject(i, j)
+ if o == nil {
+ break
+ }
+ o.Width = colWidths[j]
+ o.Height = rowHeights[i]
+ o.TopLeft = cursor.Copy()
+ cursor.X += o.Width + horizontalGap
+ }
+ cursor.X = 0
+ cursor.Y += rowHeights[i] + verticalGap
+ }
+ } else {
+ for j := 0; j < gd.columns; j++ {
+ for i := 0; i < gd.rows; i++ {
+ o := getObject(i, j)
+ if o == nil {
+ break
+ }
+ o.Width = colWidths[j]
+ o.Height = rowHeights[i]
+ o.TopLeft = cursor.Copy()
+ cursor.Y += o.Height + verticalGap
+ }
+ cursor.X += colWidths[j] + horizontalGap
+ cursor.Y = 0
+ }
+ }
+
+ var totalWidth, totalHeight float64
+ for _, w := range colWidths {
+ totalWidth += w + horizontalGap
+ }
+ for _, h := range rowHeights {
+ totalHeight += h + verticalGap
+ }
+ totalWidth -= horizontalGap
+ totalHeight -= verticalGap
+ gd.width = totalWidth
+ gd.height = totalHeight
+}
+
+func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
+ // assume we have the following objects to layout:
+ // . ┌A──────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
+ // . └───────────────┘ │ │ │ │ │ │ │ │
+ // . │ │ └──────────┘ │ │ │ │
+ // . │ │ │ │ └─────────────────┘
+ // . └───┘ │ │
+ // . └─────────┘
+ // Note: if the grid is row dominant, all objects should be the same height (same width if column dominant)
+ // . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
+ // . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │ │ │ │
+ // . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │ │ │ │
+ // . │ │ │ │ │ │ │ │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤
+ // . │ │ ├ ─ ┤ │ │ │ │ │ │
+ // . └──────────────┘ └───┘ └──────────┘ └─────────┘ └─────────────────┘
+
+ horizontalGap := float64(gd.horizontalGap)
+ verticalGap := float64(gd.verticalGap)
+
+ // we want to split up the total width across the N rows or columns as evenly as possible
+ var totalWidth, totalHeight float64
+ for _, o := range gd.objects {
+ totalWidth += o.Width
+ totalHeight += o.Height
+ }
+ totalWidth += horizontalGap * float64(len(gd.objects)-gd.rows)
+ totalHeight += verticalGap * float64(len(gd.objects)-gd.columns)
+
+ var layout [][]*d2graph.Object
+ if gd.rowDirected {
+ targetWidth := totalWidth / float64(gd.rows)
+ layout = gd.getBestLayout(targetWidth, false)
+ } else {
+ targetHeight := totalHeight / float64(gd.columns)
+ layout = gd.getBestLayout(targetHeight, true)
+ }
+
+ cursor := geo.NewPoint(0, 0)
+ var maxY, maxX float64
+ if gd.rowDirected {
+ // if we have 2 rows, then each row's objects should have the same height
+ // . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
+ // . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │
+ // . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │
+ // . │ │ │ │ │ │ │
+ // . └──────────────┘ └───┘ └──────────┘ ┴
+ // . ┌D────────┐ ┌E────────────────┐ ┬ maxHeight(D,E)
+ // . │ │ │ │ │
+ // . │ │ │ │ │
+ // . │ │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
+ // . │ │ │ │ │
+ // . └─────────┘ └─────────────────┘ ┴
+ rowWidths := []float64{}
+ for _, row := range layout {
+ rowHeight := 0.
+ for _, o := range row {
+ o.TopLeft = cursor.Copy()
+ cursor.X += o.Width + horizontalGap
+ rowHeight = math.Max(rowHeight, o.Height)
+ }
+ rowWidth := cursor.X - horizontalGap
+ rowWidths = append(rowWidths, rowWidth)
+ maxX = math.Max(maxX, rowWidth)
+
+ // set all objects in row to the same height
+ for _, o := range row {
+ o.Height = rowHeight
+ }
+
+ // new row
+ cursor.X = 0
+ cursor.Y += rowHeight + verticalGap
+ }
+ maxY = cursor.Y - horizontalGap
+
+ // then expand thinnest objects to make each row the same width
+ // . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
+ // . │ │ │ │ │ │ │
+ // . │ │ │ │ │ │ │
+ // . │ │ │ │ │ │ │
+ // . └──────────────┘ └───┘ └──────────┘ ┴
+ // . ┌D────────┬────┐ ┌E────────────────┐ ┬ maxHeight(D,E)
+ // . │ │ │ │ │
+ // . │ │ │ │ │ │
+ // . │ │ │ │ │
+ // . │ │ │ │ │ │
+ // . └─────────┴────┘ └─────────────────┘ ┴
+ for i, row := range layout {
+ rowWidth := rowWidths[i]
+ if rowWidth == maxX {
+ continue
+ }
+ delta := maxX - rowWidth
+ objects := []*d2graph.Object{}
+ var widest float64
+ for _, o := range row {
+ widest = math.Max(widest, o.Width)
+ objects = append(objects, o)
+ }
+ sort.Slice(objects, func(i, j int) bool {
+ return objects[i].Width < objects[j].Width
+ })
+ // expand smaller objects to fill remaining space
+ for _, o := range objects {
+ if o.Width < widest {
+ var index int
+ for i, rowObj := range row {
+ if o == rowObj {
+ index = i
+ break
+ }
+ }
+ grow := math.Min(widest-o.Width, delta)
+ o.Width += grow
+ // shift following objects
+ for i := index + 1; i < len(row); i++ {
+ row[i].TopLeft.X += grow
+ }
+ delta -= grow
+ if delta <= 0 {
+ break
+ }
+ }
+ }
+ if delta > 0 {
+ grow := delta / float64(len(row))
+ for i := len(row) - 1; i >= 0; i-- {
+ o := row[i]
+ o.TopLeft.X += grow * float64(i)
+ o.Width += grow
+ delta -= grow
+ }
+ }
+ }
+ } else {
+ // if we have 3 columns, then each column's objects should have the same width
+ // . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
+ // . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
+ // . └──────────────┘ │ │ │ │
+ // . ┌B──┬──────────┐ └──────────┘ │ │
+ // . │ │ ┌D────────┬┐ └─────────────────┘
+ // . │ │ │ │ │
+ // . │ │ │ ││
+ // . └───┴──────────┘ │ │
+ // . │ ││
+ // . └─────────┴┘
+ colHeights := []float64{}
+ for _, column := range layout {
+ colWidth := 0.
+ for _, o := range column {
+ o.TopLeft = cursor.Copy()
+ cursor.Y += o.Height + verticalGap
+ colWidth = math.Max(colWidth, o.Width)
+ }
+ colHeight := cursor.Y - verticalGap
+ colHeights = append(colHeights, colHeight)
+ maxY = math.Max(maxY, colHeight)
+ // set all objects in column to the same width
+ for _, o := range column {
+ o.Width = colWidth
+ }
+
+ // new column
+ cursor.Y = 0
+ cursor.X += colWidth + horizontalGap
+ }
+ maxX = cursor.X - horizontalGap
+ // then expand shortest objects to make each column the same height
+ // . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
+ // . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
+ // . ├ ─ ─ ─ ─ ─ ─ ┤ │ │ │ │
+ // . │ │ └──────────┘ │ │
+ // . └──────────────┘ ┌D─────────┐ ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤
+ // . ┌B─────────────┐ │ │ │ │
+ // . │ │ │ │ │ │
+ // . │ │ │ │ │ │
+ // . │ │ │ │ │ │
+ // . └──────────────┘ └──────────┘ └─────────────────┘
+ for i, column := range layout {
+ colHeight := colHeights[i]
+ if colHeight == maxY {
+ continue
+ }
+ delta := maxY - colHeight
+ objects := []*d2graph.Object{}
+ var tallest float64
+ for _, o := range column {
+ tallest = math.Max(tallest, o.Height)
+ objects = append(objects, o)
+ }
+ sort.Slice(objects, func(i, j int) bool {
+ return objects[i].Height < objects[j].Height
+ })
+ // expand smaller objects to fill remaining space
+ for _, o := range objects {
+ if o.Height < tallest {
+ var index int
+ for i, colObj := range column {
+ if o == colObj {
+ index = i
+ break
+ }
+ }
+ grow := math.Min(tallest-o.Height, delta)
+ o.Height += grow
+ // shift following objects
+ for i := index + 1; i < len(column); i++ {
+ column[i].TopLeft.Y += grow
+ }
+ delta -= grow
+ if delta <= 0 {
+ break
+ }
+ }
+ }
+ if delta > 0 {
+ grow := delta / float64(len(column))
+ for i := len(column) - 1; i >= 0; i-- {
+ o := column[i]
+ o.TopLeft.Y += grow * float64(i)
+ o.Height += grow
+ delta -= grow
+ }
+ }
+ }
+ }
+ gd.width = maxX
+ gd.height = maxY
+}
+
+// generate the best layout of objects aiming for each row to be the targetSize width
+// if columns is true, each column aims to have the targetSize height
+func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2graph.Object {
+ var nCuts int
+ if columns {
+ nCuts = gd.columns - 1
+ } else {
+ nCuts = gd.rows - 1
+ }
+ if nCuts == 0 {
+ return genLayout(gd.objects, nil)
+ }
+
+ // get all options for where to place these cuts, preferring later cuts over earlier cuts
+ // with 5 objects and 2 cuts we have these options:
+ // . A B C │ D │ E <- these cuts would produce: ┌A─┐ ┌B─┐ ┌C─┐
+ // . A B │ C D │ E └──┘ └──┘ └──┘
+ // . A │ B C D │ E ┌D───────────┐
+ // . A B │ C │ D E └────────────┘
+ // . A │ B C │ D E ┌E───────────┐
+ // . A │ B │ C D E └────────────┘
+ divisions := genDivisions(gd.objects, nCuts)
+
+ var bestLayout [][]*d2graph.Object
+ bestDist := math.MaxFloat64
+ // of these divisions, find the layout with rows closest to the targetSize
+ for _, division := range divisions {
+ layout := genLayout(gd.objects, division)
+ dist := getDistToTarget(layout, targetSize, float64(gd.horizontalGap), float64(gd.verticalGap), columns)
+ if dist < bestDist {
+ bestLayout = layout
+ bestDist = dist
+ }
+ }
+
+ return bestLayout
+}
+
+// get all possible divisions of objects by the number of cuts
+func genDivisions(objects []*d2graph.Object, nCuts int) (divisions [][]int) {
+ if len(objects) < 2 || nCuts == 0 {
+ return nil
+ }
+ // we go in this order to prefer extra objects in starting rows rather than later ones
+ lastObj := len(objects) - 1
+ for index := lastObj; index >= nCuts; index-- {
+ if nCuts > 1 {
+ for _, inner := range genDivisions(objects[:index], nCuts-1) {
+ divisions = append(divisions, append(inner, index-1))
+ }
+ } else {
+ divisions = append(divisions, []int{index - 1})
+ }
+ }
+
+ return divisions
+}
+
+// generate a grid of objects from the given cut indices
+func genLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
+ layout := make([][]*d2graph.Object, len(cutIndices)+1)
+ objIndex := 0
+ for i := 0; i <= len(cutIndices); i++ {
+ var stop int
+ if i < len(cutIndices) {
+ stop = cutIndices[i]
+ } else {
+ stop = len(objects) - 1
+ }
+ for ; objIndex <= stop; objIndex++ {
+ layout[i] = append(layout[i], objects[objIndex])
+ }
+ }
+ return layout
+}
+
+func getDistToTarget(layout [][]*d2graph.Object, targetSize float64, horizontalGap, verticalGap float64, columns bool) float64 {
+ totalDelta := 0.
+ for _, row := range layout {
+ rowSize := 0.
+ for _, o := range row {
+ if columns {
+ rowSize += o.Height + verticalGap
+ } else {
+ rowSize += o.Width + horizontalGap
+ }
+ }
+ totalDelta += math.Abs(rowSize - targetSize)
+ }
+ return totalDelta
+}
+
+// cleanup restores the graph after the core layout engine finishes
+// - translating the grid to its position placed by the core layout engine
+// - restore the children of the grid
+// - sorts objects to their original graph order
+func cleanup(graph *d2graph.Graph, gridDiagrams map[string]*gridDiagram, objectsOrder map[string]int) {
+ defer func() {
+ sort.SliceStable(graph.Objects, func(i, j int) bool {
+ return objectsOrder[graph.Objects[i].AbsID()] < objectsOrder[graph.Objects[j].AbsID()]
+ })
+ }()
+
+ if graph.Root.IsGridDiagram() {
+ gd, exists := gridDiagrams[graph.Root.AbsID()]
+ if exists {
+ gd.cleanup(graph.Root, graph)
+ return
+ }
+ }
+
+ for _, obj := range graph.Objects {
+ gd, exists := gridDiagrams[obj.AbsID()]
+ if !exists {
+ continue
+ }
+ obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
+ // shift the grid from (0, 0)
+ gd.shift(
+ obj.TopLeft.X+CONTAINER_PADDING,
+ obj.TopLeft.Y+CONTAINER_PADDING,
+ )
+ gd.cleanup(obj, graph)
+ }
+}
diff --git a/d2layouts/d2layoutfeatures/d2layoutfeatures.go b/d2layouts/d2layoutfeatures/d2layoutfeatures.go
index 40771b229..36b54c136 100644
--- a/d2layouts/d2layoutfeatures/d2layoutfeatures.go
+++ b/d2layouts/d2layoutfeatures/d2layoutfeatures.go
@@ -1,6 +1,6 @@
package d2layoutfeatures
-// When this is true, objects can set ther `near` key to another object
+// When this is true, objects can set their `near` key to another object
// When this is false, objects can only set `near` to constants
const NEAR_OBJECT = "near_object"
diff --git a/d2layouts/d2near/layout.go b/d2layouts/d2near/layout.go
index 082a0e296..342f75eac 100644
--- a/d2layouts/d2near/layout.go
+++ b/d2layouts/d2near/layout.go
@@ -10,44 +10,62 @@ import (
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/label"
- "oss.terrastruct.com/util-go/go2"
)
const pad = 20
// Layout finds the shapes which are assigned constant near keywords and places them.
-func Layout(ctx context.Context, g *d2graph.Graph, constantNears []*d2graph.Object) error {
- if len(constantNears) == 0 {
+func Layout(ctx context.Context, g *d2graph.Graph, constantNearGraphs []*d2graph.Graph) error {
+ if len(constantNearGraphs) == 0 {
return nil
}
+ for _, tempGraph := range constantNearGraphs {
+ tempGraph.Root.ChildrenArray[0].Parent = g.Root
+ for _, obj := range tempGraph.Objects {
+ obj.Graph = g
+ }
+ }
+
// Imagine the graph has two long texts, one at top center and one at top left.
// Top left should go left enough to not collide with center.
// So place the center ones first, then the later ones will consider them for bounding box
for _, processCenters := range []bool{true, false} {
- for _, obj := range constantNears {
- if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") {
+ for _, tempGraph := range constantNearGraphs {
+ obj := tempGraph.Root.ChildrenArray[0]
+ if processCenters == strings.Contains(d2graph.Key(obj.NearKey)[0], "-center") {
+ prevX, prevY := obj.TopLeft.X, obj.TopLeft.Y
obj.TopLeft = geo.NewPoint(place(obj))
- }
- }
- for _, obj := range constantNears {
- if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") {
- // The z-index for constant nears does not matter, as it will not collide
- g.Objects = append(g.Objects, obj)
- obj.Parent.Children[obj.ID] = obj
- obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray, obj)
- }
- }
- }
+ dx, dy := obj.TopLeft.X-prevX, obj.TopLeft.Y-prevY
- // These shapes skipped core layout, which means they also skipped label placements
- for _, obj := range constantNears {
- if obj.HasOutsideBottomLabel() {
- obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
- } else if obj.Attributes.Icon != nil {
- obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
- } else {
- obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
+ for _, subObject := range tempGraph.Objects {
+ // `obj` already been replaced above by `place(obj)`
+ if subObject == obj {
+ continue
+ }
+ subObject.TopLeft.X += dx
+ subObject.TopLeft.Y += dy
+ }
+ for _, subEdge := range tempGraph.Edges {
+ for _, point := range subEdge.Route {
+ point.X += dx
+ point.Y += dy
+ }
+ }
+ }
+ }
+ for _, tempGraph := range constantNearGraphs {
+ obj := tempGraph.Root.ChildrenArray[0]
+ if processCenters == strings.Contains(d2graph.Key(obj.NearKey)[0], "-center") {
+ // The z-index for constant nears does not matter, as it will not collide
+ g.Objects = append(g.Objects, tempGraph.Objects...)
+ if obj.Parent.Children == nil {
+ obj.Parent.Children = make(map[string]*d2graph.Object)
+ }
+ obj.Parent.Children[strings.ToLower(obj.ID)] = obj
+ obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray, obj)
+ g.Edges = append(g.Edges, tempGraph.Edges...)
+ }
}
}
@@ -59,43 +77,91 @@ func place(obj *d2graph.Object) (float64, float64) {
tl, br := boundingBox(obj.Graph)
w := br.X - tl.X
h := br.Y - tl.Y
- switch d2graph.Key(obj.Attributes.NearKey)[0] {
+
+ nearKeyStr := d2graph.Key(obj.NearKey)[0]
+ var x, y float64
+ switch nearKeyStr {
case "top-left":
- return tl.X - obj.Width - pad, tl.Y - obj.Height - pad
+ x, y = tl.X-obj.Width-pad, tl.Y-obj.Height-pad
+ break
case "top-center":
- return tl.X + w/2 - obj.Width/2, tl.Y - obj.Height - pad
+ x, y = tl.X+w/2-obj.Width/2, tl.Y-obj.Height-pad
+ break
case "top-right":
- return br.X + pad, tl.Y - obj.Height - pad
+ x, y = br.X+pad, tl.Y-obj.Height-pad
+ break
case "center-left":
- return tl.X - obj.Width - pad, tl.Y + h/2 - obj.Height/2
+ x, y = tl.X-obj.Width-pad, tl.Y+h/2-obj.Height/2
+ break
case "center-right":
- return br.X + pad, tl.Y + h/2 - obj.Height/2
+ x, y = br.X+pad, tl.Y+h/2-obj.Height/2
+ break
case "bottom-left":
- return tl.X - obj.Width - pad, br.Y + pad
+ x, y = tl.X-obj.Width-pad, br.Y+pad
+ break
case "bottom-center":
- return br.X - w/2 - obj.Width/2, br.Y + pad
+ x, y = br.X-w/2-obj.Width/2, br.Y+pad
+ break
case "bottom-right":
- return br.X + pad, br.Y + pad
+ x, y = br.X+pad, br.Y+pad
+ break
}
- return 0, 0
+
+ if obj.LabelPosition != nil && !strings.Contains(*obj.LabelPosition, "INSIDE") {
+ if strings.Contains(*obj.LabelPosition, "_TOP_") {
+ // label is on the top, and container is placed on the bottom
+ if strings.Contains(nearKeyStr, "bottom") {
+ y += float64(obj.LabelDimensions.Height)
+ }
+ } else if strings.Contains(*obj.LabelPosition, "_LEFT_") {
+ // label is on the left, and container is placed on the right
+ if strings.Contains(nearKeyStr, "right") {
+ x += float64(obj.LabelDimensions.Width)
+ }
+ } else if strings.Contains(*obj.LabelPosition, "_RIGHT_") {
+ // label is on the right, and container is placed on the left
+ if strings.Contains(nearKeyStr, "left") {
+ x -= float64(obj.LabelDimensions.Width)
+ }
+ } else if strings.Contains(*obj.LabelPosition, "_BOTTOM_") {
+ // label is on the bottom, and container is placed on the top
+ if strings.Contains(nearKeyStr, "top") {
+ y -= float64(obj.LabelDimensions.Height)
+ }
+ }
+ }
+
+ return x, y
}
// WithoutConstantNears plucks out the graph objects which have "near" set to a constant value
// This is to be called before layout engines so they don't take part in regular positioning
-func WithoutConstantNears(ctx context.Context, g *d2graph.Graph) (nears []*d2graph.Object) {
+func WithoutConstantNears(ctx context.Context, g *d2graph.Graph) (constantNearGraphs []*d2graph.Graph) {
for i := 0; i < len(g.Objects); i++ {
obj := g.Objects[i]
- if obj.Attributes.NearKey == nil {
+ if obj.NearKey == nil {
continue
}
- _, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey))
+ _, isKey := g.Root.HasChild(d2graph.Key(obj.NearKey))
if isKey {
continue
}
- _, isConst := d2graph.NearConstants[d2graph.Key(obj.Attributes.NearKey)[0]]
+ _, isConst := d2graph.NearConstants[d2graph.Key(obj.NearKey)[0]]
if isConst {
- nears = append(nears, obj)
- g.Objects = append(g.Objects[:i], g.Objects[i+1:]...)
+ descendantObjects, edges := pluckObjAndEdges(g, obj)
+
+ tempGraph := d2graph.NewGraph()
+ tempGraph.Root.ChildrenArray = []*d2graph.Object{obj}
+ tempGraph.Root.Children[strings.ToLower(obj.ID)] = obj
+
+ for _, descendantObj := range descendantObjects {
+ descendantObj.Graph = tempGraph
+ }
+ tempGraph.Objects = descendantObjects
+ tempGraph.Edges = edges
+
+ constantNearGraphs = append(constantNearGraphs, tempGraph)
+
i--
delete(obj.Parent.Children, strings.ToLower(obj.ID))
for i := 0; i < len(obj.Parent.ChildrenArray); i++ {
@@ -104,9 +170,38 @@ func WithoutConstantNears(ctx context.Context, g *d2graph.Graph) (nears []*d2gra
break
}
}
+
+ obj.Parent = tempGraph.Root
}
}
- return nears
+ return constantNearGraphs
+}
+
+func pluckObjAndEdges(g *d2graph.Graph, obj *d2graph.Object) (descendantsObjects []*d2graph.Object, edges []*d2graph.Edge) {
+ for i := 0; i < len(g.Edges); i++ {
+ edge := g.Edges[i]
+ if edge.Src == obj || edge.Dst == obj {
+ edges = append(edges, edge)
+ g.Edges = append(g.Edges[:i], g.Edges[i+1:]...)
+ i--
+ }
+ }
+
+ for i := 0; i < len(g.Objects); i++ {
+ temp := g.Objects[i]
+ if temp.AbsID() == obj.AbsID() {
+ descendantsObjects = append(descendantsObjects, obj)
+ g.Objects = append(g.Objects[:i], g.Objects[i+1:]...)
+ for _, child := range obj.ChildrenArray {
+ subObjects, subEdges := pluckObjAndEdges(g, child)
+ descendantsObjects = append(descendantsObjects, subObjects...)
+ edges = append(edges, subEdges...)
+ }
+ break
+ }
+ }
+
+ return descendantsObjects, edges
}
// boundingBox gets the center of the graph as defined by shapes
@@ -122,10 +217,10 @@ func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) {
y2 := math.Inf(-1)
for _, obj := range g.Objects {
- if obj.Attributes.NearKey != nil {
+ if obj.NearKey != nil {
// Top left should not be MORE top than top-center
// But it should go more left if top-center label extends beyond bounds of diagram
- switch d2graph.Key(obj.Attributes.NearKey)[0] {
+ switch d2graph.Key(obj.NearKey)[0] {
case "top-center", "bottom-center":
x1 = math.Min(x1, obj.TopLeft.X)
x2 = math.Max(x2, obj.TopLeft.X+obj.Width)
@@ -134,18 +229,21 @@ func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) {
y2 = math.Max(y2, obj.TopLeft.Y+obj.Height)
}
} else {
+ if obj.OuterNearContainer() != nil {
+ continue
+ }
x1 = math.Min(x1, obj.TopLeft.X)
y1 = math.Min(y1, obj.TopLeft.Y)
x2 = math.Max(x2, obj.TopLeft.X+obj.Width)
y2 = math.Max(y2, obj.TopLeft.Y+obj.Height)
- if obj.Attributes.Label.Value != "" && obj.LabelPosition != nil {
+ if obj.Label.Value != "" && obj.LabelPosition != nil {
labelPosition := label.Position(*obj.LabelPosition)
if labelPosition.IsOutside() {
- labelTL := labelPosition.GetPointOnBox(obj.Box, label.PADDING, float64(*obj.LabelWidth), float64(*obj.LabelHeight))
+ labelTL := labelPosition.GetPointOnBox(obj.Box, label.PADDING, float64(obj.LabelDimensions.Width), float64(obj.LabelDimensions.Height))
x1 = math.Min(x1, labelTL.X)
y1 = math.Min(y1, labelTL.Y)
- x2 = math.Max(x2, labelTL.X+float64(*obj.LabelWidth))
- y2 = math.Max(y2, labelTL.Y+float64(*obj.LabelHeight))
+ x2 = math.Max(x2, labelTL.X+float64(obj.LabelDimensions.Width))
+ y2 = math.Max(y2, labelTL.Y+float64(obj.LabelDimensions.Height))
}
}
}
diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go
index 28755f3e0..24427914b 100644
--- a/d2layouts/d2sequence/layout.go
+++ b/d2layouts/d2sequence/layout.go
@@ -13,6 +13,33 @@ import (
"oss.terrastruct.com/d2/lib/label"
)
+// Layout runs the sequence diagram layout engine on objects of shape sequence_diagram
+//
+// 1. Traverse graph from root, skip objects with shape not `sequence_diagram`
+// 2. Construct a sequence diagram from all descendant objects and edges
+// 3. Remove those objects and edges from the main graph
+// 4. Run layout on sequence diagrams
+// 5. Set the resulting dimensions to the main graph shape
+// 6. Run core layouts (still without sequence diagram innards)
+// 7. Put back sequence diagram innards in correct location
+func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error {
+ sequenceDiagrams, objectOrder, edgeOrder, err := WithoutSequenceDiagrams(ctx, g)
+ if err != nil {
+ return err
+ }
+
+ if g.Root.IsSequenceDiagram() {
+ // the sequence diagram is the only layout engine if the whole diagram is
+ // shape: sequence_diagram
+ g.Root.TopLeft = geo.NewPoint(0, 0)
+ } else if err := layout(ctx, g); err != nil {
+ return err
+ }
+
+ cleanup(g, sequenceDiagrams, objectOrder, edgeOrder)
+ return nil
+}
+
func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string]*sequenceDiagram, map[string]int, map[string]int, error) {
objectsToRemove := make(map[*d2graph.Object]struct{})
edgesToRemove := make(map[*d2graph.Edge]struct{})
@@ -27,7 +54,7 @@ func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string]
if len(obj.ChildrenArray) == 0 {
continue
}
- if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram {
+ if obj.Shape.Value != d2target.ShapeSequenceDiagram {
queue = append(queue, obj.ChildrenArray...)
continue
}
@@ -69,33 +96,6 @@ func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string]
return sequenceDiagrams, objectOrder, edgeOrder, nil
}
-// Layout runs the sequence diagram layout engine on objects of shape sequence_diagram
-//
-// 1. Traverse graph from root, skip objects with shape not `sequence_diagram`
-// 2. Construct a sequence diagram from all descendant objects and edges
-// 3. Remove those objects and edges from the main graph
-// 4. Run layout on sequence diagrams
-// 5. Set the resulting dimensions to the main graph shape
-// 6. Run core layouts (still without sequence diagram innards)
-// 7. Put back sequence diagram innards in correct location
-func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Context, g *d2graph.Graph) error) error {
- sequenceDiagrams, objectOrder, edgeOrder, err := WithoutSequenceDiagrams(ctx, g)
- if err != nil {
- return err
- }
-
- if g.Root.IsSequenceDiagram() {
- // the sequence diagram is the only layout engine if the whole diagram is
- // shape: sequence_diagram
- g.Root.TopLeft = geo.NewPoint(0, 0)
- } else if err := layout(ctx, g); err != nil {
- return err
- }
-
- cleanup(g, sequenceDiagrams, objectOrder, edgeOrder)
- return nil
-}
-
// layoutSequenceDiagram finds the edges inside the sequence diagram and performs the layout on the object descendants
func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) (*sequenceDiagram, error) {
var edges []*d2graph.Edge
@@ -154,11 +154,11 @@ func cleanup(g *d2graph.Graph, sequenceDiagrams map[string]*sequenceDiagram, obj
objects = g.Objects
}
for _, obj := range objects {
- if _, exists := sequenceDiagrams[obj.AbsID()]; !exists {
+ sd, exists := sequenceDiagrams[obj.AbsID()]
+ if !exists {
continue
}
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
- sd := sequenceDiagrams[obj.AbsID()]
// shift the sequence diagrams as they are always placed at (0, 0) with some padding
sd.shift(
@@ -171,22 +171,22 @@ func cleanup(g *d2graph.Graph, sequenceDiagrams map[string]*sequenceDiagram, obj
obj.Children = make(map[string]*d2graph.Object)
obj.ChildrenArray = make([]*d2graph.Object, 0)
for _, child := range sd.actors {
- obj.Children[child.ID] = child
+ obj.Children[strings.ToLower(child.ID)] = child
obj.ChildrenArray = append(obj.ChildrenArray, child)
}
for _, child := range sd.groups {
if child.Parent.AbsID() == obj.AbsID() {
- obj.Children[child.ID] = child
+ obj.Children[strings.ToLower(child.ID)] = child
obj.ChildrenArray = append(obj.ChildrenArray, child)
}
}
- g.Edges = append(g.Edges, sequenceDiagrams[obj.AbsID()].messages...)
- g.Edges = append(g.Edges, sequenceDiagrams[obj.AbsID()].lifelines...)
- g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].actors...)
- g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].notes...)
- g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].groups...)
- g.Objects = append(g.Objects, sequenceDiagrams[obj.AbsID()].spans...)
+ g.Edges = append(g.Edges, sd.messages...)
+ g.Edges = append(g.Edges, sd.lifelines...)
+ g.Objects = append(g.Objects, sd.actors...)
+ g.Objects = append(g.Objects, sd.notes...)
+ g.Objects = append(g.Objects, sd.groups...)
+ g.Objects = append(g.Objects, sd.spans...)
}
// no new objects, so just keep the same position
diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go
index 8be43712d..6fe39ce9e 100644
--- a/d2layouts/d2sequence/layout_test.go
+++ b/d2layouts/d2sequence/layout_test.go
@@ -180,7 +180,7 @@ b -> a.t2`
g, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err)
- g.Root.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
+ g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
a, has := g.Root.HasChild([]string{"a"})
assert.True(t, has)
@@ -217,14 +217,14 @@ b -> a.t2`
})
// check properties
- assert.Equal(t, strings.ToLower(shape.PERSON_TYPE), strings.ToLower(a.Attributes.Shape.Value))
+ assert.Equal(t, strings.ToLower(shape.PERSON_TYPE), strings.ToLower(a.Shape.Value))
- if a_t1.Attributes.Label.Value != "" {
- t.Fatalf("expected no label for span, got %s", a_t1.Attributes.Label.Value)
+ if a_t1.Label.Value != "" {
+ t.Fatalf("expected no label for span, got %s", a_t1.Label.Value)
}
- if a_t1.Attributes.Shape.Value != shape.SQUARE_TYPE {
- t.Fatalf("expected square shape for span, got %s", a_t1.Attributes.Shape.Value)
+ if a_t1.Shape.Value != shape.SQUARE_TYPE {
+ t.Fatalf("expected square shape for span, got %s", a_t1.Shape.Value)
}
if a_t1.Height != b_t1.Height {
@@ -323,7 +323,7 @@ container -> c: edge 1
c := g.Root.EnsureChild([]string{"c"})
c.Box = geo.NewBox(nil, 100, 100)
- c.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSquare}
+ c.Shape = d2graph.Scalar{Value: d2target.ShapeSquare}
layoutFn := func(ctx context.Context, g *d2graph.Graph) error {
if len(g.Objects) != 2 {
@@ -378,7 +378,7 @@ container -> c: edge 1
func TestSelfEdges(t *testing.T) {
g := d2graph.NewGraph()
- g.Root.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
+ g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
n1 := g.Root.EnsureChild([]string{"n1"})
n1.Box = geo.NewBox(nil, 100, 100)
@@ -387,7 +387,7 @@ func TestSelfEdges(t *testing.T) {
Src: n1,
Dst: n1,
Index: 0,
- Attributes: &d2graph.Attributes{
+ Attributes: d2graph.Attributes{
Label: d2graph.Scalar{Value: "left to right"},
},
},
@@ -414,10 +414,10 @@ func TestSelfEdges(t *testing.T) {
func TestSequenceToDescendant(t *testing.T) {
g := d2graph.NewGraph()
- g.Root.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
+ g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
a := g.Root.EnsureChild([]string{"a"})
a.Box = geo.NewBox(nil, 100, 100)
- a.Attributes = &d2graph.Attributes{
+ a.Attributes = d2graph.Attributes{
Shape: d2graph.Scalar{Value: shape.PERSON_TYPE},
}
a_t1 := a.EnsureChild([]string{"t1"})
@@ -425,15 +425,13 @@ func TestSequenceToDescendant(t *testing.T) {
g.Edges = []*d2graph.Edge{
{
- Src: a,
- Dst: a_t1,
- Index: 0,
- Attributes: &d2graph.Attributes{},
+ Src: a,
+ Dst: a_t1,
+ Index: 0,
}, {
- Src: a_t1,
- Dst: a,
- Index: 0,
- Attributes: &d2graph.Attributes{},
+ Src: a_t1,
+ Dst: a,
+ Index: 0,
},
}
diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go
index f30307d7d..ac2e8d2e2 100644
--- a/d2layouts/d2sequence/sequence_diagram.go
+++ b/d2layouts/d2sequence/sequence_diagram.go
@@ -110,7 +110,7 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
sd.objectRank[actor] = rank
if actor.Width < MIN_ACTOR_WIDTH {
- dslShape := strings.ToLower(actor.Attributes.Shape.Value)
+ dslShape := strings.ToLower(actor.Shape.Value)
switch dslShape {
case d2target.ShapePerson, d2target.ShapeOval, d2target.ShapeSquare, d2target.ShapeCircle:
// scale shape up to min width uniformly
@@ -131,7 +131,7 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
// edge groups are children of actors with no edges and children edges
if child.IsSequenceDiagramNote() {
sd.verticalIndices[child.AbsID()] = getObjEarliestLineNum(child)
- child.Attributes.Shape = d2graph.Scalar{Value: shape.PAGE_TYPE}
+ child.Shape = d2graph.Scalar{Value: shape.PAGE_TYPE}
sd.notes = append(sd.notes, child)
sd.objectRank[child] = rank
child.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
@@ -139,8 +139,8 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
} else {
// spans have no labels
// TODO why not? Spans should be able to
- child.Attributes.Label = d2graph.Scalar{Value: ""}
- child.Attributes.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE}
+ child.Label = d2graph.Scalar{Value: ""}
+ child.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE}
sd.spans = append(sd.spans, child)
sd.objectRank[child] = rank
}
@@ -186,8 +186,8 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
sd.yStep += VERTICAL_PAD
sd.maxActorHeight += VERTICAL_PAD
- if sd.root.LabelHeight != nil {
- sd.maxActorHeight += float64(*sd.root.LabelHeight)
+ if sd.root.HasLabel() {
+ sd.maxActorHeight += float64(sd.root.LabelDimensions.Height)
}
return sd, nil
@@ -282,11 +282,11 @@ func (sd *sequenceDiagram) placeGroup(group *d2graph.Object) {
}
func (sd *sequenceDiagram) adjustGroupLabel(group *d2graph.Object) {
- if group.LabelHeight == nil {
+ if !group.HasLabel() {
return
}
- heightAdd := (*group.LabelHeight + EDGE_GROUP_LABEL_PADDING) - GROUP_CONTAINER_PADDING
+ heightAdd := (group.LabelDimensions.Height + EDGE_GROUP_LABEL_PADDING) - GROUP_CONTAINER_PADDING
if heightAdd < 0 {
return
}
@@ -339,8 +339,8 @@ func (sd *sequenceDiagram) placeActors() {
if actor.HasOutsideBottomLabel() {
actor.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
yOffset = sd.maxActorHeight - actor.Height
- if actor.LabelHeight != nil {
- yOffset -= float64(*actor.LabelHeight)
+ if actor.HasLabel() {
+ yOffset -= float64(actor.LabelDimensions.Height)
}
} else {
actor.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
@@ -381,20 +381,26 @@ func (sd *sequenceDiagram) addLifelineEdges() {
for _, actor := range sd.actors {
actorBottom := actor.Center()
actorBottom.Y = actor.TopLeft.Y + actor.Height
- if *actor.LabelPosition == string(label.OutsideBottomCenter) && actor.LabelHeight != nil {
- actorBottom.Y += float64(*actor.LabelHeight) + LIFELINE_LABEL_PAD
+ if *actor.LabelPosition == string(label.OutsideBottomCenter) && actor.HasLabel() {
+ actorBottom.Y += float64(actor.LabelDimensions.Height) + LIFELINE_LABEL_PAD
}
actorLifelineEnd := actor.Center()
actorLifelineEnd.Y = endY
+ style := d2graph.Style{
+ StrokeDash: &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_DASH)},
+ StrokeWidth: &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_WIDTH)},
+ }
+ if actor.Style.StrokeDash != nil {
+ style.StrokeDash = &d2graph.Scalar{Value: actor.Style.StrokeDash.Value}
+ }
+ if actor.Style.Stroke != nil {
+ style.Stroke = &d2graph.Scalar{Value: actor.Style.Stroke.Value}
+ }
+
sd.lifelines = append(sd.lifelines, &d2graph.Edge{
- Attributes: &d2graph.Attributes{
- Style: d2graph.Style{
- StrokeDash: &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_DASH)},
- StrokeWidth: &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_WIDTH)},
- },
- },
- Src: actor,
- SrcArrow: false,
+ Attributes: d2graph.Attributes{Style: style},
+ Src: actor,
+ SrcArrow: false,
Dst: &d2graph.Object{
ID: actor.ID + fmt.Sprintf("-lifeline-end-%d", go2.StringToIntHash(actor.ID+"-lifeline-end")),
},
@@ -575,7 +581,7 @@ func (sd *sequenceDiagram) routeMessages() error {
}
messageOffset += sd.yStep
- if message.Attributes.Label.Value != "" {
+ if message.Label.Value != "" {
message.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
diff --git a/d2lib/d2.go b/d2lib/d2.go
index 060204ea9..10fbb02e7 100644
--- a/d2lib/d2.go
+++ b/d2lib/d2.go
@@ -10,6 +10,7 @@ import (
"oss.terrastruct.com/d2/d2exporter"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
+ "oss.terrastruct.com/d2/d2layouts/d2grid"
"oss.terrastruct.com/d2/d2layouts/d2near"
"oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
@@ -68,14 +69,23 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
return nil, err
}
- constantNears := d2near.WithoutConstantNears(ctx, g)
+ constantNearGraphs := d2near.WithoutConstantNears(ctx, g)
- err = d2sequence.Layout(ctx, g, coreLayout)
+ layoutWithGrids := d2grid.Layout(ctx, g, coreLayout)
+
+ // run core layout for constantNears
+ for _, tempGraph := range constantNearGraphs {
+ if err = layoutWithGrids(ctx, tempGraph); err != nil {
+ return nil, err
+ }
+ }
+
+ err = d2sequence.Layout(ctx, g, layoutWithGrids)
if err != nil {
return nil, err
}
- err = d2near.Layout(ctx, g, constantNears)
+ err = d2near.Layout(ctx, g, constantNearGraphs)
if err != nil {
return nil, err
}
@@ -110,7 +120,7 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
return d, nil
}
-func getLayout(opts *CompileOptions) (func(context.Context, *d2graph.Graph) error, error) {
+func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
if opts.Layout != nil {
return opts.Layout, nil
} else if os.Getenv("D2_LAYOUT") == "dagre" {
diff --git a/d2oracle/edit.go b/d2oracle/edit.go
index 668b6d4a0..fcb56d64d 100644
--- a/d2oracle/edit.go
+++ b/d2oracle/edit.go
@@ -17,6 +17,7 @@ import (
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
+ "oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2target"
)
@@ -159,12 +160,12 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
}
- if obj.Attributes.Label.MapKey != nil && obj.Map == nil && (!found || reserved || len(mk.Edges) > 0) {
+ if obj.Label.MapKey != nil && obj.Map == nil && (!found || reserved || len(mk.Edges) > 0) {
obj.Map = &d2ast.Map{
Range: d2ast.MakeRange(",1:0:0-1:0:0"),
}
- obj.Attributes.Label.MapKey.Primary = obj.Attributes.Label.MapKey.Value.ScalarBox()
- obj.Attributes.Label.MapKey.Value = d2ast.MakeValueBox(obj.Map)
+ obj.Label.MapKey.Primary = obj.Label.MapKey.Value.ScalarBox()
+ obj.Label.MapKey.Value = d2ast.MakeValueBox(obj.Map)
scope = obj.Map
mk.Key.Path = mk.Key.Path[toSkip-1:]
@@ -180,6 +181,10 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
}
+ ir, err := d2ir.Compile(g.AST)
+ if err != nil {
+ return err
+ }
attrs := obj.Attributes
var edge *d2graph.Edge
if len(mk.Edges) == 1 {
@@ -247,10 +252,10 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
if n.MapKey.Key.Path[0].Unbox().ScalarString() == mk.Key.Path[toSkip-1].Unbox().ScalarString() {
scope = n.MapKey.Value.Map
if mk.Key.Path[0].Unbox().ScalarString() == "source-arrowhead" && edge.SrcArrowhead != nil {
- attrs = edge.SrcArrowhead
+ attrs = *edge.SrcArrowhead
}
if mk.Key.Path[0].Unbox().ScalarString() == "target-arrowhead" && edge.DstArrowhead != nil {
- attrs = edge.DstArrowhead
+ attrs = *edge.DstArrowhead
}
reservedKey = mk.Key.Path[0].Unbox().ScalarString()
mk.Key.Path = mk.Key.Path[1:]
@@ -273,6 +278,9 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
if reserved {
+ inlined := func(s *d2graph.Scalar) bool {
+ return s != nil && s.MapKey != nil && !ir.InClass(s.MapKey)
+ }
reservedIndex := toSkip - 1
if mk.Key != nil && len(mk.Key.Path) > 0 {
if reservedKey == "" {
@@ -280,47 +288,73 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
switch reservedKey {
case "shape":
- if attrs.Shape.MapKey != nil {
+ if inlined(&attrs.Shape) {
attrs.Shape.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "link":
- if attrs.Link != nil && attrs.Link.MapKey != nil {
+ if inlined(attrs.Link) {
attrs.Link.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "tooltip":
- if attrs.Tooltip != nil && attrs.Tooltip.MapKey != nil {
+ if inlined(attrs.Tooltip) {
attrs.Tooltip.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "width":
- if attrs.Width != nil && attrs.Width.MapKey != nil {
- attrs.Width.MapKey.SetScalar(mk.Value.ScalarBox())
+ if inlined(attrs.WidthAttr) {
+ attrs.WidthAttr.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "height":
- if attrs.Height != nil && attrs.Height.MapKey != nil {
- attrs.Height.MapKey.SetScalar(mk.Value.ScalarBox())
+ if inlined(attrs.HeightAttr) {
+ attrs.HeightAttr.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "top":
- if attrs.Top != nil && attrs.Top.MapKey != nil {
+ if inlined(attrs.Top) {
attrs.Top.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "left":
- if attrs.Left != nil && attrs.Left.MapKey != nil {
+ if inlined(attrs.Left) {
attrs.Left.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
- case "source-arrowhead", "target-arrowhead":
- if reservedKey == "source-arrowhead" {
- attrs = edge.SrcArrowhead
- } else {
- attrs = edge.DstArrowhead
+ case "grid-rows":
+ if inlined(attrs.GridRows) {
+ attrs.GridRows.MapKey.SetScalar(mk.Value.ScalarBox())
+ return nil
}
- if attrs != nil {
+ case "grid-columns":
+ if inlined(attrs.GridColumns) {
+ attrs.GridColumns.MapKey.SetScalar(mk.Value.ScalarBox())
+ return nil
+ }
+ case "grid-gap":
+ if inlined(attrs.GridGap) {
+ attrs.GridGap.MapKey.SetScalar(mk.Value.ScalarBox())
+ return nil
+ }
+ case "vertical-gap":
+ if inlined(attrs.VerticalGap) {
+ attrs.VerticalGap.MapKey.SetScalar(mk.Value.ScalarBox())
+ return nil
+ }
+ case "horizontal-gap":
+ if inlined(attrs.HorizontalGap) {
+ attrs.HorizontalGap.MapKey.SetScalar(mk.Value.ScalarBox())
+ return nil
+ }
+ case "source-arrowhead", "target-arrowhead":
+ var arrowhead *d2graph.Attributes
+ if reservedKey == "source-arrowhead" {
+ arrowhead = edge.SrcArrowhead
+ } else {
+ arrowhead = edge.DstArrowhead
+ }
+ if arrowhead != nil {
if reservedTargetKey == "" {
if len(mk.Key.Path[reservedIndex:]) != 2 {
return errors.New("malformed style setting, expected 2 part path")
@@ -329,13 +363,13 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
switch reservedTargetKey {
case "shape":
- if attrs.Shape.MapKey != nil {
- attrs.Shape.MapKey.SetScalar(mk.Value.ScalarBox())
+ if inlined(&arrowhead.Shape) {
+ arrowhead.Shape.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "label":
- if attrs.Label.MapKey != nil {
- attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
+ if inlined(&arrowhead.Label) {
+ arrowhead.Label.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
}
@@ -349,98 +383,98 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
switch reservedTargetKey {
case "opacity":
- if attrs.Style.Opacity != nil {
+ if inlined(attrs.Style.Opacity) {
attrs.Style.Opacity.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "stroke":
- if attrs.Style.Stroke != nil {
+ if inlined(attrs.Style.Stroke) {
attrs.Style.Stroke.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "fill":
- if attrs.Style.Fill != nil {
+ if inlined(attrs.Style.Fill) {
attrs.Style.Fill.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "stroke-width":
- if attrs.Style.StrokeWidth != nil {
+ if inlined(attrs.Style.StrokeWidth) {
attrs.Style.StrokeWidth.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "stroke-dash":
- if attrs.Style.StrokeDash != nil {
+ if inlined(attrs.Style.StrokeDash) {
attrs.Style.StrokeDash.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "border-radius":
- if attrs.Style.BorderRadius != nil {
+ if inlined(attrs.Style.BorderRadius) {
attrs.Style.BorderRadius.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "shadow":
- if attrs.Style.Shadow != nil {
+ if inlined(attrs.Style.Shadow) {
attrs.Style.Shadow.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "3d":
- if attrs.Style.ThreeDee != nil {
+ if inlined(attrs.Style.ThreeDee) {
attrs.Style.ThreeDee.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "multiple":
- if attrs.Style.Multiple != nil {
+ if inlined(attrs.Style.Multiple) {
attrs.Style.Multiple.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "double-border":
- if attrs.Style.DoubleBorder != nil {
+ if inlined(attrs.Style.DoubleBorder) {
attrs.Style.DoubleBorder.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "font":
- if attrs.Style.Font != nil {
+ if inlined(attrs.Style.Font) {
attrs.Style.Font.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "font-size":
- if attrs.Style.FontSize != nil {
+ if inlined(attrs.Style.FontSize) {
attrs.Style.FontSize.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "font-color":
- if attrs.Style.FontColor != nil {
+ if inlined(attrs.Style.FontColor) {
attrs.Style.FontColor.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "animated":
- if attrs.Style.Animated != nil {
+ if inlined(attrs.Style.Animated) {
attrs.Style.Animated.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "bold":
- if attrs.Style.Bold != nil {
+ if inlined(attrs.Style.Bold) {
attrs.Style.Bold.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "italic":
- if attrs.Style.Italic != nil {
+ if inlined(attrs.Style.Italic) {
attrs.Style.Italic.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "underline":
- if attrs.Style.Underline != nil {
+ if inlined(attrs.Style.Underline) {
attrs.Style.Underline.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
case "fill-pattern":
- if attrs.Style.FillPattern != nil {
+ if inlined(attrs.Style.FillPattern) {
attrs.Style.FillPattern.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
}
case "label":
- if attrs.Label.MapKey != nil {
+ if inlined(&attrs.Label) {
attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
@@ -639,7 +673,7 @@ func renameConflictsToParent(g *d2graph.Graph, key *d2ast.KeyPath) (*d2graph.Gra
if !ok {
return g, nil
}
- if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
+ if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
return g, nil
}
@@ -936,7 +970,7 @@ func deleteObject(g *d2graph.Graph, key *d2ast.KeyPath, obj *d2graph.Object) (*d
isSpecial := isReserved || x.Unbox().ScalarString() == "_"
return !isSpecial
})
- if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
+ if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
deleteFromMap(ref.Scope, ref.MapKey)
} else if len(withoutSpecial) == 0 {
hoistRefChildren(g, key, ref)
@@ -965,7 +999,7 @@ func deleteObject(g *d2graph.Graph, key *d2ast.KeyPath, obj *d2graph.Object) (*d
} else if ref.InEdge() {
edge := ref.MapKey.Edges[ref.MapKeyEdgeIndex]
- if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass {
+ if obj.Shape.Value == d2target.ShapeSQLTable || obj.Shape.Value == d2target.ShapeClass {
if ref.MapKeyEdgeDest() {
ensureNode(g, refEdges, ref.ScopeObj, ref.Scope, ref.MapKey, edge.Src, true)
} else {
diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go
index 736fbcf69..ad80ed30d 100644
--- a/d2oracle/edit_test.go
+++ b/d2oracle/edit_test.go
@@ -50,11 +50,11 @@ func TestCreate(t *testing.T) {
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected g.Objects[0].Attributes.Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Attributes.Label.MapKey.Value)
+ if g.Objects[0].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected g.Objects[0].Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Label.MapKey.Value)
}
- if d2format.Format(g.Objects[0].Attributes.Label.MapKey.Key) != "square" {
- t.Fatalf("expected g.Objects[0].Attributes.Label.Node.Key to be square: %#v", g.Objects[0].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[0].Label.MapKey.Key) != "square" {
+ t.Fatalf("expected g.Objects[0].Label.Node.Key to be square: %#v", g.Objects[0].Label.MapKey.Key)
}
},
},
@@ -92,11 +92,11 @@ x 2
if g.Objects[2].AbsID() != "b.c.square" {
t.Fatalf("bad absolute ID: %#v", g.Objects[2].AbsID())
}
- if d2format.Format(g.Objects[2].Attributes.Label.MapKey.Key) != "b.c.square" {
- t.Fatalf("bad mapkey: %#v", g.Objects[2].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[2].Label.MapKey.Key) != "b.c.square" {
+ t.Fatalf("bad mapkey: %#v", g.Objects[2].Label.MapKey.Key)
}
- if g.Objects[2].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected nil mapkey value: %#v", g.Objects[2].Attributes.Label.MapKey.Value)
+ if g.Objects[2].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected nil mapkey value: %#v", g.Objects[2].Label.MapKey.Value)
}
},
},
@@ -116,11 +116,11 @@ square 2
if g.Objects[1].ID != "square 2" {
t.Fatalf("expected g.Objects[1].ID to be square 2: %#v", g.Objects[1])
}
- if g.Objects[1].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected g.Objects[1].Attributes.Label.Node.Value.Unbox() == nil: %#v", g.Objects[1].Attributes.Label.MapKey.Value)
+ if g.Objects[1].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected g.Objects[1].Label.Node.Value.Unbox() == nil: %#v", g.Objects[1].Label.MapKey.Value)
}
- if d2format.Format(g.Objects[1].Attributes.Label.MapKey.Key) != "square 2" {
- t.Fatalf("expected g.Objects[1].Attributes.Label.Node.Key to be square 2: %#v", g.Objects[1].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[1].Label.MapKey.Key) != "square 2" {
+ t.Fatalf("expected g.Objects[1].Label.Node.Key to be square 2: %#v", g.Objects[1].Label.MapKey.Key)
}
},
},
@@ -160,11 +160,11 @@ x.y.z.square 2
if g.Objects[3].ID != "square" {
t.Fatalf("expected g.Objects[3].ID to be square: %#v", g.Objects[3])
}
- if g.Objects[3].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected g.Objects[3].Attributes.Label.Node.Value.Unbox() == nil: %#v", g.Objects[3].Attributes.Label.MapKey.Value)
+ if g.Objects[3].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected g.Objects[3].Label.Node.Value.Unbox() == nil: %#v", g.Objects[3].Label.MapKey.Value)
}
- if d2format.Format(g.Objects[3].Attributes.Label.MapKey.Key) != "square" {
- t.Fatalf("expected g.Objects[3].Attributes.Label.Node.Key to be square: %#v", g.Objects[3].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[3].Label.MapKey.Key) != "square" {
+ t.Fatalf("expected g.Objects[3].Label.Node.Key to be square: %#v", g.Objects[3].Label.MapKey.Key)
}
},
},
@@ -188,11 +188,11 @@ x.y.z.square 2
if g.Objects[4].ID != "square 2" {
t.Fatalf("expected g.Objects[4].ID to be square 2: %#v", g.Objects[4])
}
- if g.Objects[4].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected g.Objects[4].Attributes.Label.Node.Value.Unbox() == nil: %#v", g.Objects[4].Attributes.Label.MapKey.Value)
+ if g.Objects[4].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected g.Objects[4].Label.Node.Value.Unbox() == nil: %#v", g.Objects[4].Label.MapKey.Value)
}
- if d2format.Format(g.Objects[4].Attributes.Label.MapKey.Key) != "square 2" {
- t.Fatalf("expected g.Objects[4].Attributes.Label.Node.Key to be square 2: %#v", g.Objects[4].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[4].Label.MapKey.Key) != "square 2" {
+ t.Fatalf("expected g.Objects[4].Label.Node.Key to be square 2: %#v", g.Objects[4].Label.MapKey.Key)
}
},
},
@@ -234,8 +234,8 @@ x.y.z.square 2
if g.Objects[13].ID != "square 11" {
t.Fatalf("expected g.Objects[13].ID to be square 11: %#v", g.Objects[13])
}
- if d2format.Format(g.Objects[13].Attributes.Label.MapKey.Key) != "square 11" {
- t.Fatalf("expected g.Objects[13].Attributes.Label.Node.Key to be square 11: %#v", g.Objects[13].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[13].Label.MapKey.Key) != "square 11" {
+ t.Fatalf("expected g.Objects[13].Label.Node.Key to be square 11: %#v", g.Objects[13].Label.MapKey.Key)
}
},
},
@@ -517,11 +517,11 @@ func TestSet(t *testing.T) {
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Label.MapKey.Value.Unbox() != nil {
- t.Fatalf("expected g.Objects[0].Attributes.Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Attributes.Label.MapKey.Value)
+ if g.Objects[0].Label.MapKey.Value.Unbox() != nil {
+ t.Fatalf("expected g.Objects[0].Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Label.MapKey.Value)
}
- if d2format.Format(g.Objects[0].Attributes.Label.MapKey.Key) != "square" {
- t.Fatalf("expected g.Objects[0].Attributes.Label.Node.Key to be square: %#v", g.Objects[0].Attributes.Label.MapKey.Key)
+ if d2format.Format(g.Objects[0].Label.MapKey.Key) != "square" {
+ t.Fatalf("expected g.Objects[0].Label.Node.Key to be square: %#v", g.Objects[0].Label.MapKey.Key)
}
},
},
@@ -546,8 +546,8 @@ func TestSet(t *testing.T) {
if g.Edges[0].Dst.ID != "y" {
t.Fatalf("expected g.Edges[0].Dst.ID == y: %#v", g.Edges[0].Dst.ID)
}
- if g.Edges[0].Attributes.Label.Value != "two" {
- t.Fatalf("expected g.Edges[0].Attributes.Label.Value == two: %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "two" {
+ t.Fatalf("expected g.Edges[0].Label.Value == two: %#v", g.Edges[0].Label.Value)
}
},
},
@@ -566,8 +566,8 @@ func TestSet(t *testing.T) {
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value != d2target.ShapeSquare {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value == square: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value != d2target.ShapeSquare {
+ t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
}
},
},
@@ -586,8 +586,8 @@ func TestSet(t *testing.T) {
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value != d2target.ShapeCircle {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value == circle: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value != d2target.ShapeCircle {
+ t.Fatalf("expected g.Objects[0].Shape.Value == circle: %#v", g.Objects[0].Shape.Value)
}
},
},
@@ -606,7 +606,7 @@ func TestSet(t *testing.T) {
if len(g.Objects) != 1 {
t.Fatalf("expected 1 object but got %#v", len(g.Objects))
}
- f, err := strconv.ParseFloat(g.Objects[0].Attributes.Style.Opacity.Value, 64)
+ f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
if err != nil || f != 0.2 {
t.Fatalf("expected g.Objects[0].Map.Nodes[0].MapKey.Value.Number.Value.Float64() == 0.2: %#v", f)
}
@@ -652,7 +652,7 @@ func TestSet(t *testing.T) {
if len(g.AST.Nodes[0].MapKey.Value.Map.Nodes) != 1 {
t.Fatalf("expected 1 node within square but got %v", len(g.AST.Nodes[0].MapKey.Value.Map.Nodes))
}
- f, err := strconv.ParseFloat(g.Objects[0].Attributes.Style.Opacity.Value, 64)
+ f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
if err != nil || f != 0.2 {
t.Fatal(err, f)
}
@@ -670,7 +670,7 @@ func TestSet(t *testing.T) {
if len(g.AST.Nodes) != 1 {
t.Fatal(g.AST)
}
- f, err := strconv.ParseFloat(g.Objects[0].Attributes.Style.Opacity.Value, 64)
+ f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
if err != nil || f != 0.2 {
t.Fatal(err, f)
}
@@ -689,7 +689,7 @@ square.style.opacity: 0.2
if len(g.AST.Nodes) != 2 {
t.Fatal(g.AST)
}
- f, err := strconv.ParseFloat(g.Objects[0].Attributes.Style.Opacity.Value, 64)
+ f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
if err != nil || f != 0.2 {
t.Fatal(err, f)
}
@@ -859,6 +859,85 @@ square.style.opacity: 0.2
exp: `square: {
style.fill-pattern: grain
}
+`,
+ },
+ {
+ name: "classes-style",
+ text: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.class: a
+`,
+ key: `b.style.fill`,
+ value: go2.Pointer(`green`),
+ exp: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.class: a
+b.style.fill: green
+`,
+ },
+ {
+ name: "dupe-classes-style",
+ text: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.class: a
+b.style.fill: red
+`,
+ key: `b.style.fill`,
+ value: go2.Pointer(`green`),
+ exp: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.class: a
+b.style.fill: green
+`,
+ },
+ {
+ name: "unapplied-classes-style",
+ text: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.style.fill: red
+`,
+ key: `b.style.fill`,
+ value: go2.Pointer(`green`),
+ exp: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b.style.fill: green
+`,
+ },
+ {
+ name: "unapplied-classes-style-2",
+ text: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b
+`,
+ key: `b.style.fill`,
+ value: go2.Pointer(`green`),
+ exp: `classes: {
+ a: {
+ style.fill: red
+ }
+}
+b: {style.fill: green}
`,
},
{
@@ -877,8 +956,8 @@ square.style.opacity: 0.2
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value == d2target.ShapeSquare {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value == square: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value == d2target.ShapeSquare {
+ t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
}
},
},
@@ -897,8 +976,8 @@ square.style.opacity: 0.2
if g.Objects[0].ID != "square" {
t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
}
- if g.Objects[0].Attributes.Shape.Value == d2target.ShapeSquare {
- t.Fatalf("expected g.Objects[0].Attributes.Shape.Value == square: %#v", g.Objects[0].Attributes.Shape.Value)
+ if g.Objects[0].Shape.Value == d2target.ShapeSquare {
+ t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
}
},
},
@@ -920,8 +999,8 @@ square.style.opacity: 0.2
if g.Objects[0].ID != "square" {
t.Fatal(g.Objects[0])
}
- if g.Objects[0].Attributes.Label.Value == "I am deeply CONCERNED and I want something GOOD for BREAKFAST!" {
- t.Fatal(g.Objects[0].Attributes.Label.Value)
+ if g.Objects[0].Label.Value == "I am deeply CONCERNED and I want something GOOD for BREAKFAST!" {
+ t.Fatal(g.Objects[0].Label.Value)
}
},
},
@@ -1036,8 +1115,8 @@ z: {
if len(g.Edges) != 2 {
t.Fatalf("expected 2 edges: %#v", g.Edges)
}
- if g.Edges[0].Attributes.Label.Value != "two" {
- t.Fatalf("expected g.Edges[0].Attributes.Label.Value == two: %#v", g.Edges[0].Attributes.Label.Value)
+ if g.Edges[0].Label.Value != "two" {
+ t.Fatalf("expected g.Edges[0].Label.Value == two: %#v", g.Edges[0].Label.Value)
}
},
},
@@ -1054,8 +1133,8 @@ z: {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
- if g.Objects[0].Attributes.Icon.String() != "https://icons.terrastruct.com/essentials/087-menu.svg" {
- t.Fatal(g.Objects[0].Attributes.Icon.String())
+ if g.Objects[0].Icon.String() != "https://icons.terrastruct.com/essentials/087-menu.svg" {
+ t.Fatal(g.Objects[0].Icon.String())
}
},
},
@@ -1133,7 +1212,7 @@ z: {
assert.JSON(t, 3, len(g.Objects))
assert.JSON(t, 1, len(g.Edges))
assert.JSON(t, "q", g.Edges[0].Src.ID)
- assert.JSON(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value)
+ assert.JSON(t, "0.4", g.Edges[0].Style.Opacity.Value)
},
},
{
@@ -1309,8 +1388,8 @@ a.b -> a.c: {style.animated: true}
if g.Edges[0].Src.ID != "q" {
t.Fatal(g.Edges[0].Src.ID)
}
- if g.Edges[0].Attributes.Style.Opacity.Value != "0.4" {
- t.Fatal(g.Edges[0].Attributes.Style.Opacity.Value)
+ if g.Edges[0].Style.Opacity.Value != "0.4" {
+ t.Fatal(g.Edges[0].Style.Opacity.Value)
}
},
},
diff --git a/d2plugin/plugin_features.go b/d2plugin/plugin_features.go
index 76e1825b4..c6db35326 100644
--- a/d2plugin/plugin_features.go
+++ b/d2plugin/plugin_features.go
@@ -8,7 +8,7 @@ import (
type PluginFeature string
-// When this is true, objects can set ther `near` key to another object
+// When this is true, objects can set their `near` key to another object
// When this is false, objects can only set `near` to constants
const NEAR_OBJECT PluginFeature = "near_object"
@@ -33,19 +33,19 @@ func FeatureSupportCheck(info *PluginInfo, g *d2graph.Graph) error {
}
for _, obj := range g.Objects {
- if obj.Attributes.Top != nil || obj.Attributes.Left != nil {
+ if obj.Top != nil || obj.Left != nil {
if _, ok := featureMap[TOP_LEFT]; !ok {
return fmt.Errorf(`Object "%s" has attribute "top" and/or "left" set, but layout engine "%s" does not support locked positions.`, obj.AbsID(), info.Name)
}
}
- if (obj.Attributes.Width != nil || obj.Attributes.Height != nil) && len(obj.ChildrenArray) > 0 {
+ if (obj.WidthAttr != nil || obj.HeightAttr != nil) && len(obj.ChildrenArray) > 0 {
if _, ok := featureMap[CONTAINER_DIMENSIONS]; !ok {
return fmt.Errorf(`Object "%s" has attribute "width" and/or "height" set, but layout engine "%s" does not support dimensions set on containers.`, obj.AbsID(), info.Name)
}
}
- if obj.Attributes.NearKey != nil {
- _, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey))
+ if obj.NearKey != nil {
+ _, isKey := g.Root.HasChild(d2graph.Key(obj.NearKey))
if isKey {
if _, ok := featureMap[NEAR_OBJECT]; !ok {
return fmt.Errorf(`Object "%s" has "near" set to another object, but layout engine "%s" only supports constant values for "near".`, obj.AbsID(), info.Name)
diff --git a/d2renderers/d2fonts/README.md b/d2renderers/d2fonts/README.md
index 0cd528d42..da46714a3 100644
--- a/d2renderers/d2fonts/README.md
+++ b/d2renderers/d2fonts/README.md
@@ -1,7 +1,7 @@
# d2fonts
The SVG renderer embeds fonts directly into the SVG as base64 data. This is to give
-determinstic outputs and load without a network call.
+deterministic outputs and load without a network call.
To include your own font, e.g. `Helvetica`, you must include the Truetype glyphs:
- `./ttf/Helvetica-Bold.ttf`
diff --git a/d2renderers/d2fonts/d2fonts.go b/d2renderers/d2fonts/d2fonts.go
index a0ae866ae..058371017 100644
--- a/d2renderers/d2fonts/d2fonts.go
+++ b/d2renderers/d2fonts/d2fonts.go
@@ -64,9 +64,10 @@ const (
FONT_SIZE_XXL = 28
FONT_SIZE_XXXL = 32
- FONT_STYLE_REGULAR FontStyle = "regular"
- FONT_STYLE_BOLD FontStyle = "bold"
- FONT_STYLE_ITALIC FontStyle = "italic"
+ FONT_STYLE_REGULAR FontStyle = "regular"
+ FONT_STYLE_BOLD FontStyle = "bold"
+ FONT_STYLE_SEMIBOLD FontStyle = "semibold"
+ FONT_STYLE_ITALIC FontStyle = "italic"
SourceSansPro FontFamily = "SourceSansPro"
SourceCodePro FontFamily = "SourceCodePro"
@@ -86,6 +87,7 @@ var FontSizes = []int{
var FontStyles = []FontStyle{
FONT_STYLE_REGULAR,
FONT_STYLE_BOLD,
+ FONT_STYLE_SEMIBOLD,
FONT_STYLE_ITALIC,
}
@@ -101,6 +103,9 @@ var sourceSansProRegularBase64 string
//go:embed encoded/SourceSansPro-Bold.txt
var sourceSansProBoldBase64 string
+//go:embed encoded/SourceSansPro-Semibold.txt
+var sourceSansProSemiboldBase64 string
+
//go:embed encoded/SourceSansPro-Italic.txt
var sourceSansProItalicBase64 string
@@ -110,6 +115,9 @@ var sourceCodeProRegularBase64 string
//go:embed encoded/SourceCodePro-Bold.txt
var sourceCodeProBoldBase64 string
+//go:embed encoded/SourceCodePro-Semibold.txt
+var sourceCodeProSemiboldBase64 string
+
//go:embed encoded/SourceCodePro-Italic.txt
var sourceCodeProItalicBase64 string
@@ -135,6 +143,10 @@ func init() {
Family: SourceSansPro,
Style: FONT_STYLE_BOLD,
}: sourceSansProBoldBase64,
+ {
+ Family: SourceSansPro,
+ Style: FONT_STYLE_SEMIBOLD,
+ }: sourceSansProSemiboldBase64,
{
Family: SourceSansPro,
Style: FONT_STYLE_ITALIC,
@@ -147,6 +159,10 @@ func init() {
Family: SourceCodePro,
Style: FONT_STYLE_BOLD,
}: sourceCodeProBoldBase64,
+ {
+ Family: SourceCodePro,
+ Style: FONT_STYLE_SEMIBOLD,
+ }: sourceCodeProSemiboldBase64,
{
Family: SourceCodePro,
Style: FONT_STYLE_ITALIC,
@@ -164,6 +180,11 @@ func init() {
Family: HandDrawn,
Style: FONT_STYLE_BOLD,
}: fuzzyBubblesBoldBase64,
+ {
+ Family: HandDrawn,
+ Style: FONT_STYLE_SEMIBOLD,
+ // This font has no semibold, so just reuse bold
+ }: fuzzyBubblesBoldBase64,
}
for k, v := range FontEncodings {
@@ -195,6 +216,14 @@ func init() {
Family: SourceCodePro,
Style: FONT_STYLE_BOLD,
}] = b
+ b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Semibold.ttf")
+ if err != nil {
+ panic(err)
+ }
+ FontFaces[Font{
+ Family: SourceCodePro,
+ Style: FONT_STYLE_SEMIBOLD,
+ }] = b
b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Italic.ttf")
if err != nil {
panic(err)
@@ -211,6 +240,14 @@ func init() {
Family: SourceSansPro,
Style: FONT_STYLE_BOLD,
}] = b
+ b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Semibold.ttf")
+ if err != nil {
+ panic(err)
+ }
+ FontFaces[Font{
+ Family: SourceSansPro,
+ Style: FONT_STYLE_SEMIBOLD,
+ }] = b
b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Italic.ttf")
if err != nil {
panic(err)
@@ -239,6 +276,10 @@ func init() {
Family: HandDrawn,
Style: FONT_STYLE_BOLD,
}] = b
+ FontFaces[Font{
+ Family: HandDrawn,
+ Style: FONT_STYLE_SEMIBOLD,
+ }] = b
}
var D2_FONT_TO_FAMILY = map[string]FontFamily{
@@ -259,7 +300,7 @@ func AddFontStyle(font Font, style FontStyle, ttf []byte) error {
return nil
}
-func AddFontFamily(name string, regularTTF, italicTTF, boldTTF []byte) (*FontFamily, error) {
+func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []byte) (*FontFamily, error) {
customFontFamily := FontFamily(name)
regularFont := Font{
@@ -316,6 +357,24 @@ func AddFontFamily(name string, regularTTF, italicTTF, boldTTF []byte) (*FontFam
FontEncodings[boldFont] = FontEncodings[fallbackFont]
}
+ semiboldFont := Font{
+ Family: customFontFamily,
+ Style: FONT_STYLE_SEMIBOLD,
+ }
+ if semiboldTTF != nil {
+ err := AddFontStyle(semiboldFont, FONT_STYLE_SEMIBOLD, semiboldTTF)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ fallbackFont := Font{
+ Family: SourceSansPro,
+ Style: FONT_STYLE_SEMIBOLD,
+ }
+ FontFaces[semiboldFont] = FontFaces[fallbackFont]
+ FontEncodings[semiboldFont] = FontEncodings[fallbackFont]
+ }
+
FontFamilies = append(FontFamilies, customFontFamily)
return &customFontFamily, nil
diff --git a/d2renderers/d2fonts/encoded/SourceCodePro-Semibold.txt b/d2renderers/d2fonts/encoded/SourceCodePro-Semibold.txt
new file mode 100644
index 000000000..47a95ed5d
--- /dev/null
+++ b/d2renderers/d2fonts/encoded/SourceCodePro-Semibold.txt
@@ -0,0 +1 @@
+data:application/font-woff;base64,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
diff --git a/d2renderers/d2fonts/encoded/SourceSansPro-Semibold.txt b/d2renderers/d2fonts/encoded/SourceSansPro-Semibold.txt
new file mode 100644
index 000000000..5016f44fa
--- /dev/null
+++ b/d2renderers/d2fonts/encoded/SourceSansPro-Semibold.txt
@@ -0,0 +1 @@
+data:application/font-woff;base64,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NsRLHxJkkFEmmWWRVTbZ5ZBTLrnlkVc++RVQUCGFFVFUMcWVCA+VDMlKKa2MssqFO8qroGK4Hx6rpLIqqqqmuhpqqqW2Ouqqp74GGmqksSaaaqa5FlpqpbU22mqnvQ466qSzLrrqprseeooQKUq0GLHixEvQS2999NVPfwMkGmiQwYYYapjhRhhplNHGGGuc8SaYaJLJpphqmulmmGmW2eaYa575FlhokcWWWGqZ5ZKssNIqq62x1jrrJdtgo00222KrbbbbYadddttjr332O+CgQw474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26H+e646577HnjokceeeOpHP/nZL371m9/94c8wzV/+9o9//ed/guABQBADAADQ7Xvbtm3btm3btm3btm3btm1z9U+A/wRCYEEEFUxwIYQUSmhhAqYJK5zwAbNEEFEkkUURVTTRxRBTLLHFEVc88SWQUCKJJZFUMsmlkFIqqaWRVjrpZZBRJpllkVU22eWQUy655ZFXPvkVUFAhhRVRVDHFlVBSKaWVUVY55VVQUSWVVVFVNdXVUFMttdVRVz31NdBQI4010VQzzbXQUiuttdFWO+110FEnnXXRVTfd9dBTL7310Vc//Q0w0CCDDTHUMMONMNIoo40x1jjjTTDRJJNNMdU0080w0yyzzTHXPPMtsNAiiy2x1DLLrbDSKqutsdY6622w0SabbbHVNtvtsNMuu+2x1z77HXDQIYcdcdQxx51w0imnnXHWOeddcNEll11x1TXX3XDTLbfdcdc99z3w0COPPfHUM8+98NIrr73x1jvvffDRJ5998dU33/3w0y+//fHX/wTBA4IQAAAAsLDOtm3btm3btvHya/vxy29//MU/AQIFCRYiVJhwESJFiRYjVpx4CRIlSZYiVZp0GTJlyZYjV558BQoVKVaiVJlyFSpVqVajVp16DRo1adaiVZt2HTp16dajV59+AwYNGTZi1JhxEyZNmTZj1px5CxYtWbZi1Zp1GzZt2bZj1559Bw4dOXbi1JlzFy5duXbj1p17Dx49efbi1Zt3Hz59+fafIHhKrAMAoCh4P07txsY+wn0EW4lt27Zt27ZfbKeY8cUPfwIIJIhgQggljHAiiCSKaGKIJY54EkgkiWRSSCWNdDLIJItscsglj3wKKKSIYkoopYxyKqikimpqqKWOehpopIlmWmiljXY66KSLbnro1ScZyVgmMpWZzGUhS1nJmj76GWCQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCMMHHPCKWecc6Ff+iEb2apMKE+OytVnvVOjmlSvBvVrQJ3qUolK9VO1quOSK665UZ+C5M4td/qir3KWqzzlrd/ykosM+iZf9SpQwdzzwCNPPPPCK2/84S///hMED4BxGAAAAH+4TLWt1LZt27ZtW0Ft27Zt27atebsT8J3v/eDHQBiB0EC4ID/5ORDsl0CQX/0mgogiiSyKqKKJLoaYYoktjrjiiS+BhBJJLImkkgmWXAoppZJaGmmlk14GGWWSWRZZZZNdDjnlklseeeWTXwEFFVJYEUUVU1wJJZVSWhlllVNeBRVVUlkVVVVTXQ011VJbHXXVU18DDTXSWBNNNdNcCy210lobbbXTXgcdddJZF111010PPfXSWx999dPfAAMNMtgQQw0z3AgjjTJaiFBhwo0x1jjjTTDRJJNNMdU0080w0yyzzTHXPPMtsNAiiy2x1DLLrbDSKqutsdY6622w0SabbbHVNtvtsNMuu+2x1z77HXDQIYcdcdQxx51w0imnnXHWOeddcNEll11x1TXX3XDTLbfdcdc99z3w0COPPfHUM8+98NIrr73x1jvvffDRJ5998dU3v/vDn/7yt3/867//CYIHACEMAACAbavLtm3btm3btm2b/9m2bdu2be9OEP/413+CCobgQggplNDCCCuc8CKIKJLIoogqmuhiiCmW2OKIK574EkgokcSSSCqZ5FJIKZXU0kgrnfQyyCiTzLLIKpvscsgpl9zyyCuf/AooqJDCiiiqmOJKKKmU0sooq5zyKqioksqqqKqa6mqoqZba6qirnvoaaKiRxppoqpnmWmipldbaaKud9jroqJPOuuiqm+566KmX3vroq5/+BhhokMGGGGqY4UYYaZTRxhhrnPEmmGiSyaaYaprpZphpltnmCBBornnmW2ChRRZbYqllllthpVVWW2OtddbbYKNNNttiq22222FnkAC7ggTabY+99tnvgIMOOeyIo4457oSTTjntjLPOOe+Ciy657Iqrrrnuhptuue2Ou+6574GHHnnsiaeeee6Fl1557Y233nnvg48++eyLr7757oeffvntj7//sxSPjVEAAByH//GXbQzZ83Ids+1m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
diff --git a/d2renderers/d2fonts/ttf/SourceCodePro-Semibold.ttf b/d2renderers/d2fonts/ttf/SourceCodePro-Semibold.ttf
new file mode 100644
index 000000000..ae8574628
Binary files /dev/null and b/d2renderers/d2fonts/ttf/SourceCodePro-Semibold.ttf differ
diff --git a/d2renderers/d2fonts/ttf/SourceSansPro-Semibold.ttf b/d2renderers/d2fonts/ttf/SourceSansPro-Semibold.ttf
new file mode 100644
index 000000000..9e6b1b529
Binary files /dev/null and b/d2renderers/d2fonts/ttf/SourceSansPro-Semibold.ttf differ
diff --git a/d2renderers/d2sketch/sketch_test.go b/d2renderers/d2sketch/sketch_test.go
index 5ed75626e..177ed47dc 100644
--- a/d2renderers/d2sketch/sketch_test.go
+++ b/d2renderers/d2sketch/sketch_test.go
@@ -1293,6 +1293,14 @@ queue -> package -> step
callout -> stored_data -> person
diamond -> oval -> circle
hexagon -> cloud
+`,
+ },
+ {
+ name: "long_arrowhead_label",
+ script: `
+a -> b: {
+ target-arrowhead: "a to b with unexpectedly long target arrowhead label"
+}
`,
},
}
diff --git a/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
index 6b58edd00..c673e8532 100644
--- a/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
+++ b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
@@ -1,9 +1,9 @@
-
\ No newline at end of file
diff --git a/d2renderers/d2sketch/testdata/all_shapes_dark/sketch.exp.svg b/d2renderers/d2sketch/testdata/all_shapes_dark/sketch.exp.svg
index b2c01767e..f8873eebd 100644
--- a/d2renderers/d2sketch/testdata/all_shapes_dark/sketch.exp.svg
+++ b/d2renderers/d2sketch/testdata/all_shapes_dark/sketch.exp.svg
@@ -1,9 +1,9 @@
-linux: because a PC is a terrible thing to waste
-linux: because a PC is a terrible thing to waste
-Five is a sufficiently close approximation to infinity.
-linux: because a PC is a terrible thing to waste
-Oldest message
@@ -843,7 +841,7 @@Last message
Next message will be
inserted here
Oldest message
@@ -843,7 +841,7 @@Last message
Next message will be
inserted here
And other normal markdown stuff
+And other normal markdown stuff
+linux: because a PC is a terrible thing to waste
-linux: because a PC is a terrible thing to waste
-Lorem ipsum dolor
This just disappears
Lorem ipsum dolor
This just disappears
If we can't fix it, it ain't broke.
Dieters live life in the fasting lane.
-If we can't fix it, it ain't broke.
Dieters live life in the fasting lane.
-Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:
-Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:
-Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.
Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.
List items may consist of multiple paragraphs. Each subsequent @@ -876,7 +874,7 @@ sit amet, consectetuer adipiscing elit.
Another item in the same list.
-List items may consist of multiple paragraphs. Each subsequent @@ -876,7 +874,7 @@ sit amet, consectetuer adipiscing elit.
Another item in the same list.
-Festivity Level 1: Your guests are chatting amiably with each other.
test strikethrough test
Festivity Level 1: Your guests are chatting amiably with each other.
test strikethrough test
they did it in style
@@ -847,7 +845,7 @@ }walk into a bar.
-they did it in style
@@ -847,7 +845,7 @@ }walk into a bar.
-Lorem ipsum dolor sit amet, consectetur adipiscing elit, Lorem ipsum dolor sit amet, consectetur adipiscing elit, Lorem ipsum dolor sit amet, consectetur adipiscing elit, Lorem ipsum dolor sit amet, consectetur adipiscing elit, a line of text and an a line of text and an
diff --git a/e2etests/testdata/stable/md_2space_newline/elk/sketch.exp.svg b/e2etests/testdata/stable/md_2space_newline/elk/sketch.exp.svg
index f2f388af5..414d24ad5 100644
--- a/e2etests/testdata/stable/md_2space_newline/elk/sketch.exp.svg
+++ b/e2etests/testdata/stable/md_2space_newline/elk/sketch.exp.svg
@@ -1,10 +1,14 @@
-
diff --git a/e2etests/testdata/stable/md_backslash_newline/dagre/sketch.exp.svg b/e2etests/testdata/stable/md_backslash_newline/dagre/sketch.exp.svg
index 337d33624..8d64bc362 100644
--- a/e2etests/testdata/stable/md_backslash_newline/dagre/sketch.exp.svg
+++ b/e2etests/testdata/stable/md_backslash_newline/dagre/sketch.exp.svg
@@ -1,10 +1,14 @@
-
diff --git a/e2etests/testdata/stable/md_backslash_newline/elk/sketch.exp.svg b/e2etests/testdata/stable/md_backslash_newline/elk/sketch.exp.svg
index 6efc71108..7fec1c30a 100644
--- a/e2etests/testdata/stable/md_backslash_newline/elk/sketch.exp.svg
+++ b/e2etests/testdata/stable/md_backslash_newline/elk/sketch.exp.svg
@@ -1,10 +1,14 @@
-
diff --git a/e2etests/testdata/stable/md_code_block_fenced/dagre/board.exp.json b/e2etests/testdata/stable/md_code_block_fenced/dagre/board.exp.json
index 6dfb60944..c6fade64f 100644
--- a/e2etests/testdata/stable/md_code_block_fenced/dagre/board.exp.json
+++ b/e2etests/testdata/stable/md_code_block_fenced/dagre/board.exp.json
@@ -131,10 +131,8 @@
"id": "(a -> md)[0]",
"src": "a",
"srcArrow": "none",
- "srcLabel": "",
"dst": "md",
"dstArrow": "triangle",
- "dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
@@ -180,10 +178,8 @@
"id": "(md -> b)[0]",
"src": "md",
"srcArrow": "none",
- "srcLabel": "",
"dst": "b",
"dstArrow": "triangle",
- "dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
diff --git a/e2etests/testdata/stable/md_code_block_fenced/dagre/sketch.exp.svg b/e2etests/testdata/stable/md_code_block_fenced/dagre/sketch.exp.svg
index 48e902f8b..8303d7834 100644
--- a/e2etests/testdata/stable/md_code_block_fenced/dagre/sketch.exp.svg
+++ b/e2etests/testdata/stable/md_code_block_fenced/dagre/sketch.exp.svg
@@ -1,23 +1,27 @@
-
-{
@@ -850,7 +848,7 @@
of: "json",
}
-{
@@ -850,7 +848,7 @@
of: "json",
}
code
code
Festivity Level 1: Your guests are chatting amiably with each other.
+test strikethrough test
Festivity Level 1: Your guests are chatting amiably with each other.
+test strikethrough test
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.
-A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.
-Here is an example of AppleScript:
@@ -852,7 +850,7 @@ end tellA code block continues until it reaches a line that is not indented (or the end of the article).
-Here is an example of AppleScript:
@@ -852,7 +850,7 @@ end tellA code block continues until it reaches a line that is not indented (or the end of the article).
-markdown text expanded to 800x400
+markdown text expanded to 800x400
markdown text expanded to 800x400
+markdown text expanded to 800x400
床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。
-床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。
-夏になると、とても暑くて、人々は汗を流しています。
여름에는 매우 더워서 사람들은 땀을 흘립니다.
-夏になると、とても暑くて、人々は汗を流しています。
여름에는 매우 더워서 사람들은 땀을 흘립니다.
-