Standalone SVG rendering for diagrams.mbt models, in the visual style of archify
///|
test {
let s = @sequence.Sequence::new(
[@sequence.Participant::new("Client"), @sequence.Participant::new("API")],
[Message(@sequence.Message::new("Client", "API", "GET /forecast"))],
)
let r = @render.Renderer::render(@svg.SvgRenderer::new(), Sequence(s))
assert_eq(r.media_type, "image/svg+xml")
assert_eq(r.diagnostics.length(), 0)
let svg = r.output.unwrap()
assert_true(svg.contains("@media (prefers-color-scheme: light)"))
assert_true(svg.contains("data-id=\"Client\""))
}///|
test {
let s = @sequence.Sequence::new([], [], title="Login flow")
let svg = @svg.render_sequence(s, mode=Fixed(Light))
assert_false(svg.contains("<style"))
assert_false(svg.contains("var("))
assert_true(svg.contains("fill=\"#f4f5f7\""))
}| Value | Geometry |
|---|---|
| participants | Boxes, labels, optional sublabels, source participants and each participant's lifeline start and end |
| messages | Chips, labels, optional sublabels, source messages and ordered path segments, including all three segments of a self-message |
| notes | A measured note box and its text; an over note spans its anchors and places text clear of activation bars |
| activations | Bars 10 px wide; each nesting depth moves 4 px right; an open activation ends at destruction or diagram end |
| frames | Frames in preorder, parent indices, header chips and ordered sections with their labels and divider segments |
| groups | Source groups, enclosing boxes and title chips; titles use separate member gaps when group extents interleave |
| title and title_box | The measured diagram title and its reserved area |
///|
test {
let s = @sequence.Sequence::new(
[@sequence.Participant::new("A"), @sequence.Participant::new("B")],
[Message(@sequence.Message::new("A", "B", "hello"))],
)
let g = @layout.layout(s)
let theme = @theme.Theme::classic()
assert_eq(@layout.check(g, theme.dark), [])
assert_eq(@layout.check(g, theme.light), [])
}///|
test "canonical scene SVG equals the ordinary static renderer" {
let sequence = @sequence.Sequence::new(
[@sequence.Participant::new("A"), @sequence.Participant::new("B")],
[Message(@sequence.Message::new("A", "B", "hello"))],
)
let renderer = @svg.SvgRenderer::new()
let scene = renderer.scene(Sequence(sequence)).scene.unwrap()
assert_eq(scene.canonical_svg, @svg.render_sequence(sequence))
assert_false(scene.canonical_svg.contains("data-motion-paths"))
assert_true(scene.display_svg.contains("data-motion-paths"))
assert_eq(scene.paths.length(), 1)
}///|
test "swimlane SVG and scene share one actual handoff path" {
let view = @swimlane.Swimlane::new(
"Request",
[
@swimlane.Node::new("Ask", lane="Customer", shape="stadium"),
@swimlane.Node::new("Review", lane="Support", shape="diamond"),
],
[@swimlane.Connection::new("handoff", "Ask", "Review", label="request")],
lanes=[@swimlane.Lane::new("Customer"), @swimlane.Lane::new("Support")],
direction=LR,
)
let renderer = @svg.SvgRenderer::new(id_prefix="request")
let result = renderer.render(Swimlane(view))
assert_eq(result.diagnostics, [])
let scene = renderer.scene(Swimlane(view)).scene.unwrap()
assert_eq(scene.canonical_svg, result.output.unwrap())
assert_eq(scene.paths.length(), 1)
assert_true(scene.canonical_svg.contains("d=\"" + scene.paths[0].path + "\""))
}pub struct Scene {
canonical_svg : String
display_svg : String
catalog : Catalog
paths : ReadOnlyArray[PathBinding]
nodes : ReadOnlyArray[NodeBinding]
}pub struct SvgRenderer {
theme : Theme
mode : Mode
id_prefix : String?
bpmn_policy : GeometryPolicy
bpmn_limits : Limits
swimlane_limits : Limits
}impl Renderer for SvgRendererfn SvgRenderer::new(theme? : Theme, mode? : Mode, id_prefix? : String, bpmn_policy? : GeometryPolicy, bpmn_limits? : Limits, swimlane_limits? : Limits) -> SvgRendererfn SvgRenderer::scene(self : SvgRenderer, diagram : Diagram, sequence_view_id? : String, limits? : Limits, output_units? : Int) -> SceneResult