...

Source file src/oss.terrastruct.com/d2/d2renderers/d2svg/table.go

Documentation: oss.terrastruct.com/d2/d2renderers/d2svg

     1  package d2svg
     2  
     3  import (
     4  	"fmt"
     5  	"html"
     6  	"io"
     7  
     8  	"oss.terrastruct.com/d2/d2target"
     9  	"oss.terrastruct.com/d2/d2themes"
    10  	"oss.terrastruct.com/d2/lib/geo"
    11  	"oss.terrastruct.com/d2/lib/label"
    12  	"oss.terrastruct.com/d2/lib/svg"
    13  	"oss.terrastruct.com/util-go/go2"
    14  )
    15  
    16  // this func helps define a clipPath for shape class and sql_table to draw border-radius
    17  func clipPathForBorderRadius(diagramHash string, shape d2target.Shape) string {
    18  	box := geo.NewBox(
    19  		geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)),
    20  		float64(shape.Width),
    21  		float64(shape.Height),
    22  	)
    23  	topX, topY := box.TopLeft.X+box.Width, box.TopLeft.Y
    24  
    25  	out := fmt.Sprintf(`<clipPath id="%v-%v">`, diagramHash, shape.ID)
    26  	out += fmt.Sprintf(`<path d="M %f %f L %f %f S %f %f %f %f `, box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius), box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius), box.TopLeft.X, box.TopLeft.Y, box.TopLeft.X+float64(shape.BorderRadius), box.TopLeft.Y)
    27  	out += fmt.Sprintf(`L %f %f L %f %f `, box.TopLeft.X+box.Width-float64(shape.BorderRadius), box.TopLeft.Y, topX-float64(shape.BorderRadius), topY)
    28  
    29  	out += fmt.Sprintf(`S %f %f %f %f `, topX, topY, topX, topY+float64(shape.BorderRadius))
    30  	out += fmt.Sprintf(`L %f %f `, topX, topY+box.Height-float64(shape.BorderRadius))
    31  
    32  	if len(shape.Columns) != 0 {
    33  		out += fmt.Sprintf(`L %f %f L %f %f`, topX, topY+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height)
    34  	} else {
    35  		out += fmt.Sprintf(`S %f % f %f %f `, topX, topY+box.Height, topX-float64(shape.BorderRadius), topY+box.Height)
    36  		out += fmt.Sprintf(`L %f %f `, box.TopLeft.X+float64(shape.BorderRadius), box.TopLeft.Y+box.Height)
    37  		out += fmt.Sprintf(`S %f %f %f %f`, box.TopLeft.X, box.TopLeft.Y+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height-float64(shape.BorderRadius))
    38  		out += fmt.Sprintf(`L %f %f`, box.TopLeft.X, box.TopLeft.Y+float64(shape.BorderRadius))
    39  	}
    40  	out += fmt.Sprintf(`Z %f %f" `, box.TopLeft.X, box.TopLeft.Y)
    41  	return out + `fill="none" /> </clipPath>`
    42  }
    43  
    44  func tableHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string {
    45  	rectEl := d2themes.NewThemableElement("rect")
    46  	rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
    47  	rectEl.Width, rectEl.Height = box.Width, box.Height
    48  	rectEl.Fill = shape.Fill
    49  	rectEl.FillPattern = shape.FillPattern
    50  	rectEl.ClassName = "class_header"
    51  	if shape.BorderRadius != 0 {
    52  		rectEl.ClipPath = fmt.Sprintf("%v-%v", diagramHash, shape.ID)
    53  	}
    54  	str := rectEl.Render()
    55  
    56  	if text != "" {
    57  		tl := label.InsideMiddleLeft.GetPointOnBox(
    58  			box,
    59  			float64(d2target.HeaderPadding),
    60  			float64(shape.Width),
    61  			textHeight,
    62  		)
    63  
    64  		textEl := d2themes.NewThemableElement("text")
    65  		textEl.X = tl.X
    66  		textEl.Y = tl.Y + textHeight*3/4
    67  		textEl.Fill = shape.GetFontColor()
    68  		textEl.ClassName = "text"
    69  		textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
    70  			"start", 4+fontSize,
    71  		)
    72  		textEl.Content = svg.EscapeText(text)
    73  		str += textEl.Render()
    74  	}
    75  	return str
    76  }
    77  
    78  func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth, longestTypeWidth float64) string {
    79  	// Row is made up of name, type, and constraint
    80  	// e.g. | diagram   int   FK |
    81  	nameTL := label.InsideMiddleLeft.GetPointOnBox(
    82  		box,
    83  		d2target.NamePadding,
    84  		0,
    85  		fontSize,
    86  	)
    87  
    88  	textEl := d2themes.NewThemableElement("text")
    89  	textEl.X = nameTL.X
    90  	textEl.Y = nameTL.Y + fontSize*3/4
    91  	textEl.Fill = shape.PrimaryAccentColor
    92  	textEl.ClassName = "text"
    93  	textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", fontSize)
    94  	textEl.Content = svg.EscapeText(nameText)
    95  	out := textEl.Render()
    96  
    97  	textEl.X += longestNameWidth + d2target.TypePadding
    98  	textEl.Fill = shape.NeutralAccentColor
    99  	textEl.Content = svg.EscapeText(typeText)
   100  	out += textEl.Render()
   101  
   102  	textEl.X = box.TopLeft.X + (box.Width - d2target.NamePadding)
   103  	textEl.Fill = shape.SecondaryAccentColor
   104  	textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
   105  	textEl.Content = constraintText
   106  	out += textEl.Render()
   107  
   108  	return out
   109  }
   110  
   111  func drawTable(writer io.Writer, diagramHash string, targetShape d2target.Shape) {
   112  	rectEl := d2themes.NewThemableElement("rect")
   113  	rectEl.X = float64(targetShape.Pos.X)
   114  	rectEl.Y = float64(targetShape.Pos.Y)
   115  	rectEl.Width = float64(targetShape.Width)
   116  	rectEl.Height = float64(targetShape.Height)
   117  	rectEl.Fill, rectEl.Stroke = d2themes.ShapeTheme(targetShape)
   118  	rectEl.FillPattern = targetShape.FillPattern
   119  	rectEl.ClassName = "shape"
   120  	rectEl.Style = targetShape.CSSStyle()
   121  	if targetShape.BorderRadius != 0 {
   122  		rectEl.Rx = float64(targetShape.BorderRadius)
   123  		rectEl.Ry = float64(targetShape.BorderRadius)
   124  	}
   125  	fmt.Fprint(writer, rectEl.Render())
   126  
   127  	box := geo.NewBox(
   128  		geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)),
   129  		float64(targetShape.Width),
   130  		float64(targetShape.Height),
   131  	)
   132  	rowHeight := box.Height / float64(1+len(targetShape.SQLTable.Columns))
   133  	headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight)
   134  
   135  	fmt.Fprint(writer,
   136  		tableHeader(diagramHash, targetShape, headerBox, targetShape.Label,
   137  			float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)),
   138  	)
   139  
   140  	var longestNameWidth int
   141  	var longestTypeWidth int
   142  	for _, f := range targetShape.Columns {
   143  		longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth)
   144  		longestTypeWidth = go2.Max(longestTypeWidth, f.Type.LabelWidth)
   145  	}
   146  
   147  	rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
   148  	rowBox.TopLeft.Y += headerBox.Height
   149  	for idx, f := range targetShape.Columns {
   150  		fmt.Fprint(writer,
   151  			tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth), float64(longestTypeWidth)),
   152  		)
   153  		rowBox.TopLeft.Y += rowHeight
   154  
   155  		lineEl := d2themes.NewThemableElement("line")
   156  		if idx == len(targetShape.Columns)-1 && targetShape.BorderRadius != 0 {
   157  			lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X+float64(targetShape.BorderRadius), rowBox.TopLeft.Y
   158  			lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width-float64(targetShape.BorderRadius), rowBox.TopLeft.Y
   159  		} else {
   160  			lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y
   161  			lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y
   162  		}
   163  		lineEl.Stroke = targetShape.Fill
   164  		lineEl.Style = "stroke-width:2"
   165  		fmt.Fprint(writer, lineEl.Render())
   166  	}
   167  
   168  	if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage {
   169  		iconPosition := label.FromString(targetShape.IconPosition)
   170  		iconSize := d2target.GetIconSize(box, targetShape.IconPosition)
   171  
   172  		tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize))
   173  
   174  		fmt.Fprintf(writer, `<image href="%s" x="%f" y="%f" width="%d" height="%d" />`,
   175  			html.EscapeString(targetShape.Icon.String()),
   176  			tl.X,
   177  			tl.Y,
   178  			iconSize,
   179  			iconSize,
   180  		)
   181  	}
   182  }
   183  

View as plain text