iteration 2
This commit is contained in:
parent
527edda636
commit
05a8e93379
1 changed files with 197 additions and 23 deletions
|
|
@ -240,7 +240,6 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
package d2cycle
|
package d2cycle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -254,9 +253,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MIN_RADIUS = 200
|
MIN_RADIUS = 200
|
||||||
PADDING = 20
|
PADDING = 20
|
||||||
ARC_STEPS = 60 // High resolution for perfect circles
|
ARC_STEPS = 60 // High resolution for perfect circles
|
||||||
)
|
)
|
||||||
|
|
||||||
func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error {
|
func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error {
|
||||||
|
|
@ -286,8 +285,8 @@ func calculateBaseRadius(objects []*d2graph.Object) float64 {
|
||||||
size := math.Max(obj.Width, obj.Height)
|
size := math.Max(obj.Width, obj.Height)
|
||||||
maxSize = math.Max(maxSize, size)
|
maxSize = math.Max(maxSize, size)
|
||||||
}
|
}
|
||||||
radius := (maxSize + 2*PADDING) / (2 * math.Sin(math.Pi/numNodes))
|
minRadius := (maxSize/2 + PADDING) / math.Sin(math.Pi/numNodes)
|
||||||
return math.Max(radius, MIN_RADIUS)
|
return math.Max(minRadius, MIN_RADIUS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func positionObjects(objects []*d2graph.Object, radius float64) {
|
func positionObjects(objects []*d2graph.Object, radius float64) {
|
||||||
|
|
@ -313,13 +312,13 @@ func createPerfectArc(edge *d2graph.Edge, baseRadius float64) {
|
||||||
|
|
||||||
srcCenter := edge.Src.Center()
|
srcCenter := edge.Src.Center()
|
||||||
dstCenter := edge.Dst.Center()
|
dstCenter := edge.Dst.Center()
|
||||||
center := geo.NewPoint(0, 0) // Layout center
|
layoutCenter := geo.NewPoint(0, 0)
|
||||||
|
|
||||||
// Calculate angles with proper wrapping
|
// Calculate angles with proper wrapping
|
||||||
startAngle := math.Atan2(srcCenter.Y-center.Y, srcCenter.X-center.X)
|
startAngle := math.Atan2(srcCenter.Y-layoutCenter.Y, srcCenter.X-layoutCenter.X)
|
||||||
endAngle := math.Atan2(dstCenter.Y-center.Y, dstCenter.X-center.X)
|
endAngle := math.Atan2(dstCenter.Y-layoutCenter.Y, dstCenter.X-layoutCenter.X)
|
||||||
|
|
||||||
// Handle angle wrapping for shortest path
|
// Calculate angular distance taking shortest path
|
||||||
angleDiff := endAngle - startAngle
|
angleDiff := endAngle - startAngle
|
||||||
if angleDiff < 0 {
|
if angleDiff < 0 {
|
||||||
angleDiff += 2 * math.Pi
|
angleDiff += 2 * math.Pi
|
||||||
|
|
@ -333,12 +332,12 @@ func createPerfectArc(edge *d2graph.Edge, baseRadius float64) {
|
||||||
for i := 0; i <= ARC_STEPS; i++ {
|
for i := 0; i <= ARC_STEPS; i++ {
|
||||||
t := float64(i) / ARC_STEPS
|
t := float64(i) / ARC_STEPS
|
||||||
currentAngle := startAngle + t*angleDiff
|
currentAngle := startAngle + t*angleDiff
|
||||||
x := center.X + baseRadius*math.Cos(currentAngle)
|
x := layoutCenter.X + baseRadius*math.Cos(currentAngle)
|
||||||
y := center.Y + baseRadius*math.Sin(currentAngle)
|
y := layoutCenter.Y + baseRadius*math.Sin(currentAngle)
|
||||||
path = append(path, geo.NewPoint(x, y))
|
path = append(path, geo.NewPoint(x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip to shape boundaries while preserving arc
|
// Clip to shape boundaries while preserving arc properties
|
||||||
edge.Route = path
|
edge.Route = path
|
||||||
startIdx, endIdx := edge.TraceToShape(edge.Route, 0, len(edge.Route)-1)
|
startIdx, endIdx := edge.TraceToShape(edge.Route, 0, len(edge.Route)-1)
|
||||||
|
|
||||||
|
|
@ -346,7 +345,7 @@ func createPerfectArc(edge *d2graph.Edge, baseRadius float64) {
|
||||||
if startIdx < endIdx {
|
if startIdx < endIdx {
|
||||||
edge.Route = edge.Route[startIdx : endIdx+1]
|
edge.Route = edge.Route[startIdx : endIdx+1]
|
||||||
|
|
||||||
// Ensure minimum points for smooth rendering
|
// Ensure minimal points for smooth rendering
|
||||||
if len(edge.Route) < 3 {
|
if len(edge.Route) < 3 {
|
||||||
edge.Route = []*geo.Point{path[0], path[len(path)-1]}
|
edge.Route = []*geo.Point{path[0], path[len(path)-1]}
|
||||||
}
|
}
|
||||||
|
|
@ -355,6 +354,28 @@ func createPerfectArc(edge *d2graph.Edge, baseRadius float64) {
|
||||||
edge.IsCurve = true
|
edge.IsCurve = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep existing helper functions (positionLabelsIcons, boxContains, boxIntersections)
|
||||||
|
// Helper if your geo.Box doesn’t implement Contains()
|
||||||
|
func boxContains(b *geo.Box, p *geo.Point) bool {
|
||||||
|
// typical bounding-box check
|
||||||
|
return p.X >= b.TopLeft.X &&
|
||||||
|
p.X <= b.TopLeft.X+b.Width &&
|
||||||
|
p.Y >= b.TopLeft.Y &&
|
||||||
|
p.Y <= b.TopLeft.Y+b.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper if your geo.Box doesn’t implement Intersections(geo.Segment) yet
|
||||||
|
func boxIntersections(b *geo.Box, seg geo.Segment) []*geo.Point {
|
||||||
|
// We'll assume d2's standard geo.Box has a built-in Intersections(*Segment) method.
|
||||||
|
// If not, implement manually. For example, checking each of the 4 edges:
|
||||||
|
// left, right, top, bottom
|
||||||
|
// For simplicity, if you do have b.Intersections(...) you can just do:
|
||||||
|
// return b.Intersections(seg)
|
||||||
|
return b.Intersections(seg)
|
||||||
|
// If you don't have that, you'd code the line-rect intersection yourself.
|
||||||
|
}
|
||||||
|
|
||||||
|
// positionLabelsIcons is basically your logic that sets default label/icon positions if needed
|
||||||
func positionLabelsIcons(obj *d2graph.Object) {
|
func positionLabelsIcons(obj *d2graph.Object) {
|
||||||
// If there's an icon but no icon position, give it a default
|
// If there's an icon but no icon position, give it a default
|
||||||
if obj.Icon != nil && obj.IconPosition == nil {
|
if obj.Icon != nil && obj.IconPosition == nil {
|
||||||
|
|
@ -371,6 +392,7 @@ func positionLabelsIcons(obj *d2graph.Object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there's a label but no label position, give it a default
|
||||||
if obj.HasLabel() && obj.LabelPosition == nil {
|
if obj.HasLabel() && obj.LabelPosition == nil {
|
||||||
if len(obj.ChildrenArray) > 0 {
|
if len(obj.ChildrenArray) > 0 {
|
||||||
obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
|
obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
|
||||||
|
|
@ -382,6 +404,7 @@ func positionLabelsIcons(obj *d2graph.Object) {
|
||||||
obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
|
obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the label is bigger than the shape, fallback to outside positions
|
||||||
if float64(obj.LabelDimensions.Width) > obj.Width ||
|
if float64(obj.LabelDimensions.Width) > obj.Width ||
|
||||||
float64(obj.LabelDimensions.Height) > obj.Height {
|
float64(obj.LabelDimensions.Height) > obj.Height {
|
||||||
if len(obj.ChildrenArray) > 0 {
|
if len(obj.ChildrenArray) > 0 {
|
||||||
|
|
@ -392,14 +415,165 @@ func positionLabelsIcons(obj *d2graph.Object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// package d2cycle
|
||||||
|
|
||||||
func boxContains(b *geo.Box, p *geo.Point) bool {
|
// import (
|
||||||
return p.X >= b.TopLeft.X &&
|
// "context"
|
||||||
p.X <= b.TopLeft.X+b.Width &&
|
// "math"
|
||||||
p.Y >= b.TopLeft.Y &&
|
|
||||||
p.Y <= b.TopLeft.Y+b.Height
|
|
||||||
}
|
|
||||||
|
|
||||||
func boxIntersections(b *geo.Box, seg geo.Segment) []*geo.Point {
|
// "oss.terrastruct.com/d2/d2graph"
|
||||||
return b.Intersections(seg)
|
// "oss.terrastruct.com/d2/lib/geo"
|
||||||
}
|
// "oss.terrastruct.com/d2/lib/label"
|
||||||
|
// "oss.terrastruct.com/util-go/go2"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// MIN_RADIUS = 200
|
||||||
|
// PADDING = 20
|
||||||
|
// ARC_STEPS = 60 // High resolution for perfect circles
|
||||||
|
// )
|
||||||
|
|
||||||
|
// func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error {
|
||||||
|
// objects := g.Root.ChildrenArray
|
||||||
|
// if len(objects) == 0 {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, obj := range g.Objects {
|
||||||
|
// positionLabelsIcons(obj)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// baseRadius := calculateBaseRadius(objects)
|
||||||
|
// positionObjects(objects, baseRadius)
|
||||||
|
|
||||||
|
// for _, edge := range g.Edges {
|
||||||
|
// createPerfectArc(edge, baseRadius)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func calculateBaseRadius(objects []*d2graph.Object) float64 {
|
||||||
|
// numNodes := float64(len(objects))
|
||||||
|
// maxSize := 0.0
|
||||||
|
// for _, obj := range objects {
|
||||||
|
// size := math.Max(obj.Width, obj.Height)
|
||||||
|
// maxSize = math.Max(maxSize, size)
|
||||||
|
// }
|
||||||
|
// radius := (maxSize + 2*PADDING) / (2 * math.Sin(math.Pi/numNodes))
|
||||||
|
// return math.Max(radius, MIN_RADIUS)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func positionObjects(objects []*d2graph.Object, radius float64) {
|
||||||
|
// numObjects := float64(len(objects))
|
||||||
|
// angleOffset := -math.Pi / 2
|
||||||
|
|
||||||
|
// for i, obj := range objects {
|
||||||
|
// angle := angleOffset + (2*math.Pi*float64(i))/numObjects
|
||||||
|
// x := radius * math.Cos(angle)
|
||||||
|
// y := radius * math.Sin(angle)
|
||||||
|
|
||||||
|
// obj.TopLeft = geo.NewPoint(
|
||||||
|
// x-obj.Width/2,
|
||||||
|
// y-obj.Height/2,
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func createPerfectArc(edge *d2graph.Edge, baseRadius float64) {
|
||||||
|
// if edge.Src == nil || edge.Dst == nil || edge.Src == edge.Dst {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// srcCenter := edge.Src.Center()
|
||||||
|
// dstCenter := edge.Dst.Center()
|
||||||
|
// center := geo.NewPoint(0, 0) // Layout center
|
||||||
|
|
||||||
|
// // Calculate angles with proper wrapping
|
||||||
|
// startAngle := math.Atan2(srcCenter.Y-center.Y, srcCenter.X-center.X)
|
||||||
|
// endAngle := math.Atan2(dstCenter.Y-center.Y, dstCenter.X-center.X)
|
||||||
|
|
||||||
|
// // Handle angle wrapping for shortest path
|
||||||
|
// angleDiff := endAngle - startAngle
|
||||||
|
// if angleDiff < 0 {
|
||||||
|
// angleDiff += 2 * math.Pi
|
||||||
|
// }
|
||||||
|
// if angleDiff > math.Pi {
|
||||||
|
// angleDiff -= 2 * math.Pi
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Generate perfect circular arc
|
||||||
|
// path := make([]*geo.Point, 0, ARC_STEPS+1)
|
||||||
|
// for i := 0; i <= ARC_STEPS; i++ {
|
||||||
|
// t := float64(i) / ARC_STEPS
|
||||||
|
// currentAngle := startAngle + t*angleDiff
|
||||||
|
// x := center.X + baseRadius*math.Cos(currentAngle)
|
||||||
|
// y := center.Y + baseRadius*math.Sin(currentAngle)
|
||||||
|
// path = append(path, geo.NewPoint(x, y))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Clip to shape boundaries while preserving arc
|
||||||
|
// edge.Route = path
|
||||||
|
// startIdx, endIdx := edge.TraceToShape(edge.Route, 0, len(edge.Route)-1)
|
||||||
|
|
||||||
|
// // Maintain smooth arc after clipping
|
||||||
|
// if startIdx < endIdx {
|
||||||
|
// edge.Route = edge.Route[startIdx : endIdx+1]
|
||||||
|
|
||||||
|
// // Ensure minimum points for smooth rendering
|
||||||
|
// if len(edge.Route) < 3 {
|
||||||
|
// edge.Route = []*geo.Point{path[0], path[len(path)-1]}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// edge.IsCurve = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func positionLabelsIcons(obj *d2graph.Object) {
|
||||||
|
// // If there's an icon but no icon position, give it a default
|
||||||
|
// if obj.Icon != nil && obj.IconPosition == nil {
|
||||||
|
// if len(obj.ChildrenArray) > 0 {
|
||||||
|
// obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String())
|
||||||
|
// if obj.LabelPosition == nil {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.OutsideTopRight.String())
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// } else if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" {
|
||||||
|
// obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String())
|
||||||
|
// } else {
|
||||||
|
// obj.IconPosition = go2.Pointer(label.InsideMiddleCenter.String())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if obj.HasLabel() && obj.LabelPosition == nil {
|
||||||
|
// if len(obj.ChildrenArray) > 0 {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
|
||||||
|
// } else if obj.HasOutsideBottomLabel() {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
|
||||||
|
// } else if obj.Icon != nil {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.InsideTopCenter.String())
|
||||||
|
// } else {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if float64(obj.LabelDimensions.Width) > obj.Width ||
|
||||||
|
// float64(obj.LabelDimensions.Height) > obj.Height {
|
||||||
|
// if len(obj.ChildrenArray) > 0 {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
|
||||||
|
// } else {
|
||||||
|
// obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func boxContains(b *geo.Box, p *geo.Point) bool {
|
||||||
|
// return p.X >= b.TopLeft.X &&
|
||||||
|
// p.X <= b.TopLeft.X+b.Width &&
|
||||||
|
// p.Y >= b.TopLeft.Y &&
|
||||||
|
// p.Y <= b.TopLeft.Y+b.Height
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func boxIntersections(b *geo.Box, seg geo.Segment) []*geo.Point {
|
||||||
|
// return b.Intersections(seg)
|
||||||
|
// }
|
||||||
Loading…
Reference in a new issue