diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index a8d27e47b..10db03527 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -7,3 +7,4 @@
#### Bugfixes ⛑️
- `d2 fmt` only rewrites if it has changes, instead of always rewriting. [#470](https://github.com/terrastruct/d2/pull/470)
+- Fixed an issue where text could overflow in sql_table shapes. [#458](https://github.com/terrastruct/d2/pull/458)
diff --git a/d2compiler/compile.go b/d2compiler/compile.go
index c49a95a43..901f6bcee 100644
--- a/d2compiler/compile.go
+++ b/d2compiler/compile.go
@@ -669,8 +669,8 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) {
typ = ""
}
d2Col := d2target.SQLColumn{
- Name: col.IDVal,
- Type: typ,
+ Name: d2target.Text{Label: col.IDVal},
+ Type: d2target.Text{Label: typ},
}
// The only map a sql table field could have is to specify constraint
if col.Map != nil {
diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go
index f29db2f16..5923293fc 100644
--- a/d2compiler/compile_test.go
+++ b/d2compiler/compile_test.go
@@ -1465,8 +1465,8 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
- assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name)
- assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name)
+ assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name.Label)
+ assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name.Label)
},
},
{
@@ -1490,8 +1490,8 @@ b`, g.Objects[0].Attributes.Label.Value)
if len(g.Objects[0].ChildrenArray) != 1 {
t.Fatal(g.Objects)
}
- assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name)
- assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name)
+ assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name.Label)
+ assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name.Label)
},
},
{
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index 528df55c1..a6ec263f0 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -995,23 +995,38 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
obj.Width = float64(maxWidth + 100)
case d2target.ShapeSQLTable:
- maxWidth := dims.Width
+ maxNameWidth := 0
+ maxTypeWidth := 0
+ constraintWidth := 0
- for _, c := range obj.SQLTable.Columns {
- cdims := getTextDimensions(mtexts, ruler, c.Text())
- if cdims == nil {
- return fmt.Errorf("dimensions for column %#v not found", c.Text())
+ font := d2fonts.SourceSansPro.Font(d2fonts.FONT_SIZE_L, d2fonts.FONT_STYLE_REGULAR)
+ for i := range obj.SQLTable.Columns {
+ // Note: we want to set dimensions of actual column not the for loop copy of the struct
+ c := &obj.SQLTable.Columns[i]
+
+ nameWidth, nameHeight := ruler.Measure(font, c.Name.Label)
+ c.Name.LabelWidth = nameWidth
+ c.Name.LabelHeight = nameHeight
+ if maxNameWidth < nameWidth {
+ maxNameWidth = nameWidth
}
- lineWidth := cdims.Width
- if maxWidth < lineWidth {
- maxWidth = lineWidth
+
+ typeWidth, typeHeight := ruler.Measure(font, c.Type.Label)
+ c.Type.LabelWidth = typeWidth
+ c.Type.LabelHeight = typeHeight
+ if maxTypeWidth < typeWidth {
+ maxTypeWidth = typeWidth
+ }
+
+ if c.Constraint != "" {
+ // covers UNQ constraint with padding
+ constraintWidth = 60
}
}
// The rows get padded a little due to header font being larger than row font
obj.Height = float64(dims.Height * (len(obj.SQLTable.Columns) + 1))
- // Leave room for padding
- obj.Width = float64(maxWidth + 100)
+ obj.Width = float64(d2target.NamePadding + maxNameWidth + d2target.TypePadding + maxTypeWidth + d2target.TypePadding + constraintWidth)
case d2target.ShapeText, d2target.ShapeCode:
}
diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go
index ce9e79536..9407c0b82 100644
--- a/d2renderers/d2svg/table.go
+++ b/d2renderers/d2svg/table.go
@@ -3,12 +3,12 @@ package d2svg
import (
"fmt"
"io"
- "math"
"strings"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/label"
+ "oss.terrastruct.com/util-go/go2"
)
func tableHeader(box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
@@ -43,13 +43,13 @@ func tableRow(box *geo.Box, nameText, typeText, constraintText string, fontSize,
// e.g. | diagram int FK |
nameTL := label.InsideMiddleLeft.GetPointOnBox(
box,
- prefixPadding,
+ d2target.NamePadding,
box.Width,
fontSize,
)
constraintTR := label.InsideMiddleRight.GetPointOnBox(
box,
- typePadding,
+ d2target.TypePadding,
0,
fontSize,
)
@@ -69,7 +69,7 @@ func tableRow(box *geo.Box, nameText, typeText, constraintText string, fontSize,
// TODO light font
fmt.Sprintf(`%s`,
- nameTL.X+longestNameWidth,
+ nameTL.X+longestNameWidth+2*d2target.NamePadding,
nameTL.Y+fontSize*3/4,
fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, neutralColor),
escapeText(typeText),
@@ -113,18 +113,16 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) {
tableHeader(headerBox, targetShape.Label, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)),
)
- fontSize := float64(targetShape.FontSize)
- var longestNameWidth float64
+ var longestNameWidth int
for _, f := range targetShape.SQLTable.Columns {
- // TODO measure text
- longestNameWidth = math.Max(longestNameWidth, float64(len(f.Name))*fontSize*5/9)
+ longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth)
}
rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
rowBox.TopLeft.Y += headerBox.Height
for _, f := range targetShape.SQLTable.Columns {
fmt.Fprint(writer,
- tableRow(rowBox, f.Name, f.Type, constraintAbbr(f.Constraint), fontSize, longestNameWidth),
+ tableRow(rowBox, f.Name.Label, f.Type.Label, constraintAbbr(f.Constraint), float64(targetShape.FontSize), float64(longestNameWidth)),
)
rowBox.TopLeft.Y += rowHeight
fmt.Fprintf(writer, ``,
@@ -132,5 +130,4 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) {
rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y,
)
}
-
}
diff --git a/d2target/sqltable.go b/d2target/sqltable.go
index 3007dd178..8642e77a0 100644
--- a/d2target/sqltable.go
+++ b/d2target/sqltable.go
@@ -6,20 +6,25 @@ import (
"oss.terrastruct.com/d2/d2renderers/d2fonts"
)
+const (
+ NamePadding = 10
+ TypePadding = 20
+)
+
type SQLTable struct {
Columns []SQLColumn `json:"columns"`
}
type SQLColumn struct {
- Name string `json:"name"`
- Type string `json:"type"`
+ Name Text `json:"name"`
+ Type Text `json:"type"`
Constraint string `json:"constraint"`
Reference string `json:"reference"`
}
func (c SQLColumn) Text() *MText {
return &MText{
- Text: fmt.Sprintf("%s%s%s%s", c.Name, c.Type, c.Constraint, c.Reference),
+ Text: fmt.Sprintf("%s%s%s%s", c.Name.Label, c.Type.Label, c.Constraint, c.Reference),
FontSize: d2fonts.FONT_SIZE_L,
IsBold: false,
IsItalic: false,
diff --git a/e2etests/regression_test.go b/e2etests/regression_test.go
index b9c2399f1..7df5efff3 100644
--- a/e2etests/regression_test.go
+++ b/e2etests/regression_test.go
@@ -52,6 +52,25 @@ foobar: {
}
foo -> foobar`,
},
+ {
+ name: "sql_table_overflow",
+ script: `
+table: sql_table_overflow {
+ shape: sql_table
+ short: loooooooooooooooooooong
+ loooooooooooooooooooong: short
+}
+table_constrained: sql_table_constrained_overflow {
+ shape: sql_table
+ short: loooooooooooooooooooong {
+ constraint: unique
+ }
+ loooooooooooooooooooong: short {
+ constraint: foreign_key
+ }
+}
+`,
+ },
}
runa(t, tcs)
diff --git a/e2etests/testdata/regression/sql_table_overflow/dagre/board.exp.json b/e2etests/testdata/regression/sql_table_overflow/dagre/board.exp.json
new file mode 100644
index 000000000..1607ab8c2
--- /dev/null
+++ b/e2etests/testdata/regression/sql_table_overflow/dagre/board.exp.json
@@ -0,0 +1,198 @@
+{
+ "name": "",
+ "shapes": [
+ {
+ "id": "table",
+ "type": "sql_table",
+ "pos": {
+ "x": 0,
+ "y": 0
+ },
+ "width": 534,
+ "height": 108,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#FFFFFF",
+ "stroke": "#0A0F25",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ }
+ ],
+ "label": "sql_table_overflow",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 211,
+ "labelHeight": 36,
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "table_constrained",
+ "type": "sql_table",
+ "pos": {
+ "x": 594,
+ "y": 0
+ },
+ "width": 594,
+ "height": 108,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#FFFFFF",
+ "stroke": "#0A0F25",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "constraint": "unique",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "constraint": "foreign_key",
+ "reference": ""
+ }
+ ],
+ "label": "sql_table_constrained_overflow",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 350,
+ "labelHeight": 36,
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": []
+}
diff --git a/e2etests/testdata/regression/sql_table_overflow/dagre/sketch.exp.svg b/e2etests/testdata/regression/sql_table_overflow/dagre/sketch.exp.svg
new file mode 100644
index 000000000..789904d74
--- /dev/null
+++ b/e2etests/testdata/regression/sql_table_overflow/dagre/sketch.exp.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json b/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json
new file mode 100644
index 000000000..e7e2b6741
--- /dev/null
+++ b/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json
@@ -0,0 +1,198 @@
+{
+ "name": "",
+ "shapes": [
+ {
+ "id": "table",
+ "type": "sql_table",
+ "pos": {
+ "x": 12,
+ "y": 12
+ },
+ "width": 534,
+ "height": 108,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#FFFFFF",
+ "stroke": "#0A0F25",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ }
+ ],
+ "label": "sql_table_overflow",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 211,
+ "labelHeight": 36,
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "table_constrained",
+ "type": "sql_table",
+ "pos": {
+ "x": 566,
+ "y": 12
+ },
+ "width": 594,
+ "height": 108,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#FFFFFF",
+ "stroke": "#0A0F25",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "constraint": "unique",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "loooooooooooooooooooong",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 242,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "short",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 45,
+ "labelHeight": 26
+ },
+ "constraint": "foreign_key",
+ "reference": ""
+ }
+ ],
+ "label": "sql_table_constrained_overflow",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 350,
+ "labelHeight": 36,
+ "zIndex": 0,
+ "level": 1
+ }
+ ],
+ "connections": []
+}
diff --git a/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg b/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg
new file mode 100644
index 000000000..49ac9e8f8
--- /dev/null
+++ b/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json
index 9f037376a..edd94ad16 100644
--- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json
+++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json
@@ -787,10 +787,10 @@
"id": "t",
"type": "sql_table",
"pos": {
- "x": 4759,
+ "x": 4783,
"y": 150
},
- "width": 210,
+ "width": 161,
"height": 108,
"opacity": 1,
"strokeDash": 0,
@@ -810,14 +810,58 @@
"methods": null,
"columns": [
{
- "name": "id",
- "type": "int",
+ "name": {
+ "label": "id",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 15,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "int",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 23,
+ "labelHeight": 26
+ },
"constraint": "",
"reference": ""
},
{
- "name": "name",
- "type": "varchar",
+ "name": {
+ "label": "name",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 47,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "varchar",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 64,
+ "labelHeight": 26
+ },
"constraint": "",
"reference": ""
}
@@ -1570,7 +1614,7 @@
"y": 2728
},
{
- "x": 4864,
+ "x": 4863.5,
"y": 2728
}
],
@@ -2346,11 +2390,11 @@
"labelPercentage": 0,
"route": [
{
- "x": 4864,
+ "x": 4863.5,
"y": 258
},
{
- "x": 4864,
+ "x": 4863.5,
"y": 2858
}
],
diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg
index 4823fd471..d5ef75817 100644
--- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg
+++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg
@@ -2,7 +2,7 @@