diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index 767ebf9ed..532aa4df1 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -7,6 +7,8 @@
- `d2 fmt` accepts multiple files to be formatted [#718](https://github.com/terrastruct/d2/issues/718)
+- `font-size` works for `sql_table` and `class` shapes [#769](https://github.com/terrastruct/d2/issues/769)
+
- You can now use the reserved keywords `layers`/`scenarios`/`steps` to define diagrams
with multiple levels of abstractions. [#714](https://github.com/terrastruct/d2/pull/714)
Docs to come soon
@@ -26,6 +28,7 @@
- Ensures shapes with icons have enough padding for their labels. [#702](https://github.com/terrastruct/d2/pull/702)
- `--force-appendix` flag adds an appendix to SVG outputs with tooltips or links. [#761](https://github.com/terrastruct/d2/pull/761)
- `d2 themes` subcommand to list themes. [#760](https://github.com/terrastruct/d2/pull/760)
+- `sql_table` header left-aligned with column [#769](https://github.com/terrastruct/d2/pull/769)
#### Bugfixes ⛑️
diff --git a/d2exporter/export.go b/d2exporter/export.go
index 5a57f0881..418e71370 100644
--- a/d2exporter/export.go
+++ b/d2exporter/export.go
@@ -136,10 +136,10 @@ func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape {
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
- shape.FontSize -= 4
+ shape.FontSize -= d2target.HeaderFontAdd
case d2target.ShapeSQLTable:
shape.SQLTable = *obj.SQLTable
- shape.FontSize -= 4
+ shape.FontSize -= d2target.HeaderFontAdd
}
shape.Label = text.Text
shape.LabelWidth = text.Dimensions.Width
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index f74b14496..d907c1999 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -465,6 +465,11 @@ func (obj *Object) Text() *d2target.MText {
isItalic = true
}
fontSize := d2fonts.FONT_SIZE_M
+
+ if obj.Class != nil || obj.SQLTable != nil {
+ fontSize = d2fonts.FONT_SIZE_L
+ }
+
if obj.OuterSequenceDiagram() == nil {
if obj.IsContainer() {
fontSize = obj.Level().LabelSize()
@@ -477,7 +482,7 @@ func (obj *Object) Text() *d2target.MText {
}
// Class and Table objects have Label set to header
if obj.Class != nil || obj.SQLTable != nil {
- fontSize = d2fonts.FONT_SIZE_XL
+ fontSize += d2target.HeaderFontAdd
}
if obj.Class != nil {
isBold = false
@@ -795,17 +800,22 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
case d2target.ShapeClass:
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)
+ }
+
for _, f := range obj.Class.Fields {
- fdims := GetTextDimensions(mtexts, ruler, f.Text(), go2.Pointer(d2fonts.SourceCodePro))
+ fdims := GetTextDimensions(mtexts, ruler, f.Text(fontSize), go2.Pointer(d2fonts.SourceCodePro))
if fdims == nil {
- return nil, fmt.Errorf("dimensions for class field %#v not found", f.Text())
+ return nil, fmt.Errorf("dimensions for class field %#v not found", f.Text(fontSize))
}
maxWidth = go2.Max(maxWidth, fdims.Width)
}
for _, m := range obj.Class.Methods {
- mdims := GetTextDimensions(mtexts, ruler, m.Text(), go2.Pointer(d2fonts.SourceCodePro))
+ mdims := GetTextDimensions(mtexts, ruler, m.Text(fontSize), go2.Pointer(d2fonts.SourceCodePro))
if mdims == nil {
- return nil, fmt.Errorf("dimensions for class method %#v not found", m.Text())
+ return nil, fmt.Errorf("dimensions for class method %#v not found", m.Text(fontSize))
}
maxWidth = go2.Max(maxWidth, mdims.Width)
}
@@ -820,9 +830,9 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
// All rows should be the same height
var anyRowText *d2target.MText
if len(obj.Class.Fields) > 0 {
- anyRowText = obj.Class.Fields[0].Text()
+ anyRowText = obj.Class.Fields[0].Text(fontSize)
} else if len(obj.Class.Methods) > 0 {
- anyRowText = obj.Class.Methods[0].Text()
+ anyRowText = obj.Class.Methods[0].Text(fontSize)
}
if anyRowText != nil {
rowHeight := GetTextDimensions(mtexts, ruler, anyRowText, go2.Pointer(d2fonts.SourceCodePro)).Height + d2target.VerticalPadding
@@ -836,10 +846,16 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
maxTypeWidth := 0
constraintWidth := 0
+ colFontSize := d2fonts.FONT_SIZE_L
+ if obj.Attributes.Style.FontSize != nil {
+ colFontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ }
+
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]
- ctexts := c.Texts()
+
+ ctexts := c.Texts(colFontSize)
nameDims := GetTextDimensions(mtexts, ruler, ctexts[0], fontFamily)
if nameDims == nil {
@@ -1327,15 +1343,23 @@ func (g *Graph) Texts() []*d2target.MText {
texts = appendTextDedup(texts, obj.Text())
}
if obj.Class != nil {
+ fontSize := d2fonts.FONT_SIZE_L
+ if obj.Attributes.Style.FontSize != nil {
+ fontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ }
for _, field := range obj.Class.Fields {
- texts = appendTextDedup(texts, field.Text())
+ texts = appendTextDedup(texts, field.Text(fontSize))
}
for _, method := range obj.Class.Methods {
- texts = appendTextDedup(texts, method.Text())
+ texts = appendTextDedup(texts, method.Text(fontSize))
}
} else if obj.SQLTable != nil {
+ colFontSize := d2fonts.FONT_SIZE_L
+ if obj.Attributes.Style.FontSize != nil {
+ colFontSize, _ = strconv.Atoi(obj.Attributes.Style.FontSize.Value)
+ }
for _, column := range obj.SQLTable.Columns {
- for _, t := range column.Texts() {
+ for _, t := range column.Texts(colFontSize) {
texts = appendTextDedup(texts, t)
}
}
diff --git a/d2target/class.go b/d2target/class.go
index 3cc39921e..c08f11144 100644
--- a/d2target/class.go
+++ b/d2target/class.go
@@ -2,8 +2,6 @@ package d2target
import (
"fmt"
-
- "oss.terrastruct.com/d2/d2renderers/d2fonts"
)
const (
@@ -25,10 +23,10 @@ type ClassField struct {
Visibility string `json:"visibility"`
}
-func (cf ClassField) Text() *MText {
+func (cf ClassField) Text(fontSize int) *MText {
return &MText{
Text: fmt.Sprintf("%s%s", cf.Name, cf.Type),
- FontSize: d2fonts.FONT_SIZE_L,
+ FontSize: fontSize,
IsBold: false,
IsItalic: false,
Shape: "class",
@@ -52,10 +50,10 @@ type ClassMethod struct {
Visibility string `json:"visibility"`
}
-func (cm ClassMethod) Text() *MText {
+func (cm ClassMethod) Text(fontSize int) *MText {
return &MText{
Text: fmt.Sprintf("%s%s", cm.Name, cm.Return),
- FontSize: d2fonts.FONT_SIZE_L,
+ FontSize: fontSize,
IsBold: false,
IsItalic: false,
Shape: "class",
diff --git a/d2target/sqltable.go b/d2target/sqltable.go
index 8332a34c8..6ffbddf05 100644
--- a/d2target/sqltable.go
+++ b/d2target/sqltable.go
@@ -1,11 +1,13 @@
package d2target
-import "oss.terrastruct.com/d2/d2renderers/d2fonts"
-
const (
NamePadding = 10
TypePadding = 20
- HeaderPadding = 20
+ HeaderPadding = 10
+
+ // Setting table font size sets it for columns
+ // The header needs to be a little larger for visual hierarchy
+ HeaderFontAdd = 4
)
type SQLTable struct {
@@ -19,18 +21,18 @@ type SQLColumn struct {
Reference string `json:"reference"`
}
-func (c SQLColumn) Texts() []*MText {
+func (c SQLColumn) Texts(fontSize int) []*MText {
return []*MText{
{
Text: c.Name.Label,
- FontSize: d2fonts.FONT_SIZE_L,
+ FontSize: fontSize,
IsBold: false,
IsItalic: false,
Shape: "sql_table",
},
{
Text: c.Type.Label,
- FontSize: d2fonts.FONT_SIZE_L,
+ FontSize: fontSize,
IsBold: false,
IsItalic: false,
Shape: "sql_table",
diff --git a/e2etests/regression_test.go b/e2etests/regression_test.go
index f40d9589b..ab5785511 100644
--- a/e2etests/regression_test.go
+++ b/e2etests/regression_test.go
@@ -1,10 +1,7 @@
package e2etests
import (
- "math"
"testing"
-
- "oss.terrastruct.com/d2/d2target"
)
func testRegression(t *testing.T) {
@@ -462,23 +459,12 @@ class2: class without rows {
shape: class
}
`,
- assertions: func(t *testing.T, g *d2target.Diagram) {
- if len(g.Shapes) != 2 {
- t.Fatal("expected 2 shapes")
- }
- c1Height := float64(g.Shapes[0].Height)
- c2Height := float64(g.Shapes[1].Height)
- if math.Round(c1Height/2.) != c2Height {
- t.Fatal("expected rowless class to be 1/2 height of class with 2 rows")
- }
- },
},
{
name: "just-width",
script: `x: "teamwork: having someone to blame" {
width: 100
}
-
`,
},
}
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index a489f6398..957451c08 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -1838,6 +1838,55 @@ x.y -> a.b: {
style.animated: true
target-arrowhead.shape: cf-many
}
+`,
+ },
+ {
+ name: "sql_table_column_styles",
+ script: `Humor in the Court: {
+ shape: sql_table
+ Could you see him from where you were standing?: "I could see his head."
+ And where was his head?: Just above his shoulders.
+ style.fill: red
+ style.stroke: lightgray
+ style.font-color: orange
+ style.font-size: 20
+}
+
+Humor in the Court2: {
+ shape: sql_table
+ Could you see him from where you were standing?: "I could see his head."
+ And where was his head?: Just above his shoulders.
+ style.fill: red
+ style.stroke: lightgray
+ style.font-color: orange
+ style.font-size: 30
+}
+
+manager: BatchManager {
+ shape: class
+ style.font-size: 20
+
+ -num: int
+ -timeout: int
+ -pid
+
+ +getStatus(): Enum
+ +getJobs(): "Job[]"
+ +setTimeout(seconds int)
+}
+
+manager2: BatchManager {
+ shape: class
+ style.font-size: 30
+
+ -num: int
+ -timeout: int
+ -pid
+
+ +getStatus(): Enum
+ +getJobs(): "Job[]"
+ +setTimeout(seconds int)
+}
`,
},
{
diff --git a/e2etests/testdata/regression/only_header_class_table/dagre/board.exp.json b/e2etests/testdata/regression/only_header_class_table/dagre/board.exp.json
index 87d63c1cb..0b744a933 100644
--- a/e2etests/testdata/regression/only_header_class_table/dagre/board.exp.json
+++ b/e2etests/testdata/regression/only_header_class_table/dagre/board.exp.json
@@ -49,10 +49,10 @@
"id": "table",
"type": "sql_table",
"pos": {
- "x": 341,
+ "x": 351,
"y": 192
},
- "width": 401,
+ "width": 381,
"height": 36,
"opacity": 1,
"strokeDash": 0,
@@ -92,10 +92,10 @@
"id": "table with short col",
"type": "sql_table",
"pos": {
- "x": 341,
+ "x": 351,
"y": 328
},
- "width": 401,
+ "width": 381,
"height": 72,
"opacity": 1,
"strokeDash": 0,
diff --git a/e2etests/testdata/regression/only_header_class_table/dagre/sketch.exp.svg b/e2etests/testdata/regression/only_header_class_table/dagre/sketch.exp.svg
index e10f558fc..547b27f30 100644
--- a/e2etests/testdata/regression/only_header_class_table/dagre/sketch.exp.svg
+++ b/e2etests/testdata/regression/only_header_class_table/dagre/sketch.exp.svg
@@ -39,9 +39,9 @@ width="1286" height="604" viewBox="-102 -102 1286 604">Humor in the CourtCould you see him from where you were standing?
+I could see his head.
+And where was his head?
+Just above his shoulders.
+Humor in the Court2Could you see him from where you were standing?
+I could see his head.
+And where was his head?
+Just above his shoulders.
+BatchManager-
+num
+int-
+timeout
+int-
+pid
++
+getStatus()
+Enum+
+getJobs()
+Job[]+
+setTimeout(seconds int)
+voidBatchManager-
+num
+int-
+timeout
+int-
+pid
++
+getStatus()
+Enum+
+getJobs()
+Job[]+
+setTimeout(seconds int)
+void
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/stable/sql_table_column_styles/elk/board.exp.json b/e2etests/testdata/stable/sql_table_column_styles/elk/board.exp.json
new file mode 100644
index 000000000..a3ac11503
--- /dev/null
+++ b/e2etests/testdata/stable/sql_table_column_styles/elk/board.exp.json
@@ -0,0 +1,357 @@
+{
+ "name": "",
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "Humor in the Court",
+ "type": "sql_table",
+ "pos": {
+ "x": 12,
+ "y": 190
+ },
+ "width": 678,
+ "height": 108,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "red",
+ "stroke": "lightgray",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "Could you see him from where you were standing?",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 418,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "I could see his head.",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 166,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "And where was his head?",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 208,
+ "labelHeight": 26
+ },
+ "type": {
+ "label": "Just above his shoulders.",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 210,
+ "labelHeight": 26
+ },
+ "constraint": "",
+ "reference": ""
+ }
+ ],
+ "label": "Humor in the Court",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "orange",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 202,
+ "labelHeight": 31,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "#0D32B2",
+ "secondaryAccentColor": "#4A6FF3",
+ "neutralAccentColor": "#676C7E"
+ },
+ {
+ "id": "Humor in the Court2",
+ "type": "sql_table",
+ "pos": {
+ "x": 710,
+ "y": 172
+ },
+ "width": 992,
+ "height": 144,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "red",
+ "stroke": "lightgray",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": [
+ {
+ "name": {
+ "label": "Could you see him from where you were standing?",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 627,
+ "labelHeight": 38
+ },
+ "type": {
+ "label": "I could see his head.",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 248,
+ "labelHeight": 38
+ },
+ "constraint": "",
+ "reference": ""
+ },
+ {
+ "name": {
+ "label": "And where was his head?",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 312,
+ "labelHeight": 38
+ },
+ "type": {
+ "label": "Just above his shoulders.",
+ "fontSize": 0,
+ "fontFamily": "",
+ "language": "",
+ "color": "",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 315,
+ "labelHeight": 38
+ },
+ "constraint": "",
+ "reference": ""
+ }
+ ],
+ "label": "Humor in the Court2",
+ "fontSize": 30,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "orange",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 303,
+ "labelHeight": 43,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "#0D32B2",
+ "secondaryAccentColor": "#4A6FF3",
+ "neutralAccentColor": "#676C7E"
+ },
+ {
+ "id": "manager",
+ "type": "class",
+ "pos": {
+ "x": 1722,
+ "y": 60
+ },
+ "width": 422,
+ "height": 368,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#0A0F25",
+ "stroke": "#FFFFFF",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": [
+ {
+ "name": "num",
+ "type": "int",
+ "visibility": "private"
+ },
+ {
+ "name": "timeout",
+ "type": "int",
+ "visibility": "private"
+ },
+ {
+ "name": "pid",
+ "type": "",
+ "visibility": "private"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getStatus()",
+ "return": "Enum",
+ "visibility": "public"
+ },
+ {
+ "name": "getJobs()",
+ "return": "Job[]",
+ "visibility": "public"
+ },
+ {
+ "name": "setTimeout(seconds int)",
+ "return": "void",
+ "visibility": "public"
+ }
+ ],
+ "columns": null,
+ "label": "BatchManager",
+ "fontSize": 20,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 170,
+ "labelHeight": 31,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "#0D32B2",
+ "secondaryAccentColor": "#4A6FF3",
+ "neutralAccentColor": "#676C7E"
+ },
+ {
+ "id": "manager2",
+ "type": "class",
+ "pos": {
+ "x": 2164,
+ "y": 12
+ },
+ "width": 582,
+ "height": 464,
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 2,
+ "borderRadius": 0,
+ "fill": "#0A0F25",
+ "stroke": "#FFFFFF",
+ "shadow": false,
+ "3d": false,
+ "multiple": false,
+ "double-border": false,
+ "tooltip": "",
+ "link": "",
+ "icon": null,
+ "iconPosition": "",
+ "blend": false,
+ "fields": [
+ {
+ "name": "num",
+ "type": "int",
+ "visibility": "private"
+ },
+ {
+ "name": "timeout",
+ "type": "int",
+ "visibility": "private"
+ },
+ {
+ "name": "pid",
+ "type": "",
+ "visibility": "private"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getStatus()",
+ "return": "Enum",
+ "visibility": "public"
+ },
+ {
+ "name": "getJobs()",
+ "return": "Job[]",
+ "visibility": "public"
+ },
+ {
+ "name": "setTimeout(seconds int)",
+ "return": "void",
+ "visibility": "public"
+ }
+ ],
+ "columns": null,
+ "label": "BatchManager",
+ "fontSize": 30,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 241,
+ "labelHeight": 43,
+ "zIndex": 0,
+ "level": 1,
+ "primaryAccentColor": "#0D32B2",
+ "secondaryAccentColor": "#4A6FF3",
+ "neutralAccentColor": "#676C7E"
+ }
+ ],
+ "connections": []
+}
diff --git a/e2etests/testdata/stable/sql_table_column_styles/elk/sketch.exp.svg b/e2etests/testdata/stable/sql_table_column_styles/elk/sketch.exp.svg
new file mode 100644
index 000000000..0473c7f13
--- /dev/null
+++ b/e2etests/testdata/stable/sql_table_column_styles/elk/sketch.exp.svg
@@ -0,0 +1,91 @@
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/stable/sql_table_tooltip_animated/dagre/sketch.exp.svg b/e2etests/testdata/stable/sql_table_tooltip_animated/dagre/sketch.exp.svg
index e047853b3..f74082d41 100644
--- a/e2etests/testdata/stable/sql_table_tooltip_animated/dagre/sketch.exp.svg
+++ b/e2etests/testdata/stable/sql_table_tooltip_animated/dagre/sketch.exp.svg
@@ -39,7 +39,7 @@ width="280" height="464" viewBox="-102 -118 280 464">