diff --git a/gallery/test.pdf b/gallery/test.pdf new file mode 100644 index 0000000..fce3a6e Binary files /dev/null and b/gallery/test.pdf differ diff --git a/gallery/test.typ b/gallery/test.typ new file mode 100644 index 0000000..7f54df4 --- /dev/null +++ b/gallery/test.typ @@ -0,0 +1,7 @@ +#import "../src/lib.typ": * + +#diagram({ + _flow("Wages", 1500, "Budget") + _flow("Other", 250, "Budget") + _flow("Budget", 450, "Taxes") +}, 10cm) \ No newline at end of file diff --git a/src/diagram.typ b/src/diagram.typ new file mode 100644 index 0000000..66480f6 --- /dev/null +++ b/src/diagram.typ @@ -0,0 +1,180 @@ +#import "@preview/cetz:0.3.0": canvas, draw + +#let _flow(from, amount, to) = { + return (( + type: "flow", + from: from, + to: to, + amount: amount + ),) +} + +#let _node(name, display-name: auto, color: auto) = { + return (( + type: "node", + name: name, + display-name: if display-name == auto { + name + } else { + display-name + }, + color: color, + is-src: false, + is-dst: false, + sources: (), + targets: (), + max-depth: 0, + total: ( + "in": 0, + "out": 0, + "max": 0 + ) + ),) +} + +#let process-depth(nodes, flows, node, visited: ()) = { + let next-depth = node.max-depth + 1 + let total-depth = node.max-depth + let depth + for flow in node.targets { + if flow.to in visited { + panic("Loop") + } + nodes.at(flow.to).max-depth = calc.max( + nodes.at(flow.to).max-depth, + next-depth + ) + + (depth, nodes) = process-depth(nodes, flows, nodes.at(flow.to), visited: visited + (flow.to,)) + total-depth = calc.max(total-depth, depth) + } + return (total-depth, nodes) +} + +#let diagram( + elmts, + height, + min-spacing: 1cm +) = { + let columns = () + let nodes = (:) + let flows = () + + for elmt in elmts { + if elmt.type == "node" { + nodes.insert(elmt.name, elmt) + } else if elmt.type == "flow" { + flows.push(elmt) + } + } + + for flow in flows { + if not flow.from in nodes { + nodes.insert(flow.from, _node(flow.from).first()) + } + if not flow.to in nodes { + nodes.insert(flow.to, _node(flow.to).first()) + } + nodes.at(flow.from).is-src = true + nodes.at(flow.from).targets.push(flow) + nodes.at(flow.to).is-dst = true + nodes.at(flow.to).sources.push(flow) + } + + for (nid, node) in nodes.pairs() { + let tot-in = node.targets.map(t => t.amount).sum(default: 0) + let tot-out = node.sources.map(t => t.amount).sum(default: 0) + nodes.at(nid).total = ( + "in": tot-in, + "out": tot-out, + "max": calc.max(tot-in, tot-out), + ) + } + + let total-depth = 0 + let d + for node in nodes.values().filter(n => n.is-src and not n.is-dst) { + (d, nodes) = process-depth(nodes, flows, node) + total-depth = calc.max(total-depth, d) + } + + columns = ((),) * (total-depth + 1) + let column-totals = (0,) * columns.len() + for (key, node) in nodes.pairs() { + columns.at(node.max-depth).push(key) + column-totals.at(node.max-depth) += node.total.max + } + + //total-depth + [ + *nodes*: #nodes + + *total-depth*: #total-depth + + *columns*: #columns + + *column-totals*: #column-totals + ] + //flows + + let h-factor = calc.min( + ..columns.zip(column-totals).map(((col, total)) => { + let available-h = height - (col.len() - 1) * min-spacing + return available-h / total + }) + ) + + let colors = (red, orange, yellow, green, blue, purple).map(c => c.lighten(50%)) + + canvas({ + let c = 0 + for (i, column) in columns.enumerate() { + let x = i * 3 + let total = column.map(n => nodes.at(n).total.max).sum() + let total-h = total * h-factor + let remaining-h = height - total-h + let spacing = remaining-h / (column.len() + 1) + + let y = -spacing + for (j, nid) in column.enumerate() { + let node = nodes.at(nid) + + let h = h-factor * node.total.max + draw.rect( + (x, y), + (x + 1, y - h), + name: nid, + fill: colors.at(c) + ) + c += 1 + + if node.is-src and not node.is-dst { + draw.content( + nid + ".west", + anchor: "east", + node.display-name, + padding: 5pt + ) + } else if node.is-src and node.is-dst { + draw.content( + nid + ".center", + anchor: "center", + node.display-name, + padding: 5pt, + frame: "rect", + fill: white.transparentize(20%) + ) + } else if not node.is-src and node.is-dst { + draw.content( + nid + ".east", + anchor: "west", + node.display-name, + padding: 5pt + ) + } + + y -= spacing + h + } + } + }) +} \ No newline at end of file diff --git a/src/lib.typ b/src/lib.typ new file mode 100644 index 0000000..dd5bad9 --- /dev/null +++ b/src/lib.typ @@ -0,0 +1,2 @@ +#import "diagram.typ": diagram +#import "diagram.typ": _flow, _node \ No newline at end of file