renderGraph builds a Mermaid diagram section for the given GraphInfo. It: 1) Collects and sorts node keys for deterministic output 2) Renders nodes (including nested sub-graphs) 3) Renders control/data edges with workflow-aware styles 4) Renders branches with decision diamonds and proper edge types
(sb *strings.Builder, info *compose.GraphInfo, prefix string, indentLevel int, style bool)
| 157 | // 3) Renders control/data edges with workflow-aware styles |
| 158 | // 4) Renders branches with decision diamonds and proper edge types |
| 159 | func (m *MermaidGenerator) renderGraph(sb *strings.Builder, info *compose.GraphInfo, prefix string, indentLevel int, style bool) { |
| 160 | indent := strings.Repeat(" ", indentLevel) |
| 161 | |
| 162 | // Collect all nodes from info.Nodes, Edges, and Branches |
| 163 | allNodes := make(map[string]bool) |
| 164 | for k := range info.Nodes { |
| 165 | allNodes[k] = true |
| 166 | } |
| 167 | for start, ends := range info.Edges { |
| 168 | allNodes[start] = true |
| 169 | for _, end := range ends { |
| 170 | allNodes[end] = true |
| 171 | } |
| 172 | } |
| 173 | for start, branches := range info.Branches { |
| 174 | allNodes[start] = true |
| 175 | for _, branch := range branches { |
| 176 | endNodes := branch.GetEndNode() |
| 177 | for end := range endNodes { |
| 178 | allNodes[end] = true |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // Sort nodes for deterministic output |
| 184 | nodes := make([]string, 0, len(allNodes)) |
| 185 | for k := range allNodes { |
| 186 | nodes = append(nodes, k) |
| 187 | } |
| 188 | sort.Strings(nodes) |
| 189 | |
| 190 | // Render Nodes |
| 191 | for _, nodeKey := range nodes { |
| 192 | nodeID := m.nodeID(prefix, nodeKey) |
| 193 | |
| 194 | if nodeInfo, ok := info.Nodes[nodeKey]; ok { |
| 195 | if nodeInfo.GraphInfo != nil { |
| 196 | // Subgraph |
| 197 | subgraphLabel := nodeKey |
| 198 | switch nodeInfo.Component { |
| 199 | case compose.ComponentOfChain: |
| 200 | subgraphLabel = fmt.Sprintf("%s (Chain)", nodeKey) |
| 201 | case compose.ComponentOfWorkflow: |
| 202 | subgraphLabel = fmt.Sprintf("%s (Workflow)", nodeKey) |
| 203 | case compose.ComponentOfGraph: |
| 204 | subgraphLabel = fmt.Sprintf("%s (Graph)", nodeKey) |
| 205 | } |
| 206 | _, _ = fmt.Fprintf(sb, "%ssubgraph %s [\"%s\"]\n", indent, nodeID, subgraphLabel) |
| 207 | childStyle := style |
| 208 | switch nodeInfo.Component { |
| 209 | case compose.ComponentOfWorkflow: |
| 210 | childStyle = true |
| 211 | case compose.ComponentOfGraph, compose.ComponentOfChain: |
| 212 | // for explicit Graph/Chain sub-graphs, do not apply workflow styling |
| 213 | childStyle = false |
| 214 | } |
| 215 | m.renderGraph(sb, nodeInfo.GraphInfo, nodeID+"_", indentLevel+1, childStyle) |
| 216 | _, _ = fmt.Fprintf(sb, "%send\n", indent) |