Compare commits

...

5 Commits

5 changed files with 404 additions and 37 deletions

View File

@ -1,5 +1,18 @@
#let Y-SPACE = 10 #let Y-SPACE = 10
#let PAR-PAD = (5pt, 3pt)
#let PAR-SPACE = 10 #let PAR-SPACE = 10
#let COMMENT-PAD = 8 #let COMMENT-PAD = 8
#let LIFELINE-W = 10 #let LIFELINE-W = 10
#let SYM-GAP = 5
#let PAR-PAD = (5pt, 3pt)
#let ACTOR-WIDTH = 20
#let BOUNDARY-HEIGHT = 20
#let CONTROL-HEIGHT = 20
#let ENTITY-HEIGHT = 20
#let DATABASE-WIDTH = 24
#let COLLECTIONS-PAD = (5pt, 3pt)
#let COLLECTIONS-DX = 3
#let COLLECTIONS-DY = 3
#let QUEUE-PAD = (5pt, 3pt)
#let COL-DESTRUCTION = rgb("#A80238")

View File

@ -10,6 +10,10 @@
} }
#let diagram(elements) = { #let diagram(elements) = {
if elements == none {
return
}
let participants = () let participants = ()
let elmts = elements let elmts = elements
let i = 0 let i = 0
@ -18,9 +22,21 @@
while i < elmts.len() { while i < elmts.len() {
let elmt = elmts.at(i) let elmt = elmts.at(i)
if elmt.type == "grp" { if elmt.type == "grp" {
let grp-elmts = elmt.elmts
elmt.elmts = elmt.elmts.map(e => {
if e.type == "seq" {
if e.p1 == "?" {
e.p1 = "?" + e.p2
} else if e.p2 == "?" {
e.p2 = e.p1 + "?"
}
}
e
})
elmts.at(i) = elmt
elmts = ( elmts = (
elmts.slice(0, i + 1) + elmts.slice(0, i + 1) +
elmt.elmts + grp-elmts +
(( ((
type: "grp-end" type: "grp-end"
),) + ),) +
@ -31,6 +47,7 @@
} }
// List participants // List participants
let linked = ()
for elmt in elmts { for elmt in elmts {
if elmt.type == "par" { if elmt.type == "par" {
participants.push(elmt) participants.push(elmt)
@ -41,25 +58,54 @@
if not participant._exists(participants, elmt.p2) { if not participant._exists(participants, elmt.p2) {
let par = _par(elmt.p2, from-start: not elmt.create-dst).first() let par = _par(elmt.p2, from-start: not elmt.create-dst).first()
participants.push(par) participants.push(par)
} else if elmt.create-dst {
let i = participants.position(p => p.name == elmt.p2)
participants.at(i).from-start = false
}
if elmt.p1 == "?" {
linked.push("?" + elmt.p2)
} else {
linked.push(elmt.p1)
}
if elmt.p2 == "?" {
linked.push(elmt.p1 + "?")
} else {
linked.push(elmt.p2)
} }
} }
} }
linked = linked.dedup()
let pars = participants let pars = participants
participants = () participants = ()
if "[" in linked {
participants.push(_par("[", invisible: true).first()) participants.push(_par("[", invisible: true).first())
}
for (i, p) in pars.enumerate() { for (i, p) in pars.enumerate() {
let before = _par("?" + p.name, invisible: true).first() let before = _par("?" + p.name, invisible: true).first()
let after = _par(p.name + "?", invisible: true).first() let after = _par(p.name + "?", invisible: true).first()
if i == 0 {
if before.name in linked {
if participants.len() == 0 or not participants.last().name.ends-with("?") {
participants.push(before) participants.push(before)
} else { } else {
participants.insert(-1, before) participants.insert(-1, before)
} }
}
participants.push(p) participants.push(p)
if after.name in linked {
participants.push(after) participants.push(after)
} }
}
if "]" in linked {
participants.push(_par("]", invisible: true).first()) participants.push(_par("]", invisible: true).first())
}
// Add index to participant // Add index to participant
for (i, p) in participants.enumerate() { for (i, p) in participants.enumerate() {

View File

@ -2,14 +2,36 @@
#import "consts.typ": * #import "consts.typ": *
#let PAR-SPECIALS = "?[]" #let PAR-SPECIALS = "?[]"
#let SHAPES = (
"participant",
"actor",
"boundary",
"control",
"entity",
"database",
"collections",
"queue",
"custom"
)
#let _par(name, display-name: auto, from-start: true, invisible: false) = { #let _par(
name,
display-name: auto,
from-start: true,
invisible: false,
shape: "participant",
color: rgb("#E2E2F0"),
custom-image: none
) = {
return (( return ((
type: "par", type: "par",
name: name, name: name,
display-name: if display-name == auto {name} else {display-name}, display-name: if display-name == auto {name} else {display-name},
from-start: from-start, from-start: from-start,
invisible: invisible invisible: invisible,
shape: shape,
color: color,
custom-image: custom-image
),) ),)
} }
@ -26,13 +48,306 @@
return false return false
} }
#let render(x-pos, p, y: 0) = { #let get-size(par) = {
draw.content( if par.invisible {
(x-pos.at(p.i), y), return (width: 0pt, height: 0pt)
p.display-name, }
name: p.name, let m = measure(box(par.display-name))
frame: "rect", let w = m.width
padding: PAR-PAD, let h = m.height
anchor: "south" let (shape-w, shape-h) = (
participant: (w + PAR-PAD.last() * 2, h + PAR-PAD.first() * 2),
actor: (ACTOR-WIDTH * 1pt, ACTOR-WIDTH * 2pt + SYM-GAP * 1pt + h),
boundary: (BOUNDARY-HEIGHT * 2pt, BOUNDARY-HEIGHT * 1pt + SYM-GAP * 1pt + h),
control: (CONTROL-HEIGHT * 1pt, CONTROL-HEIGHT * 1pt + SYM-GAP * 1pt + h),
entity: (ENTITY-HEIGHT * 1pt, ENTITY-HEIGHT * 1pt + 2pt + SYM-GAP * 1pt + h),
database: (DATABASE-WIDTH * 1pt, DATABASE-WIDTH * 4pt / 3 + SYM-GAP * 1pt + h),
collections: (
w + COLLECTIONS-PAD.last() * 2 + calc.abs(COLLECTIONS-DX) * 1pt,
h + COLLECTIONS-PAD.first() * 2 + calc.abs(COLLECTIONS-DY) * 1pt,
),
queue: (
w + QUEUE-PAD.last() * 2 + 3 * (h + QUEUE-PAD.first() * 2) / 4,
h + QUEUE-PAD.first() * 2
),
custom: (
measure(par.custom-image).width,
measure(par.custom-image).height + SYM-GAP * 1pt + h
)
).at(par.shape)
return (
width: calc.max(w, shape-w),
height: calc.max(h, shape-h)
) )
} }
#let _render-participant(x, y, p, m, bottom) = {
let w = m.width / 1pt
let h = m.height / 1pt
let x0 = x - w / 2 - PAR-PAD.last() / 1pt
let x1 = x + w / 2 + PAR-PAD.last() / 1pt
let y0 = y + h + PAR-PAD.first() / 1pt * 2
if bottom {
y0 = y
}
let y1 = y0 - h - PAR-PAD.first() / 1pt * 2
draw.rect(
(x0, y0),
(x1, y1),
radius: 2pt,
fill: p.color,
stroke: black + .5pt
)
draw.content(
((x0 + x1) / 2, (y0 + y1) / 2),
p.display-name,
anchor: "center"
)
}
#let _render-actor(x, y, p, m, bottom) = {
let w2 = ACTOR-WIDTH / 2
let head-r = ACTOR-WIDTH / 4
let height = ACTOR-WIDTH * 2
let arms-y = height * 0.375
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + height + SYM-GAP}
draw.circle(
(x, y0 - head-r),
radius: head-r,
fill: p.color,
stroke: black + .5pt
)
draw.line((x, y0 - head-r * 2), (x, y0 - height + w2), stroke: black + .5pt)
draw.line((x - w2, y0 - arms-y), (x + w2, y0 - arms-y), stroke: black + .5pt)
draw.line((x - w2, y0 - height), (x, y0 - height + w2), (x + w2, y0 - height), stroke: black + .5pt)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let _render-boundary(x, y, p, m, bottom) = {
let circle-r = BOUNDARY-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + BOUNDARY-HEIGHT + SYM-GAP}
let x0 = x - BOUNDARY-HEIGHT
let y1 = y0 - circle-r
let y2 = y0 - BOUNDARY-HEIGHT
draw.circle(
(x + circle-r, y1),
radius: circle-r,
fill: p.color,
stroke: black + .5pt
)
draw.line(
(x0, y0), (x0, y2),
stroke: black + .5pt
)
draw.line(
(x0, y1), (x, y1),
stroke: black + .5pt
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let _render-control(x, y, p, m, bottom) = {
let r = CONTROL-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + CONTROL-HEIGHT + SYM-GAP}
draw.circle(
(x, y0 - r),
radius: r,
fill: p.color,
stroke: black + .5pt
)
draw.mark((x, y0), (x - r / 2, y0), symbol: "stealth", fill: black)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let _render-entity(x, y, p, m, bottom) = {
let r = ENTITY-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + ENTITY-HEIGHT + SYM-GAP}
let y1 = y0 - ENTITY-HEIGHT - 1.5
draw.circle(
(x, y0 - r),
radius: r,
fill: p.color,
stroke: black + .5pt
)
draw.line(
(x - r, y1),
(x + r, y1),
stroke: black + .5pt
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let _render-database(x, y, p, m, bottom) = {
let height = DATABASE-WIDTH * 4 / 3
let rx = DATABASE-WIDTH / 2
let ry = rx / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + height + SYM-GAP}
let y1 = y0 - height
draw.merge-path(
close: true,
fill: p.color,
stroke: black + .5pt,
{
draw.bezier((x - rx, y0 - ry), (x, y0), (x - rx, y0 - ry/2), (x - rx/2, y0))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0), (x + rx, y0 - ry/2))
draw.line((), (x + rx, y1 + ry))
draw.bezier((), (x, y1), (x + rx, y1 + ry/2), (x + rx/2, y1))
draw.bezier((), (x - rx, y1 + ry), (x - rx/2, y1), (x - rx, y1 + ry/2))
}
)
draw.merge-path(
stroke: black + .5pt,
{
draw.bezier((x - rx, y0 - ry), (x, y0 - ry*2), (x - rx, y0 - 3*ry/2), (x - rx/2, y0 - ry*2))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0 - ry*2), (x + rx, y0 - 3*ry/2))
}
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let _render-collections(x, y, p, m, bottom) = {
let w = m.width / 1pt
let h = m.height / 1pt
let dx = COLLECTIONS-DX
let dy = COLLECTIONS-DY
let total-w = w + PAR-PAD.last() * 2 / 1pt + calc.abs(dx)
let total-h = h + PAR-PAD.first() * 2 / 1pt + calc.abs(dy)
let x0 = x - total-w / 2
let x1 = x0 + calc.abs(dx)
let x3 = x0 + total-w
let x2 = x3 - calc.abs(dx)
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - calc.abs(dy)
let y3 = y0 - total-h
let y2 = y3 + calc.abs(dy)
let r1 = (x1, y0, x3, y2)
let r2 = (x0, y1, x2, y3)
if dx < 0 {
r1.at(0) = x0
r1.at(2) = x2
r2.at(0) = x1
r2.at(2) = x3
}
if dy < 0 {
r1.at(1) = y1
r1.at(3) = y3
r2.at(1) = y0
r2.at(3) = y2
}
draw.rect(
(r1.at(0), r1.at(1)),
(r1.at(2), r1.at(3)),
fill: p.color,
stroke: black + .5pt
)
draw.rect(
(r2.at(0), r2.at(1)),
(r2.at(2), r2.at(3)),
fill: p.color,
stroke: black + .5pt
)
draw.content(
((r2.at(0) + r2.at(2)) / 2, (r2.at(1) + r2.at(3)) / 2),
p.display-name,
anchor: "center"
)
}
#let _render-queue(x, y, p, m, bottom) = {
let w = (m.width + QUEUE-PAD.last() * 2) / 1pt
let h = (m.height + QUEUE-PAD.first() * 2) / 1pt
let total-h = h
let ry = total-h / 2
let rx = ry / 2
let total-w = w + 3 + 3 * rx
let x0 = x - total-w / 2
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - total-h
let x-left = x0 + rx
let x-right = x-left + w + rx
draw.merge-path(
close: true,
fill: p.color,
stroke: black + .5pt,
{
draw.bezier((x-right, y0), (x-right + rx, y0 - ry), (x-right + rx/2, y0), (x-right + rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right + rx, y1 + ry/2), (x-right + rx/2, y1))
draw.line((), (x-left, y1))
draw.bezier((), (x-left - rx, y0 - ry), (x-left - rx/2, y1), (x-left - rx, y1 + ry/2))
draw.bezier((), (x-left, y0), (x-left - rx, y0 - ry/2), (x-left - rx/2, y0))
}
)
draw.merge-path(
stroke: black + .5pt,
{
draw.bezier((x-right, y0), (x-right - rx, y0 - ry), (x-right - rx/2, y0), (x-right - rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right - rx, y1 + ry/2), (x-right - rx/2, y1))
}
)
draw.content(
((x-left + x-right - rx) / 2, y0 - ry),
p.display-name,
anchor: "center"
)
}
#let _render-custom(x, y, p, m, bottom) = {
let image-m = measure(p.custom-image)
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + image-m.height / 1pt + SYM-GAP}
draw.content((x - image-m.width / 2pt, y0), p.custom-image, anchor: "north-west")
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"south"}
)
}
#let render(x-pos, p, y: 0, bottom: false) = {
let m = measure(box(p.display-name))
let func = (
participant: _render-participant,
actor: _render-actor,
boundary: _render-boundary,
control: _render-control,
entity: _render-entity,
database: _render-database,
collections: _render-collections,
queue: _render-queue,
custom: _render-custom,
).at(p.shape)
func(x-pos.at(p.i), y, p, m, bottom)
}

