From 5f9b32b442e51a6dbd76917f9690cfb8da976ae0 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 28 Dec 2024 23:17:40 -0700 Subject: [PATCH 1/2] d2lsp: implement autocomplete functions --- d2ast/keywords.go | 1 - d2lsp/completion.go | 500 +++++++++++++++++++++++++++++++++++++++ d2lsp/completion_test.go | 421 ++++++++++++++++++++++++++++++++ 3 files changed, 921 insertions(+), 1 deletion(-) create mode 100644 d2lsp/completion.go create mode 100644 d2lsp/completion_test.go diff --git a/d2ast/keywords.go b/d2ast/keywords.go index 7ad1fae65..f89404507 100644 --- a/d2ast/keywords.go +++ b/d2ast/keywords.go @@ -8,7 +8,6 @@ var ReservedKeywords map[string]struct{} // Non Style/Holder keywords. var SimpleReservedKeywords = map[string]struct{}{ "label": {}, - "desc": {}, "shape": {}, "icon": {}, "constraint": {}, diff --git a/d2lsp/completion.go b/d2lsp/completion.go new file mode 100644 index 000000000..cbdd2706c --- /dev/null +++ b/d2lsp/completion.go @@ -0,0 +1,500 @@ +// Completion implements lsp autocomplete features +// Currently handles: +// - Complete dot and inside maps for reserved keyword holders (style, labels, etc) +// - Complete discrete values for keywords like shape +// - Complete suggestions for formats for keywords like opacity +package d2lsp + +import ( + "strings" + "unicode" + + "oss.terrastruct.com/d2/d2ast" + "oss.terrastruct.com/d2/d2parser" + "oss.terrastruct.com/d2/d2target" +) + +type CompletionKind int + +const ( + KeywordCompletion CompletionKind = iota + StyleCompletion + ShapeCompletion +) + +type CompletionItem struct { + Label string + Kind CompletionKind + Detail string + InsertText string +} + +func GetCompletionItems(text string, line, column int) ([]CompletionItem, error) { + ast, err := d2parser.Parse("", strings.NewReader(text), nil) + if err != nil { + ast, _ = d2parser.Parse("", strings.NewReader(getTextUntilPosition(text, line, column)), nil) + } + + keyword := getKeywordContext(text, ast, line, column) + switch keyword { + case "style", "style.": + return getStyleCompletions(), nil + case "shape", "shape:": + return getShapeCompletions(), nil + case "shadow", "3d", "multiple", "animated", "bold", "italic", "underline", "filled", "double-border", + "shadow:", "3d:", "multiple:", "animated:", "bold:", "italic:", "underline:", "filled:", "double-border:": + return getBooleanCompletions(), nil + case "fill-pattern", "fill-pattern:": + return getFillPatternCompletions(), nil + case "text-transform", "text-transform:": + return getTextTransformCompletions(), nil + case "opacity", "stroke-width", "stroke-dash", "border-radius", "font-size", + "stroke", "fill", "font-color": + return getValueCompletions(keyword), nil + case "opacity:", "stroke-width:", "stroke-dash:", "border-radius:", "font-size:", + "stroke:", "fill:", "font-color:": + return getValueCompletions(keyword[:len(keyword)-1]), nil + case "width", "height", "top", "left": + return getValueCompletions(keyword), nil + case "width:", "height:", "top:", "left:": + return getValueCompletions(keyword[:len(keyword)-1]), nil + case "source-arrowhead", "target-arrowhead": + return getArrowheadCompletions(), nil + case "source-arrowhead.shape:", "target-arrowhead.shape:": + return getArrowheadShapeCompletions(), nil + case "label", "label.": + return getLabelCompletions(), nil + case "icon", "icon:": + return getIconCompletions(), nil + case "icon.": + return getLabelCompletions(), nil + case "near", "near:": + return getNearCompletions(), nil + case "tooltip:", "tooltip": + return getTooltipCompletions(), nil + case "direction:", "direction": + return getDirectionCompletions(), nil + default: + return nil, nil + } +} + +func getTextUntilPosition(text string, line, column int) string { + lines := strings.Split(text, "\n") + if line >= len(lines) { + return text + } + + result := strings.Join(lines[:line], "\n") + if len(result) > 0 { + result += "\n" + } + if column > len(lines[line]) { + result += lines[line] + } else { + result += lines[line][:column] + } + return result +} + +func getKeywordContext(text string, m *d2ast.Map, line, column int) string { + if m == nil { + return "" + } + lines := strings.Split(text, "\n") + + for _, n := range m.Nodes { + if n.MapKey == nil { + continue + } + + var firstPart, lastPart string + var key *d2ast.KeyPath + if len(n.MapKey.Edges) > 0 { + key = n.MapKey.EdgeKey + } else { + key = n.MapKey.Key + } + if key != nil && len(key.Path) > 0 { + firstKey := key.Path[0].Unbox() + if !firstKey.IsUnquoted() { + continue + } + firstPart = firstKey.ScalarString() + + pathLen := len(key.Path) + if pathLen > 1 { + lastKey := key.Path[pathLen-1].Unbox() + if lastKey.IsUnquoted() { + lastPart = lastKey.ScalarString() + _, isHolderLast := d2ast.ReservedKeywordHolders[lastPart] + if !isHolderLast { + _, isHolderLast = d2ast.CompositeReservedKeywords[lastPart] + } + keyRange := n.MapKey.Range + lineText := lines[keyRange.End.Line] + if isHolderLast && isAfterDot(lineText, column) { + return lastPart + "." + } + } + } + } + if _, isBoard := d2ast.BoardKeywords[firstPart]; isBoard { + firstPart = "" + } + + _, isHolder := d2ast.ReservedKeywordHolders[firstPart] + if !isHolder { + _, isHolder = d2ast.CompositeReservedKeywords[firstPart] + } + + // Check nested map + if n.MapKey.Value.Map != nil && isPositionInMap(line, column, n.MapKey.Value.Map) { + if nested := getKeywordContext(text, n.MapKey.Value.Map, line, column); nested != "" { + if isHolder { + // If we got a direct key completion from inside a holder's map, + // prefix it with the holder's name + if strings.HasSuffix(nested, ":") && !strings.Contains(nested, ".") { + return firstPart + "." + strings.TrimSuffix(nested, ":") + ":" + } + } + return nested + } + return firstPart + } + + keyRange := n.MapKey.Range + if line != keyRange.End.Line { + continue + } + + // 1) Skip if cursor is well above/below this key + if line < keyRange.Start.Line || line > keyRange.End.Line { + continue + } + + // 2) If on the start line, skip if before the key + if line == keyRange.Start.Line && column < keyRange.Start.Column { + continue + } + + // 3) If on the end line, allow up to keyRange.End.Column + 1 + if line == keyRange.End.Line && column > keyRange.End.Column+1 { + continue + } + + lineText := lines[keyRange.End.Line] + + if isAfterColon(lineText, column) { + if key != nil && len(key.Path) > 1 { + if isHolder && (firstPart == "source-arrowhead" || firstPart == "target-arrowhead") { + return firstPart + "." + lastPart + ":" + } + + _, isHolder := d2ast.ReservedKeywordHolders[lastPart] + if !isHolder { + return lastPart + } + } + return firstPart + ":" + } + + if isAfterDot(lineText, column) && isHolder { + return firstPart + } + } + + return "" +} + +func isAfterDot(text string, pos int) bool { + return pos > 0 && pos <= len(text) && text[pos-1] == '.' +} + +func isAfterColon(text string, pos int) bool { + if pos < 1 || pos > len(text) { + return false + } + i := pos - 1 + for i >= 0 && unicode.IsSpace(rune(text[i])) { + i-- + } + return i >= 0 && text[i] == ':' +} + +func isPositionInMap(line, column int, m *d2ast.Map) bool { + if m == nil { + return false + } + + mapRange := m.Range + if line < mapRange.Start.Line || line > mapRange.End.Line { + return false + } + + if line == mapRange.Start.Line && column < mapRange.Start.Column { + return false + } + if line == mapRange.End.Line && column > mapRange.End.Column { + return false + } + return true +} + +func getShapeCompletions() []CompletionItem { + items := make([]CompletionItem, 0, len(d2target.Shapes)) + for _, shape := range d2target.Shapes { + item := CompletionItem{ + Label: shape, + Kind: ShapeCompletion, + Detail: "shape", + InsertText: shape, + } + items = append(items, item) + } + return items +} + +func getValueCompletions(property string) []CompletionItem { + switch property { + case "opacity": + return []CompletionItem{{ + Label: "(number between 0.0 and 1.0)", + Kind: KeywordCompletion, + Detail: "e.g. 0.4", + InsertText: "", + }} + case "stroke-width": + return []CompletionItem{{ + Label: "(number between 0 and 15)", + Kind: KeywordCompletion, + Detail: "e.g. 2", + InsertText: "", + }} + case "font-size": + return []CompletionItem{{ + Label: "(number between 8 and 100)", + Kind: KeywordCompletion, + Detail: "e.g. 14", + InsertText: "", + }} + case "stroke-dash": + return []CompletionItem{{ + Label: "(number between 0 and 10)", + Kind: KeywordCompletion, + Detail: "e.g. 5", + InsertText: "", + }} + case "border-radius": + return []CompletionItem{{ + Label: "(number greater than or equal to 0)", + Kind: KeywordCompletion, + Detail: "e.g. 4", + InsertText: "", + }} + case "font-color", "stroke", "fill": + return []CompletionItem{{ + Label: "(color name or hex code)", + Kind: KeywordCompletion, + Detail: "e.g. blue, #ff0000", + InsertText: "", + }} + case "width", "height", "top", "left": + return []CompletionItem{{ + Label: "(pixels)", + Kind: KeywordCompletion, + Detail: "e.g. 400", + InsertText: "", + }} + } + return nil +} + +func getStyleCompletions() []CompletionItem { + items := make([]CompletionItem, 0, len(d2ast.StyleKeywords)) + for keyword := range d2ast.StyleKeywords { + item := CompletionItem{ + Label: keyword, + Kind: StyleCompletion, + Detail: "style property", + InsertText: keyword + ": ", + } + items = append(items, item) + } + return items +} + +func getBooleanCompletions() []CompletionItem { + return []CompletionItem{ + { + Label: "true", + Kind: KeywordCompletion, + Detail: "boolean", + InsertText: "true", + }, + { + Label: "false", + Kind: KeywordCompletion, + Detail: "boolean", + InsertText: "false", + }, + } +} + +func getFillPatternCompletions() []CompletionItem { + items := make([]CompletionItem, 0, len(d2ast.FillPatterns)) + for _, pattern := range d2ast.FillPatterns { + item := CompletionItem{ + Label: pattern, + Kind: KeywordCompletion, + Detail: "fill pattern", + InsertText: pattern, + } + items = append(items, item) + } + return items +} + +func getTextTransformCompletions() []CompletionItem { + items := make([]CompletionItem, 0, len(d2ast.TextTransforms)) + for _, transform := range d2ast.TextTransforms { + item := CompletionItem{ + Label: transform, + Kind: KeywordCompletion, + Detail: "text transform", + InsertText: transform, + } + items = append(items, item) + } + return items +} + +func isOnEmptyLine(text string, line int) bool { + lines := strings.Split(text, "\n") + if line >= len(lines) { + return true + } + + return strings.TrimSpace(lines[line]) == "" +} + +func getLabelCompletions() []CompletionItem { + return []CompletionItem{{ + Label: "near", + Kind: StyleCompletion, + Detail: "label position", + InsertText: "near: ", + }} +} + +func getNearCompletions() []CompletionItem { + items := make([]CompletionItem, 0, len(d2ast.LabelPositionsArray)+1) + + items = append(items, CompletionItem{ + Label: "(object ID)", + Kind: KeywordCompletion, + Detail: "e.g. container.inner_shape", + InsertText: "", + }) + + for _, pos := range d2ast.LabelPositionsArray { + item := CompletionItem{ + Label: pos, + Kind: KeywordCompletion, + Detail: "label position", + InsertText: pos, + } + items = append(items, item) + } + return items +} + +func getTooltipCompletions() []CompletionItem { + return []CompletionItem{ + { + Label: "(markdown)", + Kind: KeywordCompletion, + Detail: "markdown formatted text", + InsertText: "|md\n # Tooltip\n Hello world\n|", + }, + } +} + +func getIconCompletions() []CompletionItem { + return []CompletionItem{ + { + Label: "(URL, e.g. https://icons.terrastruct.com/xyz.svg)", + Kind: KeywordCompletion, + Detail: "icon URL", + InsertText: "https://icons.terrastruct.com/essentials%2F073-add.svg", + }, + } +} + +func getDirectionCompletions() []CompletionItem { + directions := []string{"up", "down", "right", "left"} + items := make([]CompletionItem, len(directions)) + for i, dir := range directions { + items[i] = CompletionItem{ + Label: dir, + Kind: KeywordCompletion, + Detail: "direction", + InsertText: dir, + } + } + return items +} + +func getArrowheadShapeCompletions() []CompletionItem { + arrowheads := []string{ + "triangle", + "arrow", + "diamond", + "circle", + "cf-one", "cf-one-required", + "cf-many", "cf-many-required", + } + + items := make([]CompletionItem, len(arrowheads)) + details := map[string]string{ + "triangle": "default", + "arrow": "like triangle but pointier", + "cf-one": "crows foot one", + "cf-one-required": "crows foot one (required)", + "cf-many": "crows foot many", + "cf-many-required": "crows foot many (required)", + } + + for i, shape := range arrowheads { + detail := details[shape] + if detail == "" { + detail = "arrowhead shape" + } + items[i] = CompletionItem{ + Label: shape, + Kind: ShapeCompletion, + Detail: detail, + InsertText: shape, + } + } + return items +} + +func getArrowheadCompletions() []CompletionItem { + completions := []string{ + "shape", + "label", + "style.filled", + } + + items := make([]CompletionItem, len(completions)) + + for i, shape := range completions { + items[i] = CompletionItem{ + Label: shape, + Kind: ShapeCompletion, + InsertText: shape, + } + } + return items +} diff --git a/d2lsp/completion_test.go b/d2lsp/completion_test.go new file mode 100644 index 000000000..9937ac358 --- /dev/null +++ b/d2lsp/completion_test.go @@ -0,0 +1,421 @@ +package d2lsp + +import ( + "testing" +) + +func TestGetCompletionItems(t *testing.T) { + tests := []struct { + name string + text string + line int + column int + want []CompletionItem + wantErr bool + }{ + { + name: "style dot suggestions", + text: "a.style.", + line: 0, + column: 8, + want: getStyleCompletions(), + }, + { + name: "style map suggestions", + text: `a: { + style. +} +`, + line: 1, + column: 8, + want: getStyleCompletions(), + }, + { + name: "3d style map suggestions", + text: `a.style: { + 3d: +} +`, + line: 1, + column: 5, + want: getBooleanCompletions(), + }, + { + name: "fill pattern style map suggestions", + text: `a.style: { + fill-pattern: +} +`, + line: 1, + column: 15, + want: getFillPatternCompletions(), + }, + { + name: "opacity style map suggestions", + text: `a.style: { + opacity: +} +`, + line: 1, + column: 10, + want: getValueCompletions("opacity"), + }, + { + name: "width dot", + text: `a.width:`, + line: 0, + column: 8, + want: getValueCompletions("width"), + }, + { + name: "layer shape", + text: `a + +layers: { + hey: { + go: { + shape: + } + } +} +`, + line: 5, + column: 12, + want: getShapeCompletions(), + }, + { + name: "stroke width value", + text: `a.style.stroke-width: 1`, + line: 0, + column: 23, + want: nil, + }, + { + name: "no style suggestions", + text: `a.style: +`, + line: 0, + column: 8, + want: nil, + }, + { + name: "style property suggestions", + text: "a -> b: { style. }", + line: 0, + column: 16, + want: getStyleCompletions(), + }, + { + name: "style.opacity value hint", + text: "a -> b: { style.opacity: }", + line: 0, + column: 24, + want: getValueCompletions("opacity"), + }, + { + name: "fill pattern completions", + text: "a -> b: { style.fill-pattern: }", + line: 0, + column: 29, + want: getFillPatternCompletions(), + }, + { + name: "text transform completions", + text: "a -> b: { style.text-transform: }", + line: 0, + column: 31, + want: getTextTransformCompletions(), + }, + { + name: "boolean property completions", + text: "a -> b: { style.shadow: }", + line: 0, + column: 23, + want: getBooleanCompletions(), + }, + { + name: "near position completions", + text: "a -> b: { label.near: }", + line: 0, + column: 21, + want: getNearCompletions(), + }, + { + name: "direction completions", + text: "a -> b: { direction: }", + line: 0, + column: 20, + want: getDirectionCompletions(), + }, + { + name: "icon url completions", + text: "a -> b: { icon: }", + line: 0, + column: 15, + want: getIconCompletions(), + }, + { + name: "icon dot url completions", + text: "a.icon:", + line: 0, + column: 7, + want: getIconCompletions(), + }, + { + name: "icon near completions", + text: "a -> b: { icon.near: }", + line: 0, + column: 20, + want: getNearCompletions(), + }, + { + name: "icon map", + text: `a.icon: { + # here +}`, + line: 1, + column: 2, + want: nil, + }, + { + name: "icon flat dot", + text: `a.icon.`, + line: 0, + column: 7, + want: getLabelCompletions(), + }, + { + name: "label flat dot", + text: `a.label.`, + line: 0, + column: 8, + want: getLabelCompletions(), + }, + { + name: "arrowhead completions - dot syntax", + text: "a -> b: { source-arrowhead. }", + line: 0, + column: 27, + want: getArrowheadCompletions(), + }, + { + name: "arrowhead completions - colon syntax", + text: "a -> b: { source-arrowhead: }", + line: 0, + column: 27, + want: nil, + }, + { + name: "arrowhead completions - map syntax", + text: `a -> b: { + source-arrowhead: { + # here + } +}`, + line: 2, + column: 4, + want: getArrowheadCompletions(), + }, + { + name: "arrowhead shape completions - flat dot syntax", + text: "(a -> b)[0].source-arrowhead.shape:", + line: 0, + column: 35, + want: getArrowheadShapeCompletions(), + }, + { + name: "arrowhead shape completions - dot syntax", + text: "a -> b: { source-arrowhead.shape: }", + line: 0, + column: 33, + want: getArrowheadShapeCompletions(), + }, + { + name: "arrowhead shape completions - map syntax", + text: "a -> b: { source-arrowhead: { shape: } }", + line: 0, + column: 36, + want: getArrowheadShapeCompletions(), + }, + { + name: "width value hint", + text: "a -> b: { width: }", + line: 0, + column: 16, + want: getValueCompletions("width"), + }, + { + name: "height value hint", + text: "a -> b: { height: }", + line: 0, + column: 17, + want: getValueCompletions("height"), + }, + { + name: "tooltip markdown template", + text: "a -> b: { tooltip: }", + line: 0, + column: 18, + want: getTooltipCompletions(), + }, + { + name: "tooltip dot markdown template", + text: "a.tooltip:", + line: 0, + column: 10, + want: getTooltipCompletions(), + }, + { + name: "shape dot suggestions", + text: "a.shape:", + line: 0, + column: 8, + want: getShapeCompletions(), + }, + { + name: "shape suggestions", + text: "a -> b: { shape: }", + line: 0, + column: 16, + want: getShapeCompletions(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetCompletionItems(tt.text, tt.line, tt.column) + if (err != nil) != tt.wantErr { + t.Errorf("GetCompletionItems() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if len(got) != len(tt.want) { + t.Errorf("GetCompletionItems() got %d completions, want %d", len(got), len(tt.want)) + return + } + + // Create maps for easy comparison + gotMap := make(map[string]CompletionItem) + wantMap := make(map[string]CompletionItem) + for _, item := range got { + gotMap[item.Label] = item + } + for _, item := range tt.want { + wantMap[item.Label] = item + } + + // Check that each completion exists and has correct properties + for label, wantItem := range wantMap { + gotItem, exists := gotMap[label] + if !exists { + t.Errorf("missing completion for %q", label) + continue + } + if gotItem.Kind != wantItem.Kind { + t.Errorf("completion %q Kind = %v, want %v", label, gotItem.Kind, wantItem.Kind) + } + if gotItem.Detail != wantItem.Detail { + t.Errorf("completion %q Detail = %v, want %v", label, gotItem.Detail, wantItem.Detail) + } + if gotItem.InsertText != wantItem.InsertText { + t.Errorf("completion %q InsertText = %v, want %v", label, gotItem.InsertText, wantItem.InsertText) + } + } + }) + } +} + +// Helper function to compare CompletionItem slices +func equalCompletions(a, b []CompletionItem) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Label != b[i].Label || + a[i].Kind != b[i].Kind || + a[i].Detail != b[i].Detail || + a[i].InsertText != b[i].InsertText { + return false + } + } + return true +} + +func TestGetArrowheadShapeCompletions(t *testing.T) { + got := getArrowheadShapeCompletions() + + expectedLabels := []string{ + "triangle", "arrow", "diamond", "circle", + "cf-one", "cf-one-required", + "cf-many", "cf-many-required", + } + + if len(got) != len(expectedLabels) { + t.Errorf("getArrowheadShapeCompletions() returned %d items, want %d", len(got), len(expectedLabels)) + return + } + + for i, label := range expectedLabels { + if got[i].Label != label { + t.Errorf("completion[%d].Label = %v, want %v", i, got[i].Label, label) + } + if got[i].Kind != ShapeCompletion { + t.Errorf("completion[%d].Kind = %v, want ShapeCompletion", i, got[i].Kind) + } + if got[i].InsertText != label { + t.Errorf("completion[%d].InsertText = %v, want %v", i, got[i].InsertText, label) + } + } +} + +func TestGetValueCompletions(t *testing.T) { + tests := []struct { + property string + wantLabel string + wantDetail string + }{ + { + property: "opacity", + wantLabel: "(number between 0.0 and 1.0)", + wantDetail: "e.g. 0.4", + }, + { + property: "stroke-width", + wantLabel: "(number between 0 and 15)", + wantDetail: "e.g. 2", + }, + { + property: "font-size", + wantLabel: "(number between 8 and 100)", + wantDetail: "e.g. 14", + }, + { + property: "width", + wantLabel: "(pixels)", + wantDetail: "e.g. 400", + }, + { + property: "stroke", + wantLabel: "(color name or hex code)", + wantDetail: "e.g. blue, #ff0000", + }, + } + + for _, tt := range tests { + t.Run(tt.property, func(t *testing.T) { + got := getValueCompletions(tt.property) + if len(got) != 1 { + t.Fatalf("getValueCompletions(%s) returned %d items, want 1", tt.property, len(got)) + } + if got[0].Label != tt.wantLabel { + t.Errorf("completion.Label = %v, want %v", got[0].Label, tt.wantLabel) + } + if got[0].Detail != tt.wantDetail { + t.Errorf("completion.Detail = %v, want %v", got[0].Detail, tt.wantDetail) + } + if got[0].InsertText != "" { + t.Errorf("completion.InsertText = %v, want empty string", got[0].InsertText) + } + }) + } +} From aa16036f09321abb60d712f980543e3803fe15c2 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 28 Dec 2024 23:39:52 -0700 Subject: [PATCH 2/2] shuffle keywords --- d2ast/keywords.go | 14 +- .../complex-layers/dagre/board.exp.json | 405 +++++++++++++++--- .../complex-layers/dagre/sketch.exp.svg | 335 ++++++++------- .../stable/complex-layers/elk/board.exp.json | 381 ++++++++++++++-- .../stable/complex-layers/elk/sketch.exp.svg | 335 ++++++++------- 5 files changed, 1049 insertions(+), 421 deletions(-) diff --git a/d2ast/keywords.go b/d2ast/keywords.go index f89404507..65f67b775 100644 --- a/d2ast/keywords.go +++ b/d2ast/keywords.go @@ -30,17 +30,17 @@ var SimpleReservedKeywords = map[string]struct{}{ // ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites var ReservedKeywordHolders = map[string]struct{}{ - "style": {}, - "source-arrowhead": {}, - "target-arrowhead": {}, + "style": {}, } // CompositeReservedKeywords are reserved keywords that can hold composites var CompositeReservedKeywords = map[string]struct{}{ - "classes": {}, - "constraint": {}, - "label": {}, - "icon": {}, + "source-arrowhead": {}, + "target-arrowhead": {}, + "classes": {}, + "constraint": {}, + "label": {}, + "icon": {}, } // StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword diff --git a/e2etests/testdata/stable/complex-layers/dagre/board.exp.json b/e2etests/testdata/stable/complex-layers/dagre/board.exp.json index 21f84ea29..ae57a8c07 100644 --- a/e2etests/testdata/stable/complex-layers/dagre/board.exp.json +++ b/e2etests/testdata/stable/complex-layers/dagre/board.exp.json @@ -4,12 +4,54 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "window", + "id": "desc", "type": "rectangle", "pos": { "x": 0, "y": 0 }, + "width": 261, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Multi-layer diagram of a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 216, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "window", + "type": "rectangle", + "pos": { + "x": 321, + "y": 0 + }, "width": 103, "height": 66, "opacity": 1, @@ -49,7 +91,7 @@ "id": "roof", "type": "rectangle", "pos": { - "x": 163, + "x": 484, "y": 0 }, "width": 75, @@ -91,7 +133,7 @@ "id": "garage", "type": "rectangle", "pos": { - "x": 298, + "x": 619, "y": 0 }, "width": 94, @@ -616,9 +658,52 @@ }, { "name": "repair", - "isFolderOnly": true, + "isFolderOnly": false, "fontFamily": "SourceSansPro", - "shapes": [], + "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], "connections": [], "root": { "id": "", @@ -667,11 +752,53 @@ "isFolderOnly": false, "fontFamily": "SourceSansPro", "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 0, + "y": 50 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "find contractors", "type": "rectangle", "pos": { - "x": 10, + "x": 250, "y": 20 }, "width": 341, @@ -713,7 +840,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 40, + "x": 280, "y": 50 }, "width": 110, @@ -755,7 +882,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 210, + "x": 450, "y": 50 }, "width": 111, @@ -842,11 +969,53 @@ "isFolderOnly": false, "fontFamily": "SourceSansPro", "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 0, + "y": 50 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "find contractors", "type": "rectangle", "pos": { - "x": 10, + "x": 250, "y": 20 }, "width": 341, @@ -888,7 +1057,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 40, + "x": 280, "y": 50 }, "width": 110, @@ -930,7 +1099,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 210, + "x": 450, "y": 50 }, "width": 111, @@ -972,7 +1141,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 196, + "x": 436, "y": 266 }, "width": 140, @@ -1038,19 +1207,19 @@ "link": "", "route": [ { - "x": 265.5, + "x": 505.5, "y": 146 }, { - "x": 265.5, + "x": 505.5, "y": 202 }, { - "x": 265.5, + "x": 505.5, "y": 226 }, { - "x": 265.5, + "x": 505.5, "y": 266 } ], @@ -1108,11 +1277,53 @@ "isFolderOnly": false, "fontFamily": "SourceSansPro", "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 0, + "y": 50 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "find contractors", "type": "rectangle", "pos": { - "x": 10, + "x": 250, "y": 20 }, "width": 341, @@ -1154,7 +1365,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 40, + "x": 280, "y": 50 }, "width": 110, @@ -1196,7 +1407,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 210, + "x": 450, "y": 50 }, "width": 111, @@ -1238,7 +1449,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 196, + "x": 436, "y": 266 }, "width": 140, @@ -1280,7 +1491,7 @@ "id": "obtain quotes", "type": "rectangle", "pos": { - "x": 401, + "x": 641, "y": 50 }, "width": 143, @@ -1322,7 +1533,7 @@ "id": "negotiate", "type": "rectangle", "pos": { - "x": 417, + "x": 657, "y": 266 }, "width": 112, @@ -1388,19 +1599,19 @@ "link": "", "route": [ { - "x": 265.5, + "x": 505.5, "y": 146 }, { - "x": 265.5, + "x": 505.5, "y": 202 }, { - "x": 265.5, + "x": 505.5, "y": 226 }, { - "x": 265.5, + "x": 505.5, "y": 266 } ], @@ -1436,19 +1647,19 @@ "link": "", "route": [ { - "x": 472.5, + "x": 712.5, "y": 116 }, { - "x": 472.5, + "x": 712.5, "y": 156 }, { - "x": 472.5, + "x": 712.5, "y": 226 }, { - "x": 472.5, + "x": 712.5, "y": 266 } ], @@ -1506,11 +1717,53 @@ "isFolderOnly": false, "fontFamily": "SourceSansPro", "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 0, + "y": 50 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "find contractors", "type": "rectangle", "pos": { - "x": 10, + "x": 250, "y": 20 }, "width": 341, @@ -1552,7 +1805,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 40, + "x": 280, "y": 50 }, "width": 110, @@ -1594,7 +1847,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 210, + "x": 450, "y": 50 }, "width": 111, @@ -1636,7 +1889,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 196, + "x": 436, "y": 266 }, "width": 140, @@ -1678,7 +1931,7 @@ "id": "obtain quotes", "type": "rectangle", "pos": { - "x": 401, + "x": 641, "y": 50 }, "width": 143, @@ -1720,7 +1973,7 @@ "id": "negotiate", "type": "rectangle", "pos": { - "x": 417, + "x": 657, "y": 266 }, "width": 112, @@ -1762,7 +2015,7 @@ "id": "book the best bid", "type": "rectangle", "pos": { - "x": 389, + "x": 629, "y": 432 }, "width": 167, @@ -1828,19 +2081,19 @@ "link": "", "route": [ { - "x": 265.5, + "x": 505.5, "y": 146 }, { - "x": 265.5, + "x": 505.5, "y": 202 }, { - "x": 265.5, + "x": 505.5, "y": 226 }, { - "x": 265.5, + "x": 505.5, "y": 266 } ], @@ -1876,19 +2129,19 @@ "link": "", "route": [ { - "x": 472.5, + "x": 712.5, "y": 116 }, { - "x": 472.5, + "x": 712.5, "y": 156 }, { - "x": 472.5, + "x": 712.5, "y": 226 }, { - "x": 472.5, + "x": 712.5, "y": 266 } ], @@ -1924,19 +2177,19 @@ "link": "", "route": [ { - "x": 472.5, + "x": 712.5, "y": 332 }, { - "x": 472.5, + "x": 712.5, "y": 372 }, { - "x": 472.5, + "x": 712.5, "y": 392 }, { - "x": 472.5, + "x": 712.5, "y": 432 } ], @@ -1999,12 +2252,54 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "window", + "id": "desc", "type": "rectangle", "pos": { "x": 0, "y": 0 }, + "width": 261, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Multi-layer diagram of a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 216, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "window", + "type": "rectangle", + "pos": { + "x": 321, + "y": 0 + }, "width": 103, "height": 66, "opacity": 1, @@ -2044,7 +2339,7 @@ "id": "roof", "type": "rectangle", "pos": { - "x": 163, + "x": 484, "y": 0 }, "width": 75, @@ -2086,7 +2381,7 @@ "id": "garage", "type": "rectangle", "pos": { - "x": 298, + "x": 619, "y": 0 }, "width": 94, @@ -2128,7 +2423,7 @@ "id": "water", "type": "rectangle", "pos": { - "x": 452, + "x": 773, "y": 0 }, "width": 88, @@ -2170,7 +2465,7 @@ "id": "rain", "type": "rectangle", "pos": { - "x": 600, + "x": 921, "y": 0 }, "width": 73, @@ -2212,7 +2507,7 @@ "id": "thunder", "type": "rectangle", "pos": { - "x": 733, + "x": 1054, "y": 0 }, "width": 103, diff --git a/e2etests/testdata/stable/complex-layers/dagre/sketch.exp.svg b/e2etests/testdata/stable/complex-layers/dagre/sketch.exp.svg index d692ba458..a657284fd 100644 --- a/e2etests/testdata/stable/complex-layers/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/complex-layers/dagre/sketch.exp.svg @@ -1,17 +1,17 @@ -windowroofgarage - - - - -blindsglass + 90.000000%, 100% { + opacity: 0; + } +}@keyframes d2Transition-d2-2913974970-9 { + 0%, 89.990000% { + opacity: 0; + } + 90.000000%, 100.000000% { + opacity: 1; + } +}]]>Multi-layer diagram of a home.windowroofgarage + + + + + +blindsglass -shinglesstarlinkutility hookup +shinglesstarlinkutility hookup -toolsvehicles +toolsvehicles -find contractorscraigslistfacebook - - - - -find contractorssolicit quotescraigslistfacebook - - - - - -find contractorssolicit quotesobtain quotesnegotiatecraigslistfacebook - - - - - - - -find contractorssolicit quotesobtain quotesnegotiatebook the best bidcraigslistfacebook - - - - - - - - -windowroofgaragewaterrainthunder - - - - - - - +How to repair a home. + + +How to repair a home.find contractorscraigslistfacebook + + + + + +How to repair a home.find contractorssolicit quotescraigslistfacebook + + + + + + +How to repair a home.find contractorssolicit quotesobtain quotesnegotiatecraigslistfacebook + + + + + + + + +How to repair a home.find contractorssolicit quotesobtain quotesnegotiatebook the best bidcraigslistfacebook + + + + + + + + + +Multi-layer diagram of a home.windowroofgaragewaterrainthunder + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/complex-layers/elk/board.exp.json b/e2etests/testdata/stable/complex-layers/elk/board.exp.json index d8da04f90..59583bc63 100644 --- a/e2etests/testdata/stable/complex-layers/elk/board.exp.json +++ b/e2etests/testdata/stable/complex-layers/elk/board.exp.json @@ -4,12 +4,54 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "window", + "id": "desc", "type": "rectangle", "pos": { "x": 12, "y": 12 }, + "width": 261, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Multi-layer diagram of a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 216, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "window", + "type": "rectangle", + "pos": { + "x": 293, + "y": 12 + }, "width": 103, "height": 66, "opacity": 1, @@ -49,7 +91,7 @@ "id": "roof", "type": "rectangle", "pos": { - "x": 135, + "x": 416, "y": 12 }, "width": 75, @@ -91,7 +133,7 @@ "id": "garage", "type": "rectangle", "pos": { - "x": 230, + "x": 511, "y": 12 }, "width": 94, @@ -616,9 +658,52 @@ }, { "name": "repair", - "isFolderOnly": true, + "isFolderOnly": false, "fontFamily": "SourceSansPro", - "shapes": [], + "shapes": [ + { + "id": "desc", + "type": "rectangle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], "connections": [], "root": { "id": "", @@ -668,10 +753,52 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "find contractors", + "id": "desc", "type": "rectangle", "pos": { "x": 12, + "y": 62 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "find contractors", + "type": "rectangle", + "pos": { + "x": 232, "y": 12 }, "width": 341, @@ -713,7 +840,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 62, + "x": 282, "y": 62 }, "width": 110, @@ -755,7 +882,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 192, + "x": 412, "y": 62 }, "width": 111, @@ -843,10 +970,52 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "find contractors", + "id": "desc", "type": "rectangle", "pos": { "x": 12, + "y": 62 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "find contractors", + "type": "rectangle", + "pos": { + "x": 232, "y": 12 }, "width": 341, @@ -888,7 +1057,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 62, + "x": 282, "y": 62 }, "width": 110, @@ -930,7 +1099,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 192, + "x": 412, "y": 62 }, "width": 111, @@ -972,7 +1141,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 112, + "x": 332, "y": 248 }, "width": 140, @@ -1038,11 +1207,11 @@ "link": "", "route": [ { - "x": 182.5, + "x": 402.5, "y": 178 }, { - "x": 182.5, + "x": 402.5, "y": 248 } ], @@ -1100,10 +1269,52 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "find contractors", + "id": "desc", "type": "rectangle", "pos": { "x": 12, + "y": 62 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "find contractors", + "type": "rectangle", + "pos": { + "x": 232, "y": 12 }, "width": 341, @@ -1145,7 +1356,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 62, + "x": 282, "y": 62 }, "width": 110, @@ -1187,7 +1398,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 192, + "x": 412, "y": 62 }, "width": 111, @@ -1229,7 +1440,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 112, + "x": 332, "y": 248 }, "width": 140, @@ -1271,7 +1482,7 @@ "id": "obtain quotes", "type": "rectangle", "pos": { - "x": 373, + "x": 593, "y": 112 }, "width": 143, @@ -1313,7 +1524,7 @@ "id": "negotiate", "type": "rectangle", "pos": { - "x": 388, + "x": 608, "y": 248 }, "width": 112, @@ -1379,11 +1590,11 @@ "link": "", "route": [ { - "x": 182.5, + "x": 402.5, "y": 178 }, { - "x": 182.5, + "x": 402.5, "y": 248 } ], @@ -1418,11 +1629,11 @@ "link": "", "route": [ { - "x": 444.5, + "x": 664.5, "y": 178 }, { - "x": 444.5, + "x": 664.5, "y": 248 } ], @@ -1480,10 +1691,52 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "find contractors", + "id": "desc", "type": "rectangle", "pos": { "x": 12, + "y": 62 + }, + "width": 200, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "How to repair a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 155, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "find contractors", + "type": "rectangle", + "pos": { + "x": 232, "y": 12 }, "width": 341, @@ -1525,7 +1778,7 @@ "id": "find contractors.craigslist", "type": "rectangle", "pos": { - "x": 62, + "x": 282, "y": 62 }, "width": 110, @@ -1567,7 +1820,7 @@ "id": "find contractors.facebook", "type": "rectangle", "pos": { - "x": 192, + "x": 412, "y": 62 }, "width": 111, @@ -1609,7 +1862,7 @@ "id": "solicit quotes", "type": "rectangle", "pos": { - "x": 112, + "x": 332, "y": 248 }, "width": 140, @@ -1651,7 +1904,7 @@ "id": "obtain quotes", "type": "rectangle", "pos": { - "x": 373, + "x": 593, "y": 112 }, "width": 143, @@ -1693,7 +1946,7 @@ "id": "negotiate", "type": "rectangle", "pos": { - "x": 388, + "x": 608, "y": 248 }, "width": 112, @@ -1735,7 +1988,7 @@ "id": "book the best bid", "type": "rectangle", "pos": { - "x": 361, + "x": 581, "y": 384 }, "width": 167, @@ -1801,11 +2054,11 @@ "link": "", "route": [ { - "x": 182.5, + "x": 402.5, "y": 178 }, { - "x": 182.5, + "x": 402.5, "y": 248 } ], @@ -1840,11 +2093,11 @@ "link": "", "route": [ { - "x": 444.5, + "x": 664.5, "y": 178 }, { - "x": 444.5, + "x": 664.5, "y": 248 } ], @@ -1879,11 +2132,11 @@ "link": "", "route": [ { - "x": 444.5, + "x": 664.5, "y": 314 }, { - "x": 444.5, + "x": 664.5, "y": 384 } ], @@ -1945,12 +2198,54 @@ "fontFamily": "SourceSansPro", "shapes": [ { - "id": "window", + "id": "desc", "type": "rectangle", "pos": { "x": 12, "y": 12 }, + "width": 261, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Multi-layer diagram of a home.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 216, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "window", + "type": "rectangle", + "pos": { + "x": 293, + "y": 12 + }, "width": 103, "height": 66, "opacity": 1, @@ -1990,7 +2285,7 @@ "id": "roof", "type": "rectangle", "pos": { - "x": 135, + "x": 416, "y": 12 }, "width": 75, @@ -2032,7 +2327,7 @@ "id": "garage", "type": "rectangle", "pos": { - "x": 230, + "x": 511, "y": 12 }, "width": 94, @@ -2074,7 +2369,7 @@ "id": "water", "type": "rectangle", "pos": { - "x": 344, + "x": 625, "y": 12 }, "width": 88, @@ -2116,7 +2411,7 @@ "id": "rain", "type": "rectangle", "pos": { - "x": 452, + "x": 733, "y": 12 }, "width": 73, @@ -2158,7 +2453,7 @@ "id": "thunder", "type": "rectangle", "pos": { - "x": 545, + "x": 826, "y": 12 }, "width": 103, diff --git a/e2etests/testdata/stable/complex-layers/elk/sketch.exp.svg b/e2etests/testdata/stable/complex-layers/elk/sketch.exp.svg index 0fb3f8fad..4f3a0e495 100644 --- a/e2etests/testdata/stable/complex-layers/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/complex-layers/elk/sketch.exp.svg @@ -1,17 +1,17 @@ -windowroofgarage - - - - -blindsglass + 90.000000%, 100% { + opacity: 0; + } +}@keyframes d2Transition-d2-469555219-9 { + 0%, 89.990000% { + opacity: 0; + } + 90.000000%, 100.000000% { + opacity: 1; + } +}]]>Multi-layer diagram of a home.windowroofgarage + + + + + +blindsglass -shinglesstarlinkutility hookup +shinglesstarlinkutility hookup -toolsvehicles +toolsvehicles -find contractorscraigslistfacebook - - - - -find contractorssolicit quotescraigslistfacebook - - - - - -find contractorssolicit quotesobtain quotesnegotiatecraigslistfacebook - - - - - - - -find contractorssolicit quotesobtain quotesnegotiatebook the best bidcraigslistfacebook - - - - - - - - -windowroofgaragewaterrainthunder - - - - - - - +How to repair a home. + + +How to repair a home.find contractorscraigslistfacebook + + + + + +How to repair a home.find contractorssolicit quotescraigslistfacebook + + + + + + +How to repair a home.find contractorssolicit quotesobtain quotesnegotiatecraigslistfacebook + + + + + + + + +How to repair a home.find contractorssolicit quotesobtain quotesnegotiatebook the best bidcraigslistfacebook + + + + + + + + + +Multi-layer diagram of a home.windowroofgaragewaterrainthunder + + + + + + + + \ No newline at end of file