package label
import (
"math"
"oss.terrastruct.com/d2/lib/geo"
)
const LEFT_LABEL_POSITION = 1.0 / 4.0
const CENTER_LABEL_POSITION = 2.0 / 4.0
const RIGHT_LABEL_POSITION = 3.0 / 4.0
const PADDING = 5
type Position int8
const (
Unset Position = iota
OutsideTopLeft
OutsideTopCenter
OutsideTopRight
OutsideLeftTop
OutsideLeftMiddle
OutsideLeftBottom
OutsideRightTop
OutsideRightMiddle
OutsideRightBottom
OutsideBottomLeft
OutsideBottomCenter
OutsideBottomRight
InsideTopLeft
InsideTopCenter
InsideTopRight
InsideMiddleLeft
InsideMiddleCenter
InsideMiddleRight
InsideBottomLeft
InsideBottomCenter
InsideBottomRight
UnlockedTop
UnlockedMiddle
UnlockedBottom
)
func FromString (s string ) Position {
switch s {
case "OUTSIDE_TOP_LEFT" :
return OutsideTopLeft
case "OUTSIDE_TOP_CENTER" :
return OutsideTopCenter
case "OUTSIDE_TOP_RIGHT" :
return OutsideTopRight
case "OUTSIDE_LEFT_TOP" :
return OutsideLeftTop
case "OUTSIDE_LEFT_MIDDLE" :
return OutsideLeftMiddle
case "OUTSIDE_LEFT_BOTTOM" :
return OutsideLeftBottom
case "OUTSIDE_RIGHT_TOP" :
return OutsideRightTop
case "OUTSIDE_RIGHT_MIDDLE" :
return OutsideRightMiddle
case "OUTSIDE_RIGHT_BOTTOM" :
return OutsideRightBottom
case "OUTSIDE_BOTTOM_LEFT" :
return OutsideBottomLeft
case "OUTSIDE_BOTTOM_CENTER" :
return OutsideBottomCenter
case "OUTSIDE_BOTTOM_RIGHT" :
return OutsideBottomRight
case "INSIDE_TOP_LEFT" :
return InsideTopLeft
case "INSIDE_TOP_CENTER" :
return InsideTopCenter
case "INSIDE_TOP_RIGHT" :
return InsideTopRight
case "INSIDE_MIDDLE_LEFT" :
return InsideMiddleLeft
case "INSIDE_MIDDLE_CENTER" :
return InsideMiddleCenter
case "INSIDE_MIDDLE_RIGHT" :
return InsideMiddleRight
case "INSIDE_BOTTOM_LEFT" :
return InsideBottomLeft
case "INSIDE_BOTTOM_CENTER" :
return InsideBottomCenter
case "INSIDE_BOTTOM_RIGHT" :
return InsideBottomRight
case "UNLOCKED_TOP" :
return UnlockedTop
case "UNLOCKED_MIDDLE" :
return UnlockedMiddle
case "UNLOCKED_BOTTOM" :
return UnlockedBottom
default :
return Unset
}
}
func (position Position ) String () string {
switch position {
case OutsideTopLeft :
return "OUTSIDE_TOP_LEFT"
case OutsideTopCenter :
return "OUTSIDE_TOP_CENTER"
case OutsideTopRight :
return "OUTSIDE_TOP_RIGHT"
case OutsideLeftTop :
return "OUTSIDE_LEFT_TOP"
case OutsideLeftMiddle :
return "OUTSIDE_LEFT_MIDDLE"
case OutsideLeftBottom :
return "OUTSIDE_LEFT_BOTTOM"
case OutsideRightTop :
return "OUTSIDE_RIGHT_TOP"
case OutsideRightMiddle :
return "OUTSIDE_RIGHT_MIDDLE"
case OutsideRightBottom :
return "OUTSIDE_RIGHT_BOTTOM"
case OutsideBottomLeft :
return "OUTSIDE_BOTTOM_LEFT"
case OutsideBottomCenter :
return "OUTSIDE_BOTTOM_CENTER"
case OutsideBottomRight :
return "OUTSIDE_BOTTOM_RIGHT"
case InsideTopLeft :
return "INSIDE_TOP_LEFT"
case InsideTopCenter :
return "INSIDE_TOP_CENTER"
case InsideTopRight :
return "INSIDE_TOP_RIGHT"
case InsideMiddleLeft :
return "INSIDE_MIDDLE_LEFT"
case InsideMiddleCenter :
return "INSIDE_MIDDLE_CENTER"
case InsideMiddleRight :
return "INSIDE_MIDDLE_RIGHT"
case InsideBottomLeft :
return "INSIDE_BOTTOM_LEFT"
case InsideBottomCenter :
return "INSIDE_BOTTOM_CENTER"
case InsideBottomRight :
return "INSIDE_BOTTOM_RIGHT"
case UnlockedTop :
return "UNLOCKED_TOP"
case UnlockedMiddle :
return "UNLOCKED_MIDDLE"
case UnlockedBottom :
return "UNLOCKED_BOTTOM"
default :
return ""
}
}
func (position Position ) IsShapePosition () bool {
switch position {
case OutsideTopLeft , OutsideTopCenter , OutsideTopRight ,
OutsideBottomLeft , OutsideBottomCenter , OutsideBottomRight ,
OutsideLeftTop , OutsideLeftMiddle , OutsideLeftBottom ,
OutsideRightTop , OutsideRightMiddle , OutsideRightBottom ,
InsideTopLeft , InsideTopCenter , InsideTopRight ,
InsideMiddleLeft , InsideMiddleCenter , InsideMiddleRight ,
InsideBottomLeft , InsideBottomCenter , InsideBottomRight :
return true
default :
return false
}
}
func (position Position ) IsEdgePosition () bool {
switch position {
case OutsideTopLeft , OutsideTopCenter , OutsideTopRight ,
InsideMiddleLeft , InsideMiddleCenter , InsideMiddleRight ,
OutsideBottomLeft , OutsideBottomCenter , OutsideBottomRight ,
UnlockedTop , UnlockedMiddle , UnlockedBottom :
return true
default :
return false
}
}
func (position Position ) IsOutside () bool {
switch position {
case OutsideTopLeft , OutsideTopCenter , OutsideTopRight ,
OutsideBottomLeft , OutsideBottomCenter , OutsideBottomRight ,
OutsideLeftTop , OutsideLeftMiddle , OutsideLeftBottom ,
OutsideRightTop , OutsideRightMiddle , OutsideRightBottom :
return true
default :
return false
}
}
func (position Position ) IsUnlocked () bool {
switch position {
case UnlockedTop , UnlockedMiddle , UnlockedBottom :
return true
default :
return false
}
}
func (position Position ) IsOnEdge () bool {
switch position {
case InsideMiddleLeft , InsideMiddleCenter , InsideMiddleRight , UnlockedMiddle :
return true
default :
return false
}
}
func (position Position ) Mirrored () Position {
switch position {
case OutsideTopLeft :
return OutsideBottomRight
case OutsideTopCenter :
return OutsideBottomCenter
case OutsideTopRight :
return OutsideBottomLeft
case OutsideLeftTop :
return OutsideRightBottom
case OutsideLeftMiddle :
return OutsideRightMiddle
case OutsideLeftBottom :
return OutsideRightTop
case OutsideRightTop :
return OutsideLeftBottom
case OutsideRightMiddle :
return OutsideLeftMiddle
case OutsideRightBottom :
return OutsideLeftTop
case OutsideBottomLeft :
return OutsideTopRight
case OutsideBottomCenter :
return OutsideTopCenter
case OutsideBottomRight :
return OutsideTopLeft
case InsideTopLeft :
return InsideBottomRight
case InsideTopCenter :
return InsideBottomCenter
case InsideTopRight :
return InsideBottomLeft
case InsideMiddleLeft :
return InsideMiddleRight
case InsideMiddleCenter :
return InsideMiddleCenter
case InsideMiddleRight :
return InsideMiddleLeft
case InsideBottomLeft :
return InsideTopRight
case InsideBottomCenter :
return InsideTopCenter
case InsideBottomRight :
return InsideTopLeft
case UnlockedTop :
return UnlockedBottom
case UnlockedBottom :
return UnlockedTop
case UnlockedMiddle :
return UnlockedMiddle
default :
return Unset
}
}
func (labelPosition Position ) GetPointOnBox (box *geo .Box , padding , width , height float64 ) *geo .Point {
p := box .TopLeft .Copy ()
boxCenter := box .Center ()
switch labelPosition {
case OutsideTopLeft :
p .X -= padding
p .Y -= padding + height
case OutsideTopCenter :
p .X = boxCenter .X - width /2
p .Y -= padding + height
case OutsideTopRight :
p .X += box .Width - width - padding
p .Y -= padding + height
case OutsideLeftTop :
p .X -= padding + width
p .Y += padding
case OutsideLeftMiddle :
p .X -= padding + width
p .Y = boxCenter .Y - height /2
case OutsideLeftBottom :
p .X -= padding + width
p .Y += box .Height - height - padding
case OutsideRightTop :
p .X += box .Width + padding
p .Y += padding
case OutsideRightMiddle :
p .X += box .Width + padding
p .Y = boxCenter .Y - height /2
case OutsideRightBottom :
p .X += box .Width + padding
p .Y += box .Height - height - padding
case OutsideBottomLeft :
p .X += padding
p .Y += box .Height + padding
case OutsideBottomCenter :
p .X = boxCenter .X - width /2
p .Y += box .Height + padding
case OutsideBottomRight :
p .X += box .Width - width - padding
p .Y += box .Height + padding
case InsideTopLeft :
p .X += padding
p .Y += padding
case InsideTopCenter :
p .X = boxCenter .X - width /2
p .Y += padding
case InsideTopRight :
p .X += box .Width - width - padding
p .Y += padding
case InsideMiddleLeft :
p .X += padding
p .Y = boxCenter .Y - height /2
case InsideMiddleCenter :
p .X = boxCenter .X - width /2
p .Y = boxCenter .Y - height /2
case InsideMiddleRight :
p .X += box .Width - width - padding
p .Y = boxCenter .Y - height /2
case InsideBottomLeft :
p .X += padding
p .Y += box .Height - height - padding
case InsideBottomCenter :
p .X = boxCenter .X - width /2
p .Y += box .Height - height - padding
case InsideBottomRight :
p .X += box .Width - width - padding
p .Y += box .Height - height - padding
}
return p
}
func (labelPosition Position ) GetPointOnRoute (route geo .Route , strokeWidth , labelPercentage , width , height float64 ) (point *geo .Point , index int ) {
totalLength := route .Length ()
leftPosition := LEFT_LABEL_POSITION * totalLength
centerPosition := CENTER_LABEL_POSITION * totalLength
rightPosition := RIGHT_LABEL_POSITION * totalLength
unlockedPosition := labelPercentage * totalLength
getOffsetLabelPosition := func (basePoint , normStart , normEnd *geo .Point , flip bool ) *geo .Point {
normalX , normalY := geo .GetUnitNormalVector (
normStart .X ,
normStart .Y ,
normEnd .X ,
normEnd .Y ,
)
if flip {
normalX *= -1
normalY *= -1
}
offsetX := strokeWidth /2 + float64 (PADDING ) + width /2
offsetY := strokeWidth /2 + float64 (PADDING ) + height /2
return geo .NewPoint (basePoint .X +normalX *offsetX , basePoint .Y +normalY *offsetY )
}
var labelCenter *geo .Point
switch labelPosition {
case InsideMiddleLeft :
labelCenter , index = route .GetPointAtDistance (leftPosition )
case InsideMiddleCenter :
labelCenter , index = route .GetPointAtDistance (centerPosition )
case InsideMiddleRight :
labelCenter , index = route .GetPointAtDistance (rightPosition )
case OutsideTopLeft :
basePoint , index := route .GetPointAtDistance (leftPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], true )
case OutsideTopCenter :
basePoint , index := route .GetPointAtDistance (centerPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], true )
case OutsideTopRight :
basePoint , index := route .GetPointAtDistance (rightPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], true )
case OutsideBottomLeft :
basePoint , index := route .GetPointAtDistance (leftPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], false )
case OutsideBottomCenter :
basePoint , index := route .GetPointAtDistance (centerPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], false )
case OutsideBottomRight :
basePoint , index := route .GetPointAtDistance (rightPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], false )
case UnlockedTop :
basePoint , index := route .GetPointAtDistance (unlockedPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], true )
case UnlockedMiddle :
labelCenter , index = route .GetPointAtDistance (unlockedPosition )
case UnlockedBottom :
basePoint , index := route .GetPointAtDistance (unlockedPosition )
labelCenter = getOffsetLabelPosition (basePoint , route [index ], route [index +1 ], false )
default :
return nil , -1
}
labelCenter .X = chopPrecision (labelCenter .X - width /2 )
labelCenter .Y = chopPrecision (labelCenter .Y - height /2 )
return labelCenter , index
}
func chopPrecision(f float64 ) float64 {
return math .Round (float64 (float32 (f *10000 )) / 10000 )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .