Re-lay out diagrams to give crossed edge labels room
This commit is contained in:
@@ -452,7 +452,7 @@ private func headerSize(_ n: BoxNode) -> CGSize {
|
||||
private struct Sizer {
|
||||
let pairGaps: [String: CGFloat]
|
||||
|
||||
init(_ rows: [BoxRow], _ edges: [BoxEdge]) {
|
||||
init(_ rows: [BoxRow], _ edges: [BoxEdge], _ extra: [Int: CGFloat]) {
|
||||
var parents: [String: String] = [:]
|
||||
func walk(_ n: BoxNode) {
|
||||
for c in n.children {
|
||||
@@ -469,8 +469,8 @@ private struct Sizer {
|
||||
return out
|
||||
}
|
||||
var gaps: [String: CGFloat] = [:]
|
||||
for e in edges {
|
||||
let need = e.label.map { ceil(textSize($0, edgeLabelFont).width) + labelRoom } ?? edgeGap
|
||||
for (i, e) in edges.enumerated() {
|
||||
let need = (e.label.map { ceil(textSize($0, edgeLabelFont).width) + labelRoom } ?? edgeGap) + (extra[i] ?? 0)
|
||||
for a in chain(e.from) {
|
||||
for b in chain(e.to) where a != b && parents[a] == parents[b] {
|
||||
let key = Sizer.pair(a, b)
|
||||
@@ -598,16 +598,23 @@ private func projectEdges(_ d: BoxDiagram, _ level: String) -> (edges: [BoxEdge]
|
||||
@MainActor
|
||||
private func layoutLevel(_ d: BoxDiagram, _ level: String, _ done: [String: BoxLayout]) throws(DiagramError) -> BoxLayout {
|
||||
let (edges, ghosts) = projectEdges(d, level)
|
||||
var (boxes, size) = placeRows(d.levels[level]!, edges)
|
||||
if !level.isEmpty {
|
||||
(boxes, size) = framed(d, level, boxes, size, ghosts, edges, done)
|
||||
var extra: [Int: CGFloat] = [:]
|
||||
while true {
|
||||
var (boxes, size) = placeRows(d.levels[level]!, edges, extra)
|
||||
if !level.isEmpty {
|
||||
(boxes, size) = framed(d, level, boxes, size, ghosts, edges, done)
|
||||
}
|
||||
let visible = Set(boxes.filter { $0.kind != .frame }.map(\.node.id))
|
||||
let presets = d.presets.compactMap { p -> BoxPreset? in
|
||||
let ids = Set(p.ids.map { d.end($0, at: level).id }).intersection(visible)
|
||||
return ids.contains { d.rep($0, at: level) == $0 } ? BoxPreset(name: p.name, ids: ids) : nil
|
||||
}
|
||||
let (layout, stuck) = try routeLevel(level, boxes, size, edges, presets)
|
||||
if stuck.isEmpty || !extra.isEmpty {
|
||||
return layout
|
||||
}
|
||||
extra = stuck
|
||||
}
|
||||
let visible = Set(boxes.filter { $0.kind != .frame }.map(\.node.id))
|
||||
let presets = d.presets.compactMap { p -> BoxPreset? in
|
||||
let ids = Set(p.ids.map { d.end($0, at: level).id }).intersection(visible)
|
||||
return ids.contains { d.rep($0, at: level) == $0 } ? BoxPreset(name: p.name, ids: ids) : nil
|
||||
}
|
||||
return try routeLevel(level, boxes, size, edges, presets)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -666,8 +673,8 @@ private func framed(_ d: BoxDiagram, _ level: String, _ inner: [PlacedBox], _ in
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func placeRows(_ rows: [BoxRow], _ edges: [BoxEdge]) -> (boxes: [PlacedBox], size: CGSize) {
|
||||
let sizer = Sizer(rows, edges)
|
||||
private func placeRows(_ rows: [BoxRow], _ edges: [BoxEdge], _ extra: [Int: CGFloat]) -> (boxes: [PlacedBox], size: CGSize) {
|
||||
let sizer = Sizer(rows, edges, extra)
|
||||
let mainWidth: CGFloat = rows.map(sizer.rowWidth).max() ?? 0
|
||||
let asideWidth: CGFloat = rows.flatMap(\.aside).map { sizer.naturalSize($0).width }.max() ?? 0
|
||||
func ids(_ n: BoxNode) -> [String] {
|
||||
@@ -684,13 +691,13 @@ private func placeRows(_ rows: [BoxRow], _ edges: [BoxEdge]) -> (boxes: [PlacedB
|
||||
}
|
||||
}
|
||||
func gap(below i: Int) -> CGFloat {
|
||||
let n = edges.filter { e in
|
||||
guard let a = rowOf[e.from], let b = rowOf[e.to] else {
|
||||
let crossing = edges.indices.filter { k in
|
||||
guard let a = rowOf[edges[k].from], let b = rowOf[edges[k].to] else {
|
||||
return false
|
||||
}
|
||||
return min(a, b) <= i && max(a, b) > i
|
||||
}.count
|
||||
return max(rowGap, CGFloat(n + 1) * laneGap)
|
||||
}
|
||||
return max(rowGap, CGFloat(crossing.count + 1) * laneGap) + crossing.compactMap { extra[$0] }.reduce(0, max)
|
||||
}
|
||||
var boxes: [PlacedBox] = []
|
||||
var y = margin
|
||||
@@ -719,7 +726,7 @@ private func placeRows(_ rows: [BoxRow], _ edges: [BoxEdge]) -> (boxes: [PlacedB
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func routeLevel(_ level: String, _ boxes: [PlacedBox], _ size: CGSize, _ edges: [BoxEdge], _ presets: [BoxPreset]) throws(DiagramError) -> BoxLayout {
|
||||
private func routeLevel(_ level: String, _ boxes: [PlacedBox], _ size: CGSize, _ edges: [BoxEdge], _ presets: [BoxPreset]) throws(DiagramError) -> (BoxLayout, [Int: CGFloat]) {
|
||||
let index = Dictionary(uniqueKeysWithValues: boxes.enumerated().map { ($0.element.node.id, $0.offset) })
|
||||
var router = Router(boxes: boxes, index: index, size: size)
|
||||
let order = edges.indices.sorted { a, b in
|
||||
@@ -739,12 +746,17 @@ private func routeLevel(_ level: String, _ boxes: [PlacedBox], _ size: CGSize, _
|
||||
var labels: [CGRect] = []
|
||||
let lines = points.map { pts in zip(pts, pts.dropFirst()).map { a, b in CGRect(x: min(a.x, b.x), y: min(a.y, b.y), width: abs(a.x - b.x), height: abs(a.y - b.y)).insetBy(dx: -1, dy: -1) } }
|
||||
var routed: [RoutedEdge] = []
|
||||
for (e, pts) in zip(edges, points) {
|
||||
var stuck: [Int: CGFloat] = [:]
|
||||
for (i, (e, pts)) in zip(edges, points).enumerated() {
|
||||
var labelRect: CGRect?
|
||||
if let label = e.label {
|
||||
let r = labelSpot(textSize(label, edgeLabelFont), pts, headers: headers, outlines: outlines, labels: labels, lines: lines.flatMap { $0 }, CGRect(x: 0, y: 0, width: size.width + margin, height: size.height + margin))
|
||||
labels.append(r)
|
||||
labelRect = r
|
||||
let crossed = lines.indices.filter { $0 != i && lines[$0].contains { $0.intersects(r) } }.count
|
||||
if crossed > 0 {
|
||||
stuck[i] = CGFloat(crossed) * laneGap
|
||||
}
|
||||
}
|
||||
routed.append(RoutedEdge(edge: e, points: pts, labelRect: labelRect))
|
||||
}
|
||||
@@ -754,7 +766,7 @@ private func routeLevel(_ level: String, _ boxes: [PlacedBox], _ size: CGSize, _
|
||||
let shifted = routed.map { r in
|
||||
RoutedEdge(edge: r.edge, points: r.points.map { CGPoint(x: $0.x + dx, y: $0.y + dy) }, labelRect: r.labelRect?.offsetBy(dx: dx, dy: dy))
|
||||
}
|
||||
return BoxLayout(level: level, boxes: boxes.map { $0.moved(dx, dy) }, index: index, edges: shifted, presets: presets, size: fit)
|
||||
return (BoxLayout(level: level, boxes: boxes.map { $0.moved(dx, dy) }, index: index, edges: shifted, presets: presets, size: fit), stuck)
|
||||
}
|
||||
|
||||
private struct Anchor {
|
||||
|
||||
Reference in New Issue
Block a user