{#- Collaboration map, driven by `criteria.network` in data/criteria.yaml. Hub sits in the middle; left_nodes / right_nodes each render as a stacked column of boxes with a connector back to the hub. Row positions, the hub's vertical centring and the overall canvas height are all computed from how many nodes you list — add / remove / reorder nodes on either side freely, you never need to touch the SVG. Heading/subtext are word- wrapped and each box grows to fit the longest wrapped node so text never overflows its box. -#} {%- set net = criteria.network -%} {%- set WIDTH = 720 -%} {%- set X0 = 15 -%} {%- set X1 = WIDTH - X0 -%} {%- set BOX_W = 220 -%} {%- set HEAD_WRAP = net.heading_wrap | default(30) -%} {%- set SUB_WRAP = net.subtext_wrap | default(38) -%} {%- set HEAD_LH = 11 -%} {%- set SUB_LH = 10 -%} {%- set HEAD_TOP = 15 -%} {%- set HEAD_SUB_GAP = 13 -%} {%- set BOX_BOTTOM_PAD = 8 -%} {%- set left_nodes = net.left_nodes or [] -%} {%- set right_nodes = net.right_nodes or [] -%} {%- set all_nodes = left_nodes + right_nodes -%} {%- set ns = namespace(max_head=1, max_sub=1) -%} {%- for node in all_nodes %} {%- set hlines = (node.heading | wordwrap(HEAD_WRAP, break_long_words=False)).split('\n') %} {%- set slines = (node.subtext | wordwrap(SUB_WRAP, break_long_words=False)).split('\n') %} {%- if hlines | length > ns.max_head %}{%- set ns.max_head = hlines | length %}{%- endif %} {%- if slines | length > ns.max_sub %}{%- set ns.max_sub = slines | length %}{%- endif %} {%- endfor %} {%- set BOX_H = net.box_height | default(HEAD_TOP + (ns.max_head - 1) * HEAD_LH + HEAD_SUB_GAP + (ns.max_sub - 1) * SUB_LH + BOX_BOTTOM_PAD) -%} {%- set ROW_H = net.row_height | default(BOX_H + 15) -%} {%- set TOP_MARGIN = net.top_margin | default(26) -%} {%- set BOTTOM_MARGIN = 12 -%} {%- set HUB_W = net.hub_width | default(176) -%} {%- set HUB_H = net.hub_height | default(44) -%} {%- set rows = [left_nodes | length, right_nodes | length] | max -%} {%- set first_center = TOP_MARGIN + BOX_H / 2 -%} {%- set last_center = TOP_MARGIN + (rows - 1) * ROW_H + BOX_H / 2 -%} {%- set hub_center_y = (first_center + last_center) / 2 -%} {%- set hub_y = hub_center_y - HUB_H / 2 -%} {%- set hub_x = (WIDTH - HUB_W) / 2 -%} {%- set svg_height = TOP_MARGIN + (rows - 1) * ROW_H + BOX_H + BOTTOM_MARGIN -%} {%- set right_box_x = WIDTH - X0 - BOX_W -%} {%- set label_fill = net.label_fill | default('#1a5faa') -%} {%- set hub_fill = net.hub_fill | default('#0f3f73') -%} {%- set hub_text_fill = net.hub_text_fill | default('#ffffff') -%} {%- set hub_subtext_fill = net.hub_subtext_fill | default('#cfe0f2') -%} {%- set node_fill = net.node_fill | default('#f2f6fb') -%} {%- set node_stroke = net.node_stroke | default('#c6d4e4') -%} {%- set edge_stroke = net.edge_stroke | default('#b9cde6') -%} {%- set heading_fill = net.heading_fill | default('#1c1f23') -%} {%- set subtext_fill = net.subtext_fill | default('#555b63') -%}

{{ net.caption }}

{{ net.left_label }} {{ net.right_label }} {%- for node in left_nodes %} {%- set center_y = TOP_MARGIN + loop.index0 * ROW_H + BOX_H / 2 %} {%- endfor %} {%- for node in right_nodes %} {%- set center_y = TOP_MARGIN + loop.index0 * ROW_H + BOX_H / 2 %} {%- endfor %} {{ net.hub.title }} {{ net.hub.name }} {{ net.hub.subtitle }} {%- for node in left_nodes %} {%- set row_y = TOP_MARGIN + loop.index0 * ROW_H %} {%- set hlines = (node.heading | wordwrap(HEAD_WRAP, break_long_words=False)).split('\n') %} {%- set slines = (node.subtext | wordwrap(SUB_WRAP, break_long_words=False)).split('\n') %} {%- set content_h = HEAD_TOP + (hlines | length - 1) * HEAD_LH + HEAD_SUB_GAP + (slines | length - 1) * SUB_LH + BOX_BOTTOM_PAD %} {%- set voffset = (BOX_H - content_h) / 2 %} {{ node.title }} {%- for line in hlines %}{{ line }}{%- endfor %} {%- for line in slines %}{{ line }}{%- endfor %} {%- endfor %} {%- for node in right_nodes %} {%- set row_y = TOP_MARGIN + loop.index0 * ROW_H %} {%- set hlines = (node.heading | wordwrap(HEAD_WRAP, break_long_words=False)).split('\n') %} {%- set slines = (node.subtext | wordwrap(SUB_WRAP, break_long_words=False)).split('\n') %} {%- set content_h = HEAD_TOP + (hlines | length - 1) * HEAD_LH + HEAD_SUB_GAP + (slines | length - 1) * SUB_LH + BOX_BOTTOM_PAD %} {%- set voffset = (BOX_H - content_h) / 2 %} {{ node.title }} {%- for line in hlines %}{{ line }}{%- endfor %} {%- for line in slines %}{{ line }}{%- endfor %} {%- endfor %}