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 @@ + +sql_table_overflowshort +loooooooooooooooooooong +loooooooooooooooooooong +short +sql_table_constrained_overflowshort +loooooooooooooooooooong +UNQloooooooooooooooooooong +short +FK + + + \ 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 @@ + +sql_table_overflowshort +loooooooooooooooooooong +loooooooooooooooooooong +short +sql_table_constrained_overflowshort +loooooooooooooooooooong +UNQloooooooooooooooooooong +short +FK + + + \ 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 @@ usersid +int +name +string +email +string +password +string +last_login +datetime +productsid +int +price +decimal +sku +string +name +string +ordersid +int +user_id +int +product_id +int +shipmentsid +int +order_id +int +tracking_number +string +status +string + + usersid -int -name -string -email -string -password -string -last_login -datetime -productsid -int -price -decimal -sku -string -name -string -ordersid -int -user_id -int -product_id -int -shipmentsid -int -order_id -int -tracking_number -string -status -string - - +usersid +int +name +string +email +string +password +string +last_login +datetime +productsid +int +price +decimal +sku +string +name +string +ordersid +int +user_id +int +product_id +int +shipmentsid +int +order_id +int +tracking_number +string +status +string + +