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