added basis for node/flow creation + node layout
This commit is contained in:
parent
15f0910584
commit
5231f4b324
BIN
gallery/test.pdf
Normal file
BIN
gallery/test.pdf
Normal file
Binary file not shown.
7
gallery/test.typ
Normal file
7
gallery/test.typ
Normal 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
180
src/diagram.typ
Normal 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
2
src/lib.typ
Normal file
@ -0,0 +1,2 @@
|
||||
#import "diagram.typ": diagram
|
||||
#import "diagram.typ": _flow, _node
|
Loading…
Reference in New Issue
Block a user