added basis for node/flow creation + node layout
This commit is contained in:
		
							
								
								
									
										
											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 | ||||||
		Reference in New Issue
	
	Block a user