...

Source file src/oss.terrastruct.com/d2/lib/shape/shape_oval.go

Documentation: oss.terrastruct.com/d2/lib/shape

     1  package shape
     2  
     3  import (
     4  	"math"
     5  
     6  	"oss.terrastruct.com/d2/lib/geo"
     7  	"oss.terrastruct.com/util-go/go2"
     8  )
     9  
    10  const OVAL_AR_LIMIT = 3.
    11  
    12  type shapeOval struct {
    13  	*baseShape
    14  }
    15  
    16  func NewOval(box *geo.Box) Shape {
    17  	shape := shapeOval{
    18  		baseShape: &baseShape{
    19  			Type: OVAL_TYPE,
    20  			Box:  box,
    21  		},
    22  	}
    23  	shape.FullShape = go2.Pointer(Shape(shape))
    24  	return shape
    25  }
    26  
    27  func (s shapeOval) GetInnerBox() *geo.Box {
    28  	width := s.Box.Width
    29  	height := s.Box.Height
    30  	insideTL := s.GetInsidePlacement(width, height, 0, 0)
    31  	tl := s.Box.TopLeft.Copy()
    32  	width -= 2 * (insideTL.X - tl.X)
    33  	height -= 2 * (insideTL.Y - tl.Y)
    34  	return geo.NewBox(&insideTL, width, height)
    35  }
    36  
    37  func (s shapeOval) GetDimensionsToFit(width, height, paddingX, paddingY float64) (float64, float64) {
    38  	theta := float64(float32(math.Atan2(height, width)))
    39  	// add padding in direction of diagonal so there is padding distance between top left and border
    40  	paddedWidth := width + paddingX*math.Cos(theta)
    41  	paddedHeight := height + paddingY*math.Sin(theta)
    42  	// see https://stackoverflow.com/questions/433371/ellipse-bounding-a-rectangle
    43  	totalWidth, totalHeight := math.Ceil(math.Sqrt2*paddedWidth), math.Ceil(math.Sqrt2*paddedHeight)
    44  
    45  	// prevent oval from becoming too flat
    46  	totalWidth, totalHeight = LimitAR(totalWidth, totalHeight, OVAL_AR_LIMIT)
    47  	return totalWidth, totalHeight
    48  }
    49  
    50  func (s shapeOval) GetInsidePlacement(width, height, paddingX, paddingY float64) geo.Point {
    51  	// showing the top left arc of the ellipse (drawn with '*')
    52  	// ┌──────────────────* ┬
    53  	// │         *        │ │ry
    54  	// │     *┌───────────┤ │ ┬
    55  	// │  *   │           │ │ │sin*r
    56  	// │*     │           │ │ │
    57  	// *──────┴───────────┼ ┴ ┴
    58  	// ├────────rx────────┤
    59  	//        ├───cos*r───┤
    60  	rx := s.Box.Width / 2
    61  	ry := s.Box.Height / 2
    62  	theta := float64(float32(math.Atan2(ry, rx)))
    63  	sin := math.Sin(theta)
    64  	cos := math.Cos(theta)
    65  	// r is the ellipse radius on the line between node.TopLeft and the ellipse center
    66  	// see https://math.stackexchange.com/questions/432902/how-to-get-the-radius-of-an-ellipse-at-a-specific-angle-by-knowing-its-semi-majo
    67  	r := rx * ry / math.Sqrt(math.Pow(rx*sin, 2)+math.Pow(ry*cos, 2))
    68  	// we want to offset r-padding/2 away from the center
    69  	return *geo.NewPoint(s.Box.TopLeft.X+math.Ceil(rx-cos*(r-paddingX/2)), s.Box.TopLeft.Y+math.Ceil(ry-sin*(r-paddingY/2)))
    70  }
    71  
    72  func (s shapeOval) Perimeter() []geo.Intersectable {
    73  	return []geo.Intersectable{geo.NewEllipse(s.Box.Center(), s.Box.Width/2, s.Box.Height/2)}
    74  }
    75  

View as plain text