added basis for node/flow creation + node layout

This commit is contained in:
Louis Heredero 2024-10-20 18:12:42 +02:00
parent 15f0910584
commit 5231f4b324
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
4 changed files with 189 additions and 0 deletions

BIN
gallery/test.pdf Normal file

Binary file not shown.

7
gallery/test.typ Normal file
View File

@ -0,0 +1,7 @@
#import "../src/lib.typ": *
#diagram({
_flow("Wages", 1500, "Budget")
_flow("Other", 250, "Budget")
_flow("Budget", 450, "Taxes")
}, 10cm)

180
src/diagram.typ Normal file
View File

@ -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
}
}
})
}

2
src/lib.typ Normal file
View File

@ -0,0 +1,2 @@
#import "diagram.typ": diagram
#import "diagram.typ": _flow, _node