set up shape specific inner bounding boxes for labels

This commit is contained in:
Gavin Nishizawa 2023-01-20 20:04:59 -08:00
parent 9b0f942c05
commit 23097370e2
No known key found for this signature in database
GPG key ID: AE3B177777CE55CD
18 changed files with 427 additions and 127 deletions

View file

@ -18,6 +18,7 @@ import (
"oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/shape"
"oss.terrastruct.com/d2/lib/textmeasure" "oss.terrastruct.com/d2/lib/textmeasure"
) )
@ -1117,14 +1118,14 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
continue continue
} }
shapeType := strings.ToLower(obj.Attributes.Shape.Value) dslShape := strings.ToLower(obj.Attributes.Shape.Value)
labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily) labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily)
if err != nil { if err != nil {
return err return err
} }
switch shapeType { switch dslShape {
case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode: case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode:
// no labels // no labels
default: default:
@ -1134,7 +1135,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
} }
} }
if shapeType != d2target.ShapeText && obj.Attributes.Label.Value != "" { if dslShape != d2target.ShapeText && obj.Attributes.Label.Value != "" {
labelDims.Width += INNER_LABEL_PADDING labelDims.Width += INNER_LABEL_PADDING
labelDims.Height += INNER_LABEL_PADDING labelDims.Height += INNER_LABEL_PADDING
} }
@ -1150,7 +1151,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
paddingX, paddingY := obj.GetPadding() paddingX, paddingY := obj.GetPadding()
switch shapeType { switch dslShape {
case d2target.ShapeSquare, d2target.ShapeCircle: case d2target.ShapeSquare, d2target.ShapeCircle:
if desiredWidth != 0 || desiredHeight != 0 { if desiredWidth != 0 || desiredHeight != 0 {
paddingX = 0. paddingX = 0.
@ -1169,6 +1170,12 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
obj.Height += float64(paddingY) obj.Height += float64(paddingY)
} }
} }
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(defaultDims.Width), float64(defaultDims.Height))
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape]
s := shape.NewShape(shapeType, contentBox)
newWidth, newHeight := s.GetDimensionsToFit(contentBox.Width, contentBox.Height, paddingX/2)
obj.Width = newWidth
obj.Height = newHeight
} }
for _, edge := range g.Edges { for _, edge := range g.Edges {
endpointLabels := []string{} endpointLabels := []string{}

View file

@ -42,7 +42,7 @@ const (
appendixIconRadius = 16 appendixIconRadius = 16
) )
var multipleOffset = geo.NewVector(10, -10) var multipleOffset = geo.NewVector(d2target.MULTIPLE_OFFSET, -d2target.MULTIPLE_OFFSET)
//go:embed tooltip.svg //go:embed tooltip.svg
var TooltipIcon string var TooltipIcon string
@ -734,7 +734,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
var multipleTL *geo.Point var multipleTL *geo.Point
if targetShape.Multiple { if targetShape.Multiple {
multipleTL = tl.AddVector(geo.NewVector(d2target.MULTIPLE_OFFSET, -d2target.MULTIPLE_OFFSET)) multipleTL = tl.AddVector(multipleOffset)
} }
switch targetShape.Type { switch targetShape.Type {
@ -744,13 +744,13 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
drawClass(writer, targetShape) drawClass(writer, targetShape)
} }
addAppendixItems(writer, targetShape) addAppendixItems(writer, targetShape)
fmt.Fprintf(writer, `</g>`) fmt.Fprint(writer, `</g>`)
fmt.Fprintf(writer, closingTag) fmt.Fprint(writer, closingTag)
return labelMask, nil return labelMask, nil
case d2target.ShapeSQLTable: case d2target.ShapeSQLTable:
if sketchRunner != nil { if sketchRunner != nil {
@ -758,13 +758,13 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
drawTable(writer, targetShape) drawTable(writer, targetShape)
} }
addAppendixItems(writer, targetShape) addAppendixItems(writer, targetShape)
fmt.Fprintf(writer, `</g>`) fmt.Fprint(writer, `</g>`)
fmt.Fprintf(writer, closingTag) fmt.Fprint(writer, closingTag)
return labelMask, nil return labelMask, nil
case d2target.ShapeOval: case d2target.ShapeOval:
if targetShape.DoubleBorder { if targetShape.DoubleBorder {
@ -776,7 +776,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
fmt.Fprint(writer, renderDoubleOval(tl, width, height, style)) fmt.Fprint(writer, renderDoubleOval(tl, width, height, style))
} }
@ -789,12 +789,17 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
fmt.Fprint(writer, renderOval(tl, width, height, style)) fmt.Fprint(writer, renderOval(tl, width, height, style))
} }
} }
// debugging
for _, pathData := range s.GetSVGPathData() {
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
}
case d2target.ShapeImage: case d2target.ShapeImage:
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`, fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
html.EscapeString(targetShape.Icon.String()), html.EscapeString(targetShape.Icon.String()),
@ -815,7 +820,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`, fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
@ -832,7 +837,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`, fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
@ -855,7 +860,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(writer, out) fmt.Fprint(writer, out)
} else { } else {
for _, pathData := range s.GetSVGPathData() { for _, pathData := range s.GetSVGPathData() {
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style) fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
@ -864,7 +869,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
} }
// Closes the class=shape // Closes the class=shape
fmt.Fprintf(writer, `</g>`) fmt.Fprint(writer, `</g>`)
if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage { if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage {
iconPosition := label.Position(targetShape.IconPosition) iconPosition := label.Position(targetShape.IconPosition)
@ -895,7 +900,11 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
} else { } else {
box = s.GetInnerBox() box = s.GetInnerBox()
} }
labelTL := labelPosition.GetPointOnBox(box, label.PADDING, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight)) labelTL := labelPosition.GetPointOnBox(box, label.PADDING,
float64(targetShape.LabelWidth),
// TODO consider further
float64(targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING),
)
fontClass := "text" fontClass := "text"
if targetShape.Bold { if targetShape.Bold {
@ -932,7 +941,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
fmt.Fprintf(writer, `<rect class="shape" width="%d" height="%d" style="%s" />`, fmt.Fprintf(writer, `<rect class="shape" width="%d" height="%d" style="%s" />`,
targetShape.Width, targetShape.Height, containerStyle) targetShape.Width, targetShape.Height, containerStyle)
// Padding // Padding
fmt.Fprintf(writer, `<g transform="translate(6 6)">`) fmt.Fprint(writer, `<g transform="translate(6 6)">`)
for index, tokens := range chroma.SplitTokensIntoLines(iterator.Tokens()) { for index, tokens := range chroma.SplitTokensIntoLines(iterator.Tokens()) {
// TODO mono font looks better with 1.2 em (use px equivalent), but textmeasure needs to account for it. Not obvious how that should be done // TODO mono font looks better with 1.2 em (use px equivalent), but textmeasure needs to account for it. Not obvious how that should be done
@ -947,7 +956,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
} }
fmt.Fprint(writer, "</text>") fmt.Fprint(writer, "</text>")
} }
fmt.Fprintf(writer, "</g></g>") fmt.Fprint(writer, "</g></g>")
} else if targetShape.Type == d2target.ShapeText && targetShape.Language == "latex" { } else if targetShape.Type == d2target.ShapeText && targetShape.Language == "latex" {
render, err := d2latex.Render(targetShape.Label) render, err := d2latex.Render(targetShape.Label)
if err != nil { if err != nil {
@ -955,7 +964,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
} }
fmt.Fprintf(writer, `<g transform="translate(%f %f)">`, box.TopLeft.X, box.TopLeft.Y) fmt.Fprintf(writer, `<g transform="translate(%f %f)">`, box.TopLeft.X, box.TopLeft.Y)
fmt.Fprint(writer, render) fmt.Fprint(writer, render)
fmt.Fprintf(writer, "</g>") fmt.Fprint(writer, "</g>")
} else if targetShape.Type == d2target.ShapeText && targetShape.Language != "" { } else if targetShape.Type == d2target.ShapeText && targetShape.Language != "" {
render, err := textmeasure.RenderMarkdown(targetShape.Label) render, err := textmeasure.RenderMarkdown(targetShape.Label)
if err != nil { if err != nil {
@ -1000,7 +1009,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
addAppendixItems(writer, targetShape) addAppendixItems(writer, targetShape)
fmt.Fprintf(writer, closingTag) fmt.Fprint(writer, closingTag)
return labelMask, nil return labelMask, nil
} }
@ -1230,7 +1239,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS) fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS)
} }
if sketchRunner != nil { if sketchRunner != nil {
fmt.Fprintf(buf, d2sketch.DefineFillPattern()) fmt.Fprint(buf, d2sketch.DefineFillPattern())
} }
// only define shadow filter if a shape uses it // only define shadow filter if a shape uses it

View file

@ -4,6 +4,7 @@ import (
"math" "math"
"oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/svg"
) )
const ( const (
@ -90,6 +91,8 @@ func (s baseShape) GetInnerTopLeft(_, _, padding float64) geo.Point {
return *geo.NewPoint(s.Box.TopLeft.X+padding, s.Box.TopLeft.Y+padding) return *geo.NewPoint(s.Box.TopLeft.X+padding, s.Box.TopLeft.Y+padding)
} }
// return the minimum shape dimensions needed to fit content (width x height)
// in the shape's innerBox with padding
func (s baseShape) GetDimensionsToFit(width, height, padding float64) (float64, float64) { func (s baseShape) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
return width + padding*2, height + padding*2 return width + padding*2, height + padding*2
} }
@ -209,3 +212,13 @@ func TraceToShapeBorder(shape Shape, rectBorderPoint, prevPoint *geo.Point) *geo
return geo.NewPoint(math.Round(closestPoint.X), math.Round(closestPoint.Y)) return geo.NewPoint(math.Round(closestPoint.X), math.Round(closestPoint.Y))
} }
func boxPath(box *geo.Box) *svg.SvgPathContext {
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(0, 0))
pc.L(false, box.Width, 0)
pc.L(false, box.Width, box.Height)
pc.L(false, 0, box.Height)
pc.Z()
return pc
}

View file

@ -9,6 +9,11 @@ type shapeCallout struct {
*baseShape *baseShape
} }
const (
defaultTipWidth = 30.
defaultTipHeight = 45.
)
func NewCallout(box *geo.Box) Shape { func NewCallout(box *geo.Box) Shape {
return shapeCallout{ return shapeCallout{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,25 +23,31 @@ func NewCallout(box *geo.Box) Shape {
} }
} }
func (s shapeCallout) GetInnerBox() *geo.Box { func getTipWidth(box *geo.Box) float64 {
height := s.Box.Height tipWidth := defaultTipWidth
tipHeight := 45.0 if box.Width < tipWidth*2 {
if height < tipHeight*2 { tipWidth = box.Width / 2.0
tipHeight = height / 2.0
} }
height -= tipHeight return tipWidth
}
func getTipHeight(box *geo.Box) float64 {
tipHeight := defaultTipHeight
if box.Height < tipHeight*2 {
tipHeight = box.Height / 2.0
}
return tipHeight
}
func (s shapeCallout) GetInnerBox() *geo.Box {
tipHeight := getTipHeight(s.Box)
height := s.Box.Height - tipHeight
return geo.NewBox(s.Box.TopLeft.Copy(), s.Box.Width, height) return geo.NewBox(s.Box.TopLeft.Copy(), s.Box.Width, height)
} }
func calloutPath(box *geo.Box) *svg.SvgPathContext { func calloutPath(box *geo.Box) *svg.SvgPathContext {
tipWidth := 30.0 tipWidth := getTipWidth(box)
if box.Width < tipWidth*2 { tipHeight := getTipHeight(box)
tipWidth = box.Width / 2.0
}
tipHeight := 45.0
if box.Height < tipHeight*2 {
tipHeight = box.Height / 2.0
}
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(0, 0)) pc.StartAt(pc.Absolute(0, 0))
pc.V(true, box.Height-tipHeight) pc.V(true, box.Height-tipHeight)
@ -57,5 +68,19 @@ func (s shapeCallout) Perimeter() []geo.Intersectable {
func (s shapeCallout) GetSVGPathData() []string { func (s shapeCallout) GetSVGPathData() []string {
return []string{ return []string{
calloutPath(s.Box).PathData(), calloutPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeCallout) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
// return the minimum shape dimensions needed to fit content (width x height)
// in the shape's innerBox with padding
baseHeight := height + padding*2
if baseHeight < defaultTipHeight {
baseHeight *= 2
} else {
baseHeight += defaultTipHeight
}
return width + padding*2, baseHeight
}

View file

@ -19,13 +19,23 @@ func NewCircle(box *geo.Box) Shape {
} }
} }
func (s shapeCircle) GetInnerBox() *geo.Box {
width := s.Box.Width
height := s.Box.Height
insideTL := s.GetInsidePlacement(width, height, 0)
tl := s.Box.TopLeft.Copy()
width -= 2 * (insideTL.X - tl.X)
height -= 2 * (insideTL.Y - tl.Y)
return geo.NewBox(&insideTL, width, height)
}
func (s shapeCircle) AspectRatio1() bool { func (s shapeCircle) AspectRatio1() bool {
return true return true
} }
func (s shapeCircle) GetDimensionsToFit(width, height, padding float64) (float64, float64) { func (s shapeCircle) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
radius := math.Ceil(math.Sqrt(math.Pow(width/2, 2)+math.Pow(height/2, 2))) + padding diameter := math.Ceil(math.Sqrt(2 * math.Pow(math.Max(width, height)+2*padding, 2)))
return radius * 2, radius * 2 return diameter, diameter
} }
func (s shapeCircle) GetInsidePlacement(width, height, padding float64) geo.Point { func (s shapeCircle) GetInsidePlacement(width, height, padding float64) geo.Point {

View file

@ -40,6 +40,24 @@ func NewCloud(box *geo.Box) Shape {
} }
} }
func (s shapeCloud) GetInnerBox() *geo.Box {
width := s.Box.Width
height := s.Box.Height
insideTL := s.GetInsidePlacement(width, height, 0)
aspectRatio := width / height
if aspectRatio > CLOUD_WIDE_ASPECT_BOUNDARY {
width *= CLOUD_WIDE_INNER_WIDTH
height *= CLOUD_WIDE_INNER_HEIGHT
} else if aspectRatio < CLOUD_TALL_ASPECT_BOUNDARY {
width *= CLOUD_TALL_INNER_WIDTH
height *= CLOUD_TALL_INNER_HEIGHT
} else {
width *= CLOUD_SQUARE_INNER_WIDTH
height *= CLOUD_SQUARE_INNER_HEIGHT
}
return geo.NewBox(&insideTL, width, height)
}
func (s shapeCloud) GetDimensionsToFit(width, height, padding float64) (float64, float64) { func (s shapeCloud) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
width += padding width += padding
height += padding height += padding
@ -96,5 +114,7 @@ func (s shapeCloud) Perimeter() []geo.Intersectable {
func (s shapeCloud) GetSVGPathData() []string { func (s shapeCloud) GetSVGPathData() []string {
return []string{ return []string{
cloudPath(s.Box).PathData(), cloudPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }

View file

@ -9,6 +9,10 @@ type shapeCylinder struct {
*baseShape *baseShape
} }
const (
defaultArcDepth = 24.
)
func NewCylinder(box *geo.Box) Shape { func NewCylinder(box *geo.Box) Shape {
return shapeCylinder{ return shapeCylinder{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,46 +22,47 @@ func NewCylinder(box *geo.Box) Shape {
} }
} }
func getArcHeight(box *geo.Box) float64 {
arcHeight := defaultArcDepth
// Note: box height should always be larger than 3*default
// this just handles after collapsing into an oval
if box.Height < arcHeight*2 {
arcHeight = box.Height / 2.0
}
return arcHeight
}
func (s shapeCylinder) GetInnerBox() *geo.Box { func (s shapeCylinder) GetInnerBox() *geo.Box {
height := s.Box.Height height := s.Box.Height
tl := s.Box.TopLeft.Copy() tl := s.Box.TopLeft.Copy()
arcDepth := 24.0 arc := getArcHeight(s.Box)
if height < arcDepth*2 { height -= 3 * arc
arcDepth = height / 2.0 tl.Y += 2 * arc
}
height -= 3 * arcDepth
tl.Y += 2 * arcDepth
return geo.NewBox(tl, s.Box.Width, height) return geo.NewBox(tl, s.Box.Width, height)
} }
func cylinderOuterPath(box *geo.Box) *svg.SvgPathContext { func cylinderOuterPath(box *geo.Box) *svg.SvgPathContext {
arcDepth := 24.0 arcHeight := getArcHeight(box)
if box.Height < arcDepth*2 {
arcDepth = box.Height / 2
}
multiplier := 0.45 multiplier := 0.45
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(0, arcDepth)) pc.StartAt(pc.Absolute(0, arcHeight))
pc.C(false, 0, 0, box.Width*multiplier, 0, box.Width/2, 0) pc.C(false, 0, 0, box.Width*multiplier, 0, box.Width/2, 0)
pc.C(false, box.Width-box.Width*multiplier, 0, box.Width, 0, box.Width, arcDepth) pc.C(false, box.Width-box.Width*multiplier, 0, box.Width, 0, box.Width, arcHeight)
pc.V(true, box.Height-arcDepth*2) pc.V(true, box.Height-arcHeight*2)
pc.C(false, box.Width, box.Height, box.Width-box.Width*multiplier, box.Height, box.Width/2, box.Height) pc.C(false, box.Width, box.Height, box.Width-box.Width*multiplier, box.Height, box.Width/2, box.Height)
pc.C(false, box.Width*multiplier, box.Height, 0, box.Height, 0, box.Height-arcDepth) pc.C(false, box.Width*multiplier, box.Height, 0, box.Height, 0, box.Height-arcHeight)
pc.V(true, -(box.Height - arcDepth*2)) pc.V(true, -(box.Height - arcHeight*2))
pc.Z() pc.Z()
return pc return pc
} }
func cylinderInnerPath(box *geo.Box) *svg.SvgPathContext { func cylinderInnerPath(box *geo.Box) *svg.SvgPathContext {
arcDepth := 24.0 arcHeight := getArcHeight(box)
if box.Height < arcDepth*2 {
arcDepth = box.Height / 2
}
multiplier := 0.45 multiplier := 0.45
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(0, arcDepth)) pc.StartAt(pc.Absolute(0, arcHeight))
pc.C(false, 0, arcDepth*2, box.Width*multiplier, arcDepth*2, box.Width/2, arcDepth*2) pc.C(false, 0, arcHeight*2, box.Width*multiplier, arcHeight*2, box.Width/2, arcHeight*2)
pc.C(false, box.Width-box.Width*multiplier, arcDepth*2, box.Width, arcDepth*2, box.Width, arcDepth) pc.C(false, box.Width-box.Width*multiplier, arcHeight*2, box.Width, arcHeight*2, box.Width, arcHeight)
return pc return pc
} }
@ -69,5 +74,13 @@ func (s shapeCylinder) GetSVGPathData() []string {
return []string{ return []string{
cylinderOuterPath(s.Box).PathData(), cylinderOuterPath(s.Box).PathData(),
cylinderInnerPath(s.Box).PathData(), cylinderInnerPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeCylinder) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
// 2 arcs top, height + padding, 1 arc bottom
totalHeight := height + padding*2 + 3*defaultArcDepth
return width + padding*2, totalHeight
}

View file

@ -18,6 +18,17 @@ func NewDiamond(box *geo.Box) Shape {
} }
} }
func (s shapeDiamond) GetInnerBox() *geo.Box {
width := s.Box.Width
height := s.Box.Height
tl := s.Box.TopLeft.Copy()
tl.X += width / 4.
tl.Y += height / 4.
width /= 2.
height /= 2.
return geo.NewBox(tl, width, height)
}
func diamondPath(box *geo.Box) *svg.SvgPathContext { func diamondPath(box *geo.Box) *svg.SvgPathContext {
pc := svg.NewSVGPathContext(box.TopLeft, box.Width/77, box.Height/76.9) pc := svg.NewSVGPathContext(box.TopLeft, box.Width/77, box.Height/76.9)
pc.StartAt(pc.Absolute(38.5, 76.9)) pc.StartAt(pc.Absolute(38.5, 76.9))
@ -41,5 +52,13 @@ func (s shapeDiamond) Perimeter() []geo.Intersectable {
func (s shapeDiamond) GetSVGPathData() []string { func (s shapeDiamond) GetSVGPathData() []string {
return []string{ return []string{
diamondPath(s.Box).PathData(), diamondPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeDiamond) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := 2 * (width + 2*padding)
totalHeight := 2 * (height + 2*padding)
return totalWidth, totalHeight
}

View file

@ -9,6 +9,13 @@ type shapeDocument struct {
*baseShape *baseShape
} }
const (
// the shape is taller than where the bottom of the path ends
docPathHeight = 18.925
docPathInnerBottom = 14
docPathBottom = 16.3
)
func NewDocument(box *geo.Box) Shape { func NewDocument(box *geo.Box) Shape {
return shapeDocument{ return shapeDocument{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,15 +25,19 @@ func NewDocument(box *geo.Box) Shape {
} }
} }
func (s shapeDocument) GetInnerBox() *geo.Box {
height := s.Box.Height * docPathInnerBottom / docPathHeight
return geo.NewBox(s.Box.TopLeft.Copy(), s.Box.Width, height)
}
func documentPath(box *geo.Box) *svg.SvgPathContext { func documentPath(box *geo.Box) *svg.SvgPathContext {
pathHeight := 18.925
pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height) pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height)
pc.StartAt(pc.Absolute(0, 16.3/pathHeight)) pc.StartAt(pc.Absolute(0, docPathBottom/docPathHeight))
pc.L(false, 0, 0) pc.L(false, 0, 0)
pc.L(false, 1, 0) pc.L(false, 1, 0)
pc.L(false, 1, 16.3/pathHeight) pc.L(false, 1, docPathBottom/docPathHeight)
pc.C(false, 5/6.0, 12.8/pathHeight, 2/3.0, 12.8/pathHeight, 1/2.0, 16.3/pathHeight) pc.C(false, 5/6.0, 12.8/docPathHeight, 2/3.0, 12.8/docPathHeight, 1/2.0, docPathBottom/docPathHeight)
pc.C(false, 1/3.0, 19.8/pathHeight, 1/6.0, 19.8/pathHeight, 0, 16.3/pathHeight) pc.C(false, 1/3.0, 19.8/docPathHeight, 1/6.0, 19.8/docPathHeight, 0, docPathBottom/docPathHeight)
pc.Z() pc.Z()
return pc return pc
} }
@ -38,5 +49,12 @@ func (s shapeDocument) Perimeter() []geo.Intersectable {
func (s shapeDocument) GetSVGPathData() []string { func (s shapeDocument) GetSVGPathData() []string {
return []string{ return []string{
documentPath(s.Box).PathData(), documentPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeDocument) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
baseHeight := (height + padding*2) * docPathHeight / docPathInnerBottom
return width + padding*2, baseHeight
}

View file

@ -18,6 +18,14 @@ func NewHexagon(box *geo.Box) Shape {
} }
} }
func (s shapeHexagon) GetInnerBox() *geo.Box {
width := s.Box.Width
tl := s.Box.TopLeft.Copy()
tl.X += width / 4.
width /= 2.
return geo.NewBox(tl, width, s.Box.Height)
}
func hexagonPath(box *geo.Box) *svg.SvgPathContext { func hexagonPath(box *geo.Box) *svg.SvgPathContext {
halfYFactor := 43.6 / 87.3 halfYFactor := 43.6 / 87.3
pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height) pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height)
@ -38,5 +46,12 @@ func (s shapeHexagon) Perimeter() []geo.Intersectable {
func (s shapeHexagon) GetSVGPathData() []string { func (s shapeHexagon) GetSVGPathData() []string {
return []string{ return []string{
hexagonPath(s.Box).PathData(), hexagonPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeHexagon) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := 2 * (width + 2*padding)
return totalWidth, height + 2*padding
}

View file

@ -19,6 +19,16 @@ func NewOval(box *geo.Box) Shape {
} }
} }
func (s shapeOval) GetInnerBox() *geo.Box {
width := s.Box.Width
height := s.Box.Height
insideTL := s.GetInsidePlacement(width, height, 0)
tl := s.Box.TopLeft.Copy()
width -= 2 * (insideTL.X - tl.X)
height -= 2 * (insideTL.Y - tl.Y)
return geo.NewBox(&insideTL, width, height)
}
func (s shapeOval) GetDimensionsToFit(width, height, padding float64) (float64, float64) { func (s shapeOval) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
theta := math.Atan2(height, width) theta := math.Atan2(height, width)
// add padding in direction of diagonal so there is padding distance between top left and border // add padding in direction of diagonal so there is padding distance between top left and border
@ -53,3 +63,10 @@ func (s shapeOval) GetInsidePlacement(width, height, padding float64) geo.Point
func (s shapeOval) Perimeter() []geo.Intersectable { func (s shapeOval) Perimeter() []geo.Intersectable {
return []geo.Intersectable{geo.NewEllipse(s.Box.Center(), s.Box.Width/2, s.Box.Height/2)} return []geo.Intersectable{geo.NewEllipse(s.Box.Center(), s.Box.Width/2, s.Box.Height/2)}
} }
// debugging
func (s shapeOval) GetSVGPathData() []string {
return []string{
boxPath(s.GetInnerBox()).PathData(),
}
}

View file

@ -11,6 +11,15 @@ type shapePackage struct {
*baseShape *baseShape
} }
const (
packageTopMinHeight = 34.
packageTopMaxHeight = 55.
packageTopMinWidth = 50.
packageTopMaxWidth = 150.
packageHorizontalScalar = 0.5
packageVerticalScalar = 0.2
)
func NewPackage(box *geo.Box) Shape { func NewPackage(box *geo.Box) Shape {
return shapePackage{ return shapePackage{
baseShape: &baseShape{ baseShape: &baseShape{
@ -20,22 +29,27 @@ func NewPackage(box *geo.Box) Shape {
} }
} }
func packagePath(box *geo.Box) *svg.SvgPathContext { func (s shapePackage) GetInnerBox() *geo.Box {
const MIN_TOP_HEIGHT = 34 tl := s.Box.TopLeft.Copy()
const MAX_TOP_HEIGHT = 55 height := s.Box.Height
const MIN_TOP_WIDTH = 50
const MAX_TOP_WIDTH = 150
const horizontalScalar = 0.5 _, topHeight := getTopDimensions(s.Box)
topWidth := box.Width * horizontalScalar tl.Y += topHeight
if box.Width >= 2*MIN_TOP_WIDTH { height -= topHeight
topWidth = math.Min(MAX_TOP_WIDTH, math.Max(MIN_TOP_WIDTH, topWidth)) return geo.NewBox(tl, s.Box.Width, height)
} }
const verticalScalar = 0.2
topHeight := box.Height * verticalScalar func getTopDimensions(box *geo.Box) (width, height float64) {
if box.Height >= 2*MIN_TOP_HEIGHT { width = box.Width * packageHorizontalScalar
topHeight = math.Min(MAX_TOP_HEIGHT, math.Max(MIN_TOP_HEIGHT, topHeight)) if box.Width >= 2*packageTopMinWidth {
width = math.Min(packageTopMaxWidth, math.Max(packageTopMinWidth, width))
} }
height = math.Min(packageTopMaxHeight, box.Height*packageVerticalScalar)
return width, height
}
func packagePath(box *geo.Box) *svg.SvgPathContext {
topWidth, topHeight := getTopDimensions(box)
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(0, 0)) pc.StartAt(pc.Absolute(0, 0))
@ -55,5 +69,18 @@ func (s shapePackage) Perimeter() []geo.Intersectable {
func (s shapePackage) GetSVGPathData() []string { func (s shapePackage) GetSVGPathData() []string {
return []string{ return []string{
packagePath(s.Box).PathData(), packagePath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapePackage) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
innerHeight := height + padding*2
// We want to compute what the topHeight will be to add to inner height;
// topHeight=(verticalScalar * totalHeight) and totalHeight=(topHeight + innerHeight)
// so solving for topHeight we get: topHeight=innerHeight * (verticalScalar/(1-verticalScalar))
topHeight := innerHeight * packageVerticalScalar / (1. - packageVerticalScalar)
totalHeight := innerHeight + math.Min(topHeight, packageTopMaxHeight)
return width + padding*2, totalHeight
}

View file

@ -1,6 +1,8 @@
package shape package shape
import ( import (
"math"
"oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/svg" "oss.terrastruct.com/d2/lib/svg"
) )
@ -9,6 +11,12 @@ type shapePage struct {
*baseShape *baseShape
} }
const (
// TODO: cleanup
pageCornerWidth = 20.8164
pageCornerHeight = 20.348
)
func NewPage(box *geo.Box) Shape { func NewPage(box *geo.Box) Shape {
return shapePage{ return shapePage{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,49 +26,51 @@ func NewPage(box *geo.Box) Shape {
} }
} }
const PAGE_WIDTH = 66. func (s shapePage) GetInnerBox() *geo.Box {
const PAGE_HEIGHT = 79. // Note: for simplicity this assumes shape padding is greater than pageCornerSize
width := s.Box.Width
// consider right hand side occupied by corner for short pages
if s.Box.Height < 3*pageCornerHeight {
width -= pageCornerWidth
}
return geo.NewBox(s.Box.TopLeft.Copy(), width, s.Box.Height)
}
func pageOuterPath(box *geo.Box) *svg.SvgPathContext { func pageOuterPath(box *geo.Box) *svg.SvgPathContext {
// TODO: cleanup
pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.) pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.)
baseX := box.Width - PAGE_WIDTH
baseY := box.Height - PAGE_HEIGHT
pc.StartAt(pc.Absolute(0.5, 0)) pc.StartAt(pc.Absolute(0.5, 0))
pc.H(false, baseX+45.1836) // = width-(66+45.1836) pc.H(false, box.Width-20.8164)
pc.C(false, baseX+46.3544, 0.0, baseX+47.479, 0.456297, baseX+48.3189, 1.27202) pc.C(false, box.Width-19.6456, 0.0, box.Width-18.521, 0.456297, box.Width-17.6811, 1.27202)
pc.L(false, baseX+64.6353, 17.12) pc.L(false, box.Width-1.3647, 17.12)
pc.C(false, baseX+65.5077, 17.9674, baseX+66., 19.1318, baseX+66., 20.348) pc.C(false, box.Width-0.4923, 17.9674, box.Width, 19.1318, box.Width, 20.348)
// baseY is not needed above because the coordinates start at 0 pc.V(false, box.Height-0.5)
pc.V(false, baseY+78.5) pc.C(false, box.Width, box.Height-0.2239, box.Width-0.2239, box.Height, box.Width-0.5, box.Height)
pc.C(false, baseX+66.0, baseY+78.7761, baseX+65.7761, baseY+79.0, baseX+65.5, baseY+79.0)
pc.H(false, .499999) pc.H(false, 0.499999)
pc.C(false, 0.223857, baseY+79.0, 0.0, baseY+78.7761, 0.0, baseY+78.5) pc.C(false, 0.223857, box.Height, 0, box.Height-0.2239, 0, box.Height-0.5)
pc.V(false, 0.499999) pc.V(false, 0.499999)
pc.C(false, 0.0, 0.223857, 0.223857, 0.0, 0.5, 0.0) pc.C(false, 0, 0.223857, 0.223857, 0, 0.5, 0)
pc.Z() pc.Z()
return pc return pc
} }
func pageInnerPath(box *geo.Box) *svg.SvgPathContext { func pageInnerPath(box *geo.Box) *svg.SvgPathContext {
baseX := box.Width - PAGE_WIDTH
baseY := box.Height - PAGE_HEIGHT
pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.) pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.)
pc.StartAt(pc.Absolute(baseX+64.91803, baseY+79.)) pc.StartAt(pc.Absolute(box.Width-1.08197, box.Height))
pc.H(false, 1.08196) pc.H(false, 1.08196)
pc.C(true, -0.64918, 0, -1.08196, -0.43287, -1.08196, -1.08219) pc.C(true, -0.64918, 0, -1.08196, -0.43287, -1.08196, -1.08219)
pc.V(false, 1.08219) pc.V(false, 1.08219)
pc.C(true, 0, -0.64931, 0.43278, -1.08219, 1.08196, -1.08219) pc.C(true, 0, -0.64931, 0.43278, -1.08219, 1.08196, -1.08219)
pc.H(true, baseX+43.27868) pc.H(true, box.Width-22.72132)
pc.C(true, 0.64918, 0, 1.08196, 0.43287, 1.08196, 1.08219) pc.C(true, 0.64918, 0, 1.08196, 0.43287, 1.08196, 1.08219)
pc.V(true, 17.09863) pc.V(true, 17.09863)
pc.C(true, 0, 1.29863, 0.86557, 2.38082, 2.38032, 2.38082) pc.C(true, 0, 1.29863, 0.86557, 2.38082, 2.38032, 2.38082)
pc.H(false, baseX+64.91803) pc.H(false, box.Width-1.08197)
pc.C(true, .64918, 0, 1.08196, 0.43287, 1.08196, 1.08196) pc.C(true, .64918, 0, 1.08196, 0.43287, 1.08196, 1.08196)
pc.V(false, baseY+77.91780) pc.V(false, box.Height-1.0822)
pc.C(false, baseX+64.99999, baseY+78.56712, baseX+65.56721, baseY+79, baseX+64.91803, baseY+79) pc.C(false, box.Width-1.0, box.Height-0.43288, box.Width-0.43279, box.Height, box.Width-1.08197, box.Height)
pc.Z() pc.Z()
return pc return pc
} }
@ -73,5 +83,19 @@ func (s shapePage) GetSVGPathData() []string {
return []string{ return []string{
pageOuterPath(s.Box).PathData(), pageOuterPath(s.Box).PathData(),
pageInnerPath(s.Box).PathData(), pageInnerPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapePage) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := width + padding*2
totalHeight := height + padding*2
// add space for corner with short pages
if totalHeight < 3*pageCornerHeight {
totalWidth += pageCornerWidth
}
totalWidth = math.Max(totalWidth, 2*pageCornerWidth)
totalHeight = math.Max(totalHeight, pageCornerHeight)
return totalWidth, totalHeight
}

View file

@ -9,6 +9,8 @@ type shapeParallelogram struct {
*baseShape *baseShape
} }
const parallelWedgeWidth = 26.
func NewParallelogram(box *geo.Box) Shape { func NewParallelogram(box *geo.Box) Shape {
return shapeParallelogram{ return shapeParallelogram{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,8 +20,17 @@ func NewParallelogram(box *geo.Box) Shape {
} }
} }
func (s shapeParallelogram) GetInnerBox() *geo.Box {
tl := s.Box.TopLeft.Copy()
width := s.Box.Width - 2*parallelWedgeWidth
tl.X += parallelWedgeWidth
return geo.NewBox(tl, width, s.Box.Height)
}
func parallelogramPath(box *geo.Box) *svg.SvgPathContext { func parallelogramPath(box *geo.Box) *svg.SvgPathContext {
wedgeWidth := 26.0 wedgeWidth := parallelWedgeWidth
// Note: box width should always be larger than parallelWedgeWidth
// this just handles after collapsing into a line
if box.Width <= wedgeWidth { if box.Width <= wedgeWidth {
wedgeWidth = box.Width / 2.0 wedgeWidth = box.Width / 2.0
} }
@ -40,5 +51,12 @@ func (s shapeParallelogram) Perimeter() []geo.Intersectable {
func (s shapeParallelogram) GetSVGPathData() []string { func (s shapeParallelogram) GetSVGPathData() []string {
return []string{ return []string{
parallelogramPath(s.Box).PathData(), parallelogramPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeParallelogram) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := width + padding*2 + parallelWedgeWidth*2
return totalWidth, height + padding*2
}

View file

@ -18,6 +18,19 @@ func NewPerson(box *geo.Box) Shape {
} }
} }
const (
personShoulderWidthFactor = 20.2 / 68.3
)
func (s shapePerson) GetInnerBox() *geo.Box {
width := s.Box.Width
tl := s.Box.TopLeft.Copy()
shoulderWidth := personShoulderWidthFactor * width
tl.X += shoulderWidth
width -= shoulderWidth * 2
return geo.NewBox(tl, width, s.Box.Height)
}
func personPath(box *geo.Box) *svg.SvgPathContext { func personPath(box *geo.Box) *svg.SvgPathContext {
pc := svg.NewSVGPathContext(box.TopLeft, box.Width/68.3, box.Height/77.4) pc := svg.NewSVGPathContext(box.TopLeft, box.Width/68.3, box.Height/77.4)
@ -50,5 +63,16 @@ func (s shapePerson) Perimeter() []geo.Intersectable {
func (s shapePerson) GetSVGPathData() []string { func (s shapePerson) GetSVGPathData() []string {
return []string{ return []string{
personPath(s.Box).PathData(), personPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapePerson) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := width + padding*2
// see shapePackage
shoulderWidth := totalWidth * personShoulderWidthFactor / (1 - 2*personShoulderWidthFactor)
totalWidth += 2 * shoulderWidth
totalHeight := height + padding*2
return totalWidth, totalHeight
}

View file

@ -18,46 +18,47 @@ func NewQueue(box *geo.Box) Shape {
} }
} }
func getArcWidth(box *geo.Box) float64 {
arcWidth := defaultArcDepth
// Note: box width should always be larger than 3*default
// this just handles after collaping into an oval
if box.Width < arcWidth*2 {
arcWidth = box.Width / 2.0
}
return arcWidth
}
func (s shapeQueue) GetInnerBox() *geo.Box { func (s shapeQueue) GetInnerBox() *geo.Box {
width := s.Box.Width width := s.Box.Width
tl := s.Box.TopLeft.Copy() tl := s.Box.TopLeft.Copy()
arcDepth := 24.0 arcWidth := getArcWidth(s.Box)
if width < arcDepth*2 { width -= 3 * arcWidth
arcDepth = width / 2.0 tl.X += arcWidth
}
width -= 3 * arcDepth
tl.X += arcDepth
return geo.NewBox(tl, width, s.Box.Height) return geo.NewBox(tl, width, s.Box.Height)
} }
func queueOuterPath(box *geo.Box) *svg.SvgPathContext { func queueOuterPath(box *geo.Box) *svg.SvgPathContext {
arcDepth := 24.0 arcWidth := getArcWidth(box)
multiplier := 0.45 multiplier := 0.45
if box.Width < arcDepth*2 {
arcDepth = box.Width / 2.0
}
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(arcDepth, 0)) pc.StartAt(pc.Absolute(arcWidth, 0))
pc.H(true, box.Width-2*arcDepth) pc.H(true, box.Width-2*arcWidth)
pc.C(false, box.Width, 0, box.Width, box.Height*multiplier, box.Width, box.Height/2.0) pc.C(false, box.Width, 0, box.Width, box.Height*multiplier, box.Width, box.Height/2.0)
pc.C(false, box.Width, box.Height-box.Height*multiplier, box.Width, box.Height, box.Width-arcDepth, box.Height) pc.C(false, box.Width, box.Height-box.Height*multiplier, box.Width, box.Height, box.Width-arcWidth, box.Height)
pc.H(true, -1*(box.Width-2*arcDepth)) pc.H(true, -1*(box.Width-2*arcWidth))
pc.C(false, 0, box.Height, 0, box.Height-box.Height*multiplier, 0, box.Height/2.0) pc.C(false, 0, box.Height, 0, box.Height-box.Height*multiplier, 0, box.Height/2.0)
pc.C(false, 0, box.Height*multiplier, 0, 0, arcDepth, 0) pc.C(false, 0, box.Height*multiplier, 0, 0, arcWidth, 0)
pc.Z() pc.Z()
return pc return pc
} }
func queueInnerPath(box *geo.Box) *svg.SvgPathContext { func queueInnerPath(box *geo.Box) *svg.SvgPathContext {
arcDepth := 24.0 arcWidth := getArcWidth(box)
multiplier := 0.45 multiplier := 0.45
if box.Width < arcDepth*2 {
arcDepth = box.Width / 2.0
}
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1) pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
pc.StartAt(pc.Absolute(box.Width-arcDepth, 0)) pc.StartAt(pc.Absolute(box.Width-arcWidth, 0))
pc.C(false, box.Width-2*arcDepth, 0, box.Width-2*arcDepth, box.Height*multiplier, box.Width-2*arcDepth, box.Height/2.0) pc.C(false, box.Width-2*arcWidth, 0, box.Width-2*arcWidth, box.Height*multiplier, box.Width-2*arcWidth, box.Height/2.0)
pc.C(false, box.Width-2*arcDepth, box.Height-box.Height*multiplier, box.Width-2*arcDepth, box.Height, box.Width-arcDepth, box.Height) pc.C(false, box.Width-2*arcWidth, box.Height-box.Height*multiplier, box.Width-2*arcWidth, box.Height, box.Width-arcWidth, box.Height)
return pc return pc
} }
@ -69,5 +70,13 @@ func (s shapeQueue) GetSVGPathData() []string {
return []string{ return []string{
queueOuterPath(s.Box).PathData(), queueOuterPath(s.Box).PathData(),
queueInnerPath(s.Box).PathData(), queueInnerPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeQueue) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
// 1 arc left, width+ padding, 2 arcs right
totalWidth := 3*defaultArcDepth + width + padding*2
return totalWidth, height + padding*2
}

View file

@ -20,6 +20,14 @@ func NewStep(box *geo.Box) Shape {
const STEP_WEDGE_WIDTH = 35.0 const STEP_WEDGE_WIDTH = 35.0
func (s shapeStep) GetInnerBox() *geo.Box {
width := s.Box.Width
tl := s.Box.TopLeft.Copy()
width -= 2 * STEP_WEDGE_WIDTH
tl.X += STEP_WEDGE_WIDTH
return geo.NewBox(tl, width, s.Box.Height)
}
func stepPath(box *geo.Box) *svg.SvgPathContext { func stepPath(box *geo.Box) *svg.SvgPathContext {
wedgeWidth := STEP_WEDGE_WIDTH wedgeWidth := STEP_WEDGE_WIDTH
if box.Width <= wedgeWidth { if box.Width <= wedgeWidth {
@ -43,5 +51,12 @@ func (s shapeStep) Perimeter() []geo.Intersectable {
func (s shapeStep) GetSVGPathData() []string { func (s shapeStep) GetSVGPathData() []string {
return []string{ return []string{
stepPath(s.Box).PathData(), stepPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeStep) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := width + padding*2 + 2*STEP_WEDGE_WIDTH
return totalWidth, height + padding*2
}

View file

@ -9,6 +9,8 @@ type shapeStoredData struct {
*baseShape *baseShape
} }
const storedDataWedgeWidth = 15.
func NewStoredData(box *geo.Box) Shape { func NewStoredData(box *geo.Box) Shape {
return shapeStoredData{ return shapeStoredData{
baseShape: &baseShape{ baseShape: &baseShape{
@ -18,8 +20,16 @@ func NewStoredData(box *geo.Box) Shape {
} }
} }
func (s shapeStoredData) GetInnerBox() *geo.Box {
width := s.Box.Width
tl := s.Box.TopLeft.Copy()
width -= 2 * storedDataWedgeWidth
tl.X += storedDataWedgeWidth
return geo.NewBox(tl, width, s.Box.Height)
}
func storedDataPath(box *geo.Box) *svg.SvgPathContext { func storedDataPath(box *geo.Box) *svg.SvgPathContext {
wedgeWidth := 15.0 wedgeWidth := storedDataWedgeWidth
multiplier := 0.27 multiplier := 0.27
if box.Width < wedgeWidth*2 { if box.Width < wedgeWidth*2 {
wedgeWidth = box.Width / 2.0 wedgeWidth = box.Width / 2.0
@ -43,5 +53,12 @@ func (s shapeStoredData) Perimeter() []geo.Intersectable {
func (s shapeStoredData) GetSVGPathData() []string { func (s shapeStoredData) GetSVGPathData() []string {
return []string{ return []string{
storedDataPath(s.Box).PathData(), storedDataPath(s.Box).PathData(),
// debugging
boxPath(s.GetInnerBox()).PathData(),
} }
} }
func (s shapeStoredData) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
totalWidth := width + padding*2 + 2*storedDataWedgeWidth
return totalWidth, height + padding*2
}