View File

@ -53,15 +53,15 @@
} }
// Compute column widths // Compute column widths
// Compute minimum widths for participant names // Compute minimum widths for participant names and shapes
let widths = () let widths = ()
for i in range(participants.len() - 1) { for i in range(participants.len() - 1) {
let p1 = participants.at(i) let p1 = participants.at(i)
let p2 = participants.at(i + 1) let p2 = participants.at(i + 1)
let w1 = if p1.invisible {0pt} else {measure(box(p1.display-name)).width} let m1 = participant.get-size(p1)
let w2 = if p2.invisible {0pt} else {measure(box(p2.display-name)).width} let m2 = participant.get-size(p2)
w1 += PAR-PAD.last() * 2 let w1 = m1.width
w2 += PAR-PAD.last() * 2 let w2 = m2.width
widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE) widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE)
} }
@ -83,7 +83,7 @@
let m = measure(cell.cell) let m = measure(cell.cell)
widths.at(cell.i2 - 1) = calc.max( widths.at(cell.i2 - 1) = calc.max(
widths.at(cell.i2 - 1), widths.at(cell.i2 - 1),
m.width / 1pt - widths.slice(cell.i1, cell.i2 - 1).sum() m.width / 1pt + COMMENT-PAD - widths.slice(cell.i1, cell.i2 - 1).sum()
) )
} }
@ -262,19 +262,12 @@
// Draw lifeline destructions // Draw lifeline destructions
for dest in destructions { for dest in destructions {
let (cx, cy) = dest let (cx, cy) = dest
draw.line((cx - 8, cy - 8), (cx + 8, cy + 8), stroke: red + 2pt) draw.line((cx - 8, cy - 8), (cx + 8, cy + 8), stroke: COL-DESTRUCTION + 2pt)
draw.line((cx - 8, cy + 8), (cx + 8, cy - 8), stroke: red + 2pt) draw.line((cx - 8, cy + 8), (cx + 8, cy - 8), stroke: COL-DESTRUCTION + 2pt)
} }
// Draw participants (end) // Draw participants (end)
draw.content( draw-par(p, y: y, bottom: true)
(x, y),
p.display-name,
name: p.name,
frame: "rect",
padding: PAR-PAD,
anchor: "north"
)
} }
}) })

View File

@ -117,7 +117,7 @@
} }
let style = ( let style = (
mark: (end: "straight"), mark: (end: ">", fill: elmt.color),
stroke: ( stroke: (
dash: if elmt.dashed {"dashed"} else {"solid"}, dash: if elmt.dashed {"dashed"} else {"solid"},
paint: elmt.color paint: elmt.color