1 package label
2
3 import (
4 "math"
5
6 "oss.terrastruct.com/d2/lib/geo"
7 )
8
9
10 const LEFT_LABEL_POSITION = 1.0 / 4.0
11 const CENTER_LABEL_POSITION = 2.0 / 4.0
12 const RIGHT_LABEL_POSITION = 3.0 / 4.0
13
14
15 const PADDING = 5
16
17 type Position int8
18
19 const (
20 Unset Position = iota
21
22 OutsideTopLeft
23 OutsideTopCenter
24 OutsideTopRight
25
26 OutsideLeftTop
27 OutsideLeftMiddle
28 OutsideLeftBottom
29
30 OutsideRightTop
31 OutsideRightMiddle
32 OutsideRightBottom
33
34 OutsideBottomLeft
35 OutsideBottomCenter
36 OutsideBottomRight
37
38 InsideTopLeft
39 InsideTopCenter
40 InsideTopRight
41
42 InsideMiddleLeft
43 InsideMiddleCenter
44 InsideMiddleRight
45
46 InsideBottomLeft
47 InsideBottomCenter
48 InsideBottomRight
49
50 UnlockedTop
51 UnlockedMiddle
52 UnlockedBottom
53 )
54
55 func FromString(s string) Position {
56 switch s {
57 case "OUTSIDE_TOP_LEFT":
58 return OutsideTopLeft
59 case "OUTSIDE_TOP_CENTER":
60 return OutsideTopCenter
61 case "OUTSIDE_TOP_RIGHT":
62 return OutsideTopRight
63
64 case "OUTSIDE_LEFT_TOP":
65 return OutsideLeftTop
66 case "OUTSIDE_LEFT_MIDDLE":
67 return OutsideLeftMiddle
68 case "OUTSIDE_LEFT_BOTTOM":
69 return OutsideLeftBottom
70
71 case "OUTSIDE_RIGHT_TOP":
72 return OutsideRightTop
73 case "OUTSIDE_RIGHT_MIDDLE":
74 return OutsideRightMiddle
75 case "OUTSIDE_RIGHT_BOTTOM":
76 return OutsideRightBottom
77
78 case "OUTSIDE_BOTTOM_LEFT":
79 return OutsideBottomLeft
80 case "OUTSIDE_BOTTOM_CENTER":
81 return OutsideBottomCenter
82 case "OUTSIDE_BOTTOM_RIGHT":
83 return OutsideBottomRight
84
85 case "INSIDE_TOP_LEFT":
86 return InsideTopLeft
87 case "INSIDE_TOP_CENTER":
88 return InsideTopCenter
89 case "INSIDE_TOP_RIGHT":
90 return InsideTopRight
91
92 case "INSIDE_MIDDLE_LEFT":
93 return InsideMiddleLeft
94 case "INSIDE_MIDDLE_CENTER":
95 return InsideMiddleCenter
96 case "INSIDE_MIDDLE_RIGHT":
97 return InsideMiddleRight
98
99 case "INSIDE_BOTTOM_LEFT":
100 return InsideBottomLeft
101 case "INSIDE_BOTTOM_CENTER":
102 return InsideBottomCenter
103 case "INSIDE_BOTTOM_RIGHT":
104 return InsideBottomRight
105
106 case "UNLOCKED_TOP":
107 return UnlockedTop
108 case "UNLOCKED_MIDDLE":
109 return UnlockedMiddle
110 case "UNLOCKED_BOTTOM":
111 return UnlockedBottom
112 default:
113 return Unset
114 }
115 }
116
117 func (position Position) String() string {
118 switch position {
119 case OutsideTopLeft:
120 return "OUTSIDE_TOP_LEFT"
121 case OutsideTopCenter:
122 return "OUTSIDE_TOP_CENTER"
123 case OutsideTopRight:
124 return "OUTSIDE_TOP_RIGHT"
125
126 case OutsideLeftTop:
127 return "OUTSIDE_LEFT_TOP"
128 case OutsideLeftMiddle:
129 return "OUTSIDE_LEFT_MIDDLE"
130 case OutsideLeftBottom:
131 return "OUTSIDE_LEFT_BOTTOM"
132
133 case OutsideRightTop:
134 return "OUTSIDE_RIGHT_TOP"
135 case OutsideRightMiddle:
136 return "OUTSIDE_RIGHT_MIDDLE"
137 case OutsideRightBottom:
138 return "OUTSIDE_RIGHT_BOTTOM"
139
140 case OutsideBottomLeft:
141 return "OUTSIDE_BOTTOM_LEFT"
142 case OutsideBottomCenter:
143 return "OUTSIDE_BOTTOM_CENTER"
144 case OutsideBottomRight:
145 return "OUTSIDE_BOTTOM_RIGHT"
146
147 case InsideTopLeft:
148 return "INSIDE_TOP_LEFT"
149 case InsideTopCenter:
150 return "INSIDE_TOP_CENTER"
151 case InsideTopRight:
152 return "INSIDE_TOP_RIGHT"
153
154 case InsideMiddleLeft:
155 return "INSIDE_MIDDLE_LEFT"
156 case InsideMiddleCenter:
157 return "INSIDE_MIDDLE_CENTER"
158 case InsideMiddleRight:
159 return "INSIDE_MIDDLE_RIGHT"
160
161 case InsideBottomLeft:
162 return "INSIDE_BOTTOM_LEFT"
163 case InsideBottomCenter:
164 return "INSIDE_BOTTOM_CENTER"
165 case InsideBottomRight:
166 return "INSIDE_BOTTOM_RIGHT"
167
168 case UnlockedTop:
169 return "UNLOCKED_TOP"
170 case UnlockedMiddle:
171 return "UNLOCKED_MIDDLE"
172 case UnlockedBottom:
173 return "UNLOCKED_BOTTOM"
174
175 default:
176 return ""
177 }
178 }
179
180 func (position Position) IsShapePosition() bool {
181 switch position {
182 case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
183 OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
184 OutsideLeftTop, OutsideLeftMiddle, OutsideLeftBottom,
185 OutsideRightTop, OutsideRightMiddle, OutsideRightBottom,
186
187 InsideTopLeft, InsideTopCenter, InsideTopRight,
188 InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight,
189 InsideBottomLeft, InsideBottomCenter, InsideBottomRight:
190 return true
191 default:
192 return false
193 }
194 }
195
196 func (position Position) IsEdgePosition() bool {
197 switch position {
198 case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
199 InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight,
200 OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
201 UnlockedTop, UnlockedMiddle, UnlockedBottom:
202 return true
203 default:
204 return false
205 }
206 }
207
208 func (position Position) IsOutside() bool {
209 switch position {
210 case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
211 OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
212 OutsideLeftTop, OutsideLeftMiddle, OutsideLeftBottom,
213 OutsideRightTop, OutsideRightMiddle, OutsideRightBottom:
214 return true
215 default:
216 return false
217 }
218 }
219
220 func (position Position) IsUnlocked() bool {
221 switch position {
222 case UnlockedTop, UnlockedMiddle, UnlockedBottom:
223 return true
224 default:
225 return false
226 }
227 }
228
229 func (position Position) IsOnEdge() bool {
230 switch position {
231 case InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight, UnlockedMiddle:
232 return true
233 default:
234 return false
235 }
236 }
237
238 func (position Position) Mirrored() Position {
239 switch position {
240 case OutsideTopLeft:
241 return OutsideBottomRight
242 case OutsideTopCenter:
243 return OutsideBottomCenter
244 case OutsideTopRight:
245 return OutsideBottomLeft
246
247 case OutsideLeftTop:
248 return OutsideRightBottom
249 case OutsideLeftMiddle:
250 return OutsideRightMiddle
251 case OutsideLeftBottom:
252 return OutsideRightTop
253
254 case OutsideRightTop:
255 return OutsideLeftBottom
256 case OutsideRightMiddle:
257 return OutsideLeftMiddle
258 case OutsideRightBottom:
259 return OutsideLeftTop
260
261 case OutsideBottomLeft:
262 return OutsideTopRight
263 case OutsideBottomCenter:
264 return OutsideTopCenter
265 case OutsideBottomRight:
266 return OutsideTopLeft
267
268 case InsideTopLeft:
269 return InsideBottomRight
270 case InsideTopCenter:
271 return InsideBottomCenter
272 case InsideTopRight:
273 return InsideBottomLeft
274
275 case InsideMiddleLeft:
276 return InsideMiddleRight
277 case InsideMiddleCenter:
278 return InsideMiddleCenter
279 case InsideMiddleRight:
280 return InsideMiddleLeft
281
282 case InsideBottomLeft:
283 return InsideTopRight
284 case InsideBottomCenter:
285 return InsideTopCenter
286 case InsideBottomRight:
287 return InsideTopLeft
288
289 case UnlockedTop:
290 return UnlockedBottom
291 case UnlockedBottom:
292 return UnlockedTop
293 case UnlockedMiddle:
294 return UnlockedMiddle
295
296 default:
297 return Unset
298 }
299 }
300
301 func (labelPosition Position) GetPointOnBox(box *geo.Box, padding, width, height float64) *geo.Point {
302 p := box.TopLeft.Copy()
303 boxCenter := box.Center()
304
305 switch labelPosition {
306 case OutsideTopLeft:
307 p.X -= padding
308 p.Y -= padding + height
309 case OutsideTopCenter:
310 p.X = boxCenter.X - width/2
311 p.Y -= padding + height
312 case OutsideTopRight:
313 p.X += box.Width - width - padding
314 p.Y -= padding + height
315
316 case OutsideLeftTop:
317 p.X -= padding + width
318 p.Y += padding
319 case OutsideLeftMiddle:
320 p.X -= padding + width
321 p.Y = boxCenter.Y - height/2
322 case OutsideLeftBottom:
323 p.X -= padding + width
324 p.Y += box.Height - height - padding
325
326 case OutsideRightTop:
327 p.X += box.Width + padding
328 p.Y += padding
329 case OutsideRightMiddle:
330 p.X += box.Width + padding
331 p.Y = boxCenter.Y - height/2
332 case OutsideRightBottom:
333 p.X += box.Width + padding
334 p.Y += box.Height - height - padding
335
336 case OutsideBottomLeft:
337 p.X += padding
338 p.Y += box.Height + padding
339 case OutsideBottomCenter:
340 p.X = boxCenter.X - width/2
341 p.Y += box.Height + padding
342 case OutsideBottomRight:
343 p.X += box.Width - width - padding
344 p.Y += box.Height + padding
345
346 case InsideTopLeft:
347 p.X += padding
348 p.Y += padding
349 case InsideTopCenter:
350 p.X = boxCenter.X - width/2
351 p.Y += padding
352 case InsideTopRight:
353 p.X += box.Width - width - padding
354 p.Y += padding
355
356 case InsideMiddleLeft:
357 p.X += padding
358 p.Y = boxCenter.Y - height/2
359 case InsideMiddleCenter:
360 p.X = boxCenter.X - width/2
361 p.Y = boxCenter.Y - height/2
362 case InsideMiddleRight:
363 p.X += box.Width - width - padding
364 p.Y = boxCenter.Y - height/2
365
366 case InsideBottomLeft:
367 p.X += padding
368 p.Y += box.Height - height - padding
369 case InsideBottomCenter:
370 p.X = boxCenter.X - width/2
371 p.Y += box.Height - height - padding
372 case InsideBottomRight:
373 p.X += box.Width - width - padding
374 p.Y += box.Height - height - padding
375 }
376
377 return p
378 }
379
380
381
382 func (labelPosition Position) GetPointOnRoute(route geo.Route, strokeWidth, labelPercentage, width, height float64) (point *geo.Point, index int) {
383 totalLength := route.Length()
384 leftPosition := LEFT_LABEL_POSITION * totalLength
385 centerPosition := CENTER_LABEL_POSITION * totalLength
386 rightPosition := RIGHT_LABEL_POSITION * totalLength
387 unlockedPosition := labelPercentage * totalLength
388
389
390
391 getOffsetLabelPosition := func(basePoint, normStart, normEnd *geo.Point, flip bool) *geo.Point {
392
393 normalX, normalY := geo.GetUnitNormalVector(
394 normStart.X,
395 normStart.Y,
396 normEnd.X,
397 normEnd.Y,
398 )
399 if flip {
400 normalX *= -1
401 normalY *= -1
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 offsetX := strokeWidth/2 + float64(PADDING) + width/2
423 offsetY := strokeWidth/2 + float64(PADDING) + height/2
424
425 return geo.NewPoint(basePoint.X+normalX*offsetX, basePoint.Y+normalY*offsetY)
426 }
427
428 var labelCenter *geo.Point
429 switch labelPosition {
430 case InsideMiddleLeft:
431 labelCenter, index = route.GetPointAtDistance(leftPosition)
432 case InsideMiddleCenter:
433 labelCenter, index = route.GetPointAtDistance(centerPosition)
434 case InsideMiddleRight:
435 labelCenter, index = route.GetPointAtDistance(rightPosition)
436
437 case OutsideTopLeft:
438 basePoint, index := route.GetPointAtDistance(leftPosition)
439 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], true)
440 case OutsideTopCenter:
441 basePoint, index := route.GetPointAtDistance(centerPosition)
442 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], true)
443 case OutsideTopRight:
444 basePoint, index := route.GetPointAtDistance(rightPosition)
445 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], true)
446
447 case OutsideBottomLeft:
448 basePoint, index := route.GetPointAtDistance(leftPosition)
449 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], false)
450 case OutsideBottomCenter:
451 basePoint, index := route.GetPointAtDistance(centerPosition)
452 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], false)
453 case OutsideBottomRight:
454 basePoint, index := route.GetPointAtDistance(rightPosition)
455 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], false)
456
457 case UnlockedTop:
458 basePoint, index := route.GetPointAtDistance(unlockedPosition)
459 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], true)
460 case UnlockedMiddle:
461 labelCenter, index = route.GetPointAtDistance(unlockedPosition)
462 case UnlockedBottom:
463 basePoint, index := route.GetPointAtDistance(unlockedPosition)
464 labelCenter = getOffsetLabelPosition(basePoint, route[index], route[index+1], false)
465 default:
466 return nil, -1
467 }
468
469 labelCenter.X = chopPrecision(labelCenter.X - width/2)
470 labelCenter.Y = chopPrecision(labelCenter.Y - height/2)
471 return labelCenter, index
472 }
473
474
475 func chopPrecision(f float64) float64 {
476
477 return math.Round(float64(float32(f*10000)) / 10000)
478 }
479
View as plain text