2022-11-03 13:54:49 +00:00
|
|
|
package label
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"math"
|
|
|
|
|
|
|
|
|
|
"oss.terrastruct.com/d2/lib/geo"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// These are % locations where labels will be placed along the connection
|
|
|
|
|
const LEFT_LABEL_POSITION = 1.0 / 4.0
|
|
|
|
|
const CENTER_LABEL_POSITION = 2.0 / 4.0
|
|
|
|
|
const RIGHT_LABEL_POSITION = 3.0 / 4.0
|
|
|
|
|
|
|
|
|
|
// This is the space between a node border and its outside label
|
|
|
|
|
const PADDING = 5
|
|
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
type Position int8
|
2022-11-03 13:54:49 +00:00
|
|
|
|
|
|
|
|
const (
|
2023-07-17 21:21:36 +00:00
|
|
|
Unset Position = iota
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
OutsideTopLeft
|
|
|
|
|
OutsideTopCenter
|
|
|
|
|
OutsideTopRight
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
OutsideLeftTop
|
|
|
|
|
OutsideLeftMiddle
|
|
|
|
|
OutsideLeftBottom
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
OutsideRightTop
|
|
|
|
|
OutsideRightMiddle
|
|
|
|
|
OutsideRightBottom
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
OutsideBottomLeft
|
|
|
|
|
OutsideBottomCenter
|
|
|
|
|
OutsideBottomRight
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
InsideTopLeft
|
|
|
|
|
InsideTopCenter
|
|
|
|
|
InsideTopRight
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
InsideMiddleLeft
|
|
|
|
|
InsideMiddleCenter
|
|
|
|
|
InsideMiddleRight
|
2022-11-03 13:54:49 +00:00
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
InsideBottomLeft
|
|
|
|
|
InsideBottomCenter
|
|
|
|
|
InsideBottomRight
|
|
|
|
|
|
|
|
|
|
UnlockedTop
|
|
|
|
|
UnlockedMiddle
|
|
|
|
|
UnlockedBottom
|
2022-11-03 13:54:49 +00:00
|
|
|
)
|
|
|
|
|
|
2023-07-17 21:21:36 +00:00
|
|
|
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 ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 20:24:24 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 13:54:49 +00:00
|
|
|
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:
|
2023-07-17 21:21:36 +00:00
|
|
|
return Unset
|
2022-11-03 13:54:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return the top left point of a width x height label at the given label position on the route
|
2023-04-17 21:45:36 +00:00
|
|
|
// also return the index of the route segment that point is on
|
2023-04-15 02:08:29 +00:00
|
|
|
func (labelPosition Position) GetPointOnRoute(route geo.Route, strokeWidth, labelPercentage, width, height float64) (point *geo.Point, index int) {
|
2022-11-03 13:54:49 +00:00
|
|
|
totalLength := route.Length()
|
|
|
|
|
leftPosition := LEFT_LABEL_POSITION * totalLength
|
|
|
|
|
centerPosition := CENTER_LABEL_POSITION * totalLength
|
|
|
|
|
rightPosition := RIGHT_LABEL_POSITION * totalLength
|
|
|
|
|
unlockedPosition := labelPercentage * totalLength
|
|
|
|
|
|
|
|
|
|
// outside labels have to be offset in the direction of the edge's normal Vector
|
|
|
|
|
// Note: we flip the normal for Top labels but keep it as is for Bottom labels since positive Y is below in SVG
|
|
|
|
|
getOffsetLabelPosition := func(basePoint, normStart, normEnd *geo.Point, flip bool) *geo.Point {
|
|
|
|
|
// get the normal as a unit Vector so we can multiply to project in its direction
|
|
|
|
|
normalX, normalY := geo.GetUnitNormalVector(
|
|
|
|
|
normStart.X,
|
|
|
|
|
normStart.Y,
|
|
|
|
|
normEnd.X,
|
|
|
|
|
normEnd.Y,
|
|
|
|
|
)
|
|
|
|
|
if flip {
|
|
|
|
|
normalX *= -1
|
|
|
|
|
normalY *= -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Horizontal Edge with Outside Label | Vertical Edge with Outside Label
|
|
|
|
|
// ┌────────────────────┐ ┬ | ┌─┬─┐
|
|
|
|
|
// │ │ │ | │ │ │ ┌───────────┬───────────┐
|
|
|
|
|
// │ │ │ | │ e │ │ │ │
|
|
|
|
|
// ├────label─center────┤ ┬ ┼label height | │ d │ │ label │
|
|
|
|
|
// │ │ │ │ | │ g │ │ center │
|
|
|
|
|
// │ │ │ │ | │ e │ │ │ │
|
|
|
|
|
// └────────────────────┘ │ ┴ ┬ | │ │ │ └───────────┴───────────┘
|
|
|
|
|
// │ │ | └─┴─┘ offset
|
|
|
|
|
// offset│ │label padding | ├──────────────────┤
|
|
|
|
|
// │ │ |
|
|
|
|
|
// ┌──────────────────────┐ │ ┬ ┴ | ├───────────┼───────────┤
|
|
|
|
|
// │ │ │ │ | ├────┤ label width
|
|
|
|
|
// ├─────edge─center──────┤ ┴ ┼stroke width | label padding
|
|
|
|
|
// │ │ │ | ├─┼─┤
|
|
|
|
|
// └──────────────────────┘ ┴ | stroke width
|
|
|
|
|
//
|
|
|
|
|
// TODO: get actual edge stroke width on edge
|
|
|
|
|
offsetX := strokeWidth/2 + float64(PADDING) + width/2
|
|
|
|
|
offsetY := strokeWidth/2 + float64(PADDING) + height/2
|
|
|
|
|
|
2023-01-25 00:45:08 +00:00
|
|
|
return geo.NewPoint(basePoint.X+normalX*offsetX, basePoint.Y+normalY*offsetY)
|
2022-11-03 13:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var labelCenter *geo.Point
|
|
|
|
|
switch labelPosition {
|
|
|
|
|
case InsideMiddleLeft:
|
2023-04-15 02:08:29 +00:00
|
|
|
labelCenter, index = route.GetPointAtDistance(leftPosition)
|
2022-11-03 13:54:49 +00:00
|
|
|
case InsideMiddleCenter:
|
2023-04-15 02:08:29 +00:00
|
|
|
labelCenter, index = route.GetPointAtDistance(centerPosition)
|
2022-11-03 13:54:49 +00:00
|
|
|
case InsideMiddleRight:
|
2023-04-15 02:08:29 +00:00
|
|
|
labelCenter, index = route.GetPointAtDistance(rightPosition)
|
2022-11-03 13:54:49 +00:00
|
|
|
|
|
|
|
|
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:
|
2023-04-15 02:08:29 +00:00
|
|
|
labelCenter, index = route.GetPointAtDistance(unlockedPosition)
|
2022-11-03 13:54:49 +00:00
|
|
|
case UnlockedBottom:
|
|
|
|
|
basePoint, index := route.GetPointAtDistance(unlockedPosition)
|
|
|
|
|
labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], false)
|
|
|
|
|
default:
|
2023-04-15 02:08:29 +00:00
|
|
|
return nil, -1
|
2022-11-03 13:54:49 +00:00
|
|
|
}
|
|
|
|
|
// convert from center to top left
|
2023-01-25 00:45:08 +00:00
|
|
|
labelCenter.X = chopPrecision(labelCenter.X - width/2)
|
|
|
|
|
labelCenter.Y = chopPrecision(labelCenter.Y - height/2)
|
2023-04-15 02:08:29 +00:00
|
|
|
return labelCenter, index
|
2022-11-03 13:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO probably use math.Big
|
|
|
|
|
func chopPrecision(f float64) float64 {
|
2023-01-24 23:43:47 +00:00
|
|
|
// bring down to float32 precision before rounding for consistency across architectures
|
|
|
|
|
return math.Round(float64(float32(f*10000)) / 10000)
|
2022-11-03 13:54:49 +00:00
|
|
|
}
|