///|
/// A note attached to an actual drawn connection rather than a control node.
pub struct ConnectionNote {
id : String
connection : String
note : String
}
///|
pub fn ConnectionNote::new(
id : String,
connection : String,
note : String,
) -> ConnectionNote {
{ id, connection, note, }
}
///|
pub fn Geometry::annotate(
self : Geometry,
notes : ArrayView[ConnectionNote],
limits~ : Limits = Limits::new(),
) -> Result {
let budget = Budget::{ limits, diagnostics: [], work: 0, failed: false, }
let shapes : Map[String, Shape] = Map([])
for shape in self.shapes {
if !budget.charge(shape.id.length() + 1) {
return layout_failure(budget.diagnostics)
}
shapes[shape.id] = shape
}
let edges : Map[String, Connection] = Map([])
for edge in self.connections {
if !budget.charge(edge.connection.id.length() + 1) {
return layout_failure(budget.diagnostics)
}
edges[edge.connection.id] = edge
}
let connections = self.connections.iter().to_array()
let authored = self.view.connections.iter().to_array()
let mut width = self.width.to_double()
let mut height = self.height.to_double()
for note in notes {
if !budget.charge(
note.id.length() + note.connection.length() + note.note.length() + 1,
) {
return layout_failure(budget.diagnostics)
}
guard shapes.get(note.note) is Some(target) else { continue }
guard edges.get(note.connection) is Some(edge) else {
budget.diagnostics.push(
@diagrams.Diagnostic::warning(
"swimlane/hidden-note-target",
"An edge-attached note has no visible target connection: " +
note.connection,
),
)
continue
}
if connections.length() >= limits.edges {
budget.error(
"swimlane/layout-limit", "Note attachments exceed the connection limit",
)
return layout_failure(budget.diagnostics)
}
let mut selected : (Point, Double)? = None
for i in 1.. {
point.x >= l.box.x - 8.0 &&
point.x <= l.box.x + l.box.width + 8.0 &&
point.y >= l.box.y - 8.0 &&
point.y <= l.box.y + l.box.height + 8.0
})
.unwrap_or(false)
if !covered && (selected == None || length > selected.unwrap().1) {
selected = Some((point, length))
}
}
}
guard selected is Some((start, _)) else {
budget.error(
"swimlane/layout-limit", "No clear point on a note's connection",
)
return layout_failure(budget.diagnostics)
}
let side = if start.x < target.box.x {
2
} else if start.x > target.box.x + target.box.width {
0
} else if start.y < target.box.y {
3
} else {
1
}
let end = port(target, side, 0.5)
let entry : Point = match side {
0 =>
{ x: target.outline_box.x + target.outline_box.width + 12.0, y: end.y, }
1 =>
{
x: end.x,
y: target.outline_box.y + target.outline_box.height + 12.0,
}
2 => { x: target.outline_box.x - 12.0, y: end.y, }
_ => { x: end.x, y: target.outline_box.y - 12.0, }
}
let obstacles = []
for shape in self.shapes {
if !budget.charge(1) {
return layout_failure(budget.diagnostics)
}
if shape.kind == Node {
obstacles.push(
inflated(
shape.outline_box,
if shape.id == target.id {
0.0
} else {
8.0
},
),
)
}
if shape.label is Some(label) {
obstacles.push(inflated(label.box, 6.0))
}
}
guard orthogonal(start, entry, obstacles, height + 64.0, None, budget)
is Some(middle) else {
if !budget.failed {
budget.error(
"swimlane/layout-limit", "No bounded route from a connection to its note",
)
}
return layout_failure(budget.diagnostics)
}
let line = compact(middle.iter().concat([end].iter()).to_array())
for point in line {
width = Double::max(width, point.x + 32.0)
height = Double::max(height, point.y + 32.0)
}
let connection = @model.Connection::new(
note.id,
note.connection,
note.note,
style=Dotted,
target_marker=None,
)
let mut anchor_id = note.id + ":edge-anchor"
while shapes.contains(anchor_id) {
if !budget.charge(1) {
return layout_failure(budget.diagnostics)
}
anchor_id += "_"
}
connections.push({
connection,
index: authored.length(),
source: anchor_id,
target: note.note,
line: ReadOnlyArray::from_array(line),
label: None,
})
authored.push(connection)
}
if budget.failed {
return layout_failure(budget.diagnostics)
}
let view = @model.Swimlane::new(
self.view.id,
self.view.nodes[:],
authored[:],
direction=self.view.direction,
lanes=self.view.lanes[:],
groups=self.view.groups[:],
title?=self.view.title,
accessibility_title?=self.view.accessibility_title,
accessibility_description?=self.view.accessibility_description,
)
{
geometry: Some({
view,
shapes: self.shapes,
connections: ReadOnlyArray::from_array(connections),
width: width.ceil().to_int(),
height: height.ceil().to_int(),
}),
diagnostics: ReadOnlyArray::from_array(budget.diagnostics),
}
}