Files
git.stella-ops.org/docs/workflow/tutorials/10-rendering/README.md
master e91cf98f8f Add ElkSharp rendering architecture docs, ADRs, tutorial, AGENTS rules
Five documentation deliverables for the ElkSharp rendering improvements:

1. docs/workflow/engine/16-elksharp-rendering-architecture.md (453 lines)
   Full pipeline: Sugiyama stages, edge routing strategies, hybrid
   deterministic mode, gateway geometry, 18-category scoring system,
   corridor routing, Y-gutter expansion, diagnostics.

2. docs/workflow/engine/17-elksharp-architectural-decisions.md (259 lines)
   Six ADRs: short-stub normalization, gateway vertex entries, Y-gutter
   expansion, corridor rerouting, FinalScore adjustment, alongside
   detection.

3. docs/workflow/tutorials/10-rendering/README.md (234 lines)
   Practical tutorial: setup, layout options, SVG/PNG rendering,
   diagnostics capture, violation reports, full end-to-end example.

4. src/__Libraries/StellaOps.ElkSharp/AGENTS.md — 7 new local rules
   for Y-gutter, corridor reroute, gateway vertices, FinalScore
   adjustments, short-stub normalization, alongside detection,
   target-join spread.

5. docs/workflow/ENGINE.md — replaced monolithic ElkSharp paragraph
   with structured pipeline overview, effort-level table, and links
   to the new architecture docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:37:32 +03:00

6.5 KiB

Tutorial 10: Rendering Workflow Diagrams

This tutorial shows how to use the Stella Ops workflow rendering system to produce visual diagrams from workflow definitions.


Prerequisites

  • A workflow canonical definition (from the compiler or imported JSON).
  • Reference to StellaOps.Workflow.Renderer and StellaOps.ElkSharp assemblies.

Basic Usage

1. Create the Layout Engine

var engine = new ElkSharpWorkflowRenderLayoutEngine();

2. Configure Layout Options

var request = new WorkflowRenderLayoutRequest
{
    Direction = WorkflowRenderLayoutDirection.LeftToRight,
    Effort = WorkflowRenderLayoutEffort.Best,
    NodeSpacing = 40,
    LayerSpacing = 60,
};

3. Compute the Layout

var layout = await engine.LayoutAsync(graph, request);

The graph parameter is a WorkflowRenderGraph produced by the WorkflowRenderGraphCompiler from a canonical workflow definition.

4. Render to SVG

var svgRenderer = new WorkflowRenderSvgRenderer();
var svgDoc = svgRenderer.Render(layout, "My Workflow");
await File.WriteAllTextAsync("workflow.svg", svgDoc.Svg);

5. Export to PNG

var pngExporter = new WorkflowRenderPngExporter();
await pngExporter.ExportAsync(svgDoc, "workflow.png", scale: 2f);

The scale parameter controls the pixel density (2f = 2x resolution for HiDPI).


Layout Options Reference

Direction

Value Description
LeftToRight Nodes flow left to right (default for workflows).
TopToBottom Nodes flow top to bottom. Uses the legacy iterative path.

Effort Levels

Level Speed Quality Use Case
Draft Fast (~1s) Basic Interactive editing, previews
Balanced Medium (~3-5s) Good Medium graphs, dev-time rendering
Best Slow (~12-15s) Production Final artifacts, export, CI rendering

Draft uses 8 ordering iterations, 3 placement iterations, and baseline routing only. No iterative optimization is performed.

Balanced uses 14 ordering iterations, 6 placement iterations, and light repair that fixes the worst violations without full A* search.

Best uses 24 ordering iterations, 10 placement iterations, and the hybrid deterministic optimization pipeline with full-core parallel repair candidates.

Spacing

  • NodeSpacing (default 40): Vertical gap between nodes in pixels. The engine may scale this up to 1.8x when edge density is high.
  • LayerSpacing (default 60): Horizontal gap between layers in pixels.

Reading the Layout Result

The LayoutAsync result contains positioned nodes and routed edges.

Nodes

foreach (var node in layout.Nodes)
{
    Console.WriteLine($"Node {node.Id}: ({node.X}, {node.Y}) " +
                      $"size {node.Width}x{node.Height} " +
                      $"shape={node.Shape}");
}

Node shapes include Rectangle (service tasks), Diamond (decision gateways), Hexagon (fork/join gateways), Circle (start/end events), and others.

Edges

foreach (var edge in layout.Edges)
{
    Console.WriteLine($"Edge {edge.SourceId} -> {edge.TargetId}");
    foreach (var point in edge.BendPoints)
    {
        Console.WriteLine($"  bend: ({point.X}, {point.Y})");
    }
}

Bend points define the orthogonal path from source to target. Two consecutive bend points with the same Y form a horizontal segment; two with the same X form a vertical segment.


Diagnostics

When using Best effort, the engine captures detailed diagnostics about the optimization process.

Enabling Diagnostics

Diagnostics are captured automatically in Best mode. Access them through the layout result.

Violation Report

The violation report lists each edge's violations with category, severity, and geometric details.

if (layout.Diagnostics?.ViolationReport != null)
{
    foreach (var entry in layout.Diagnostics.ViolationReport)
    {
        Console.WriteLine($"Edge {entry.EdgeId}: " +
                          $"{entry.Category} (penalty {entry.Penalty})");
    }
}

Violation Categories

The scoring system uses 18 categories. Hard violations (100K penalty) include node crossings, under-node routing, shared lanes, and boundary slot conflicts. Medium violations (50K) include backtracking and detours. Soft violations (200-650) include edge crossings, proximity, and excessive bends.

A FinalScore of 0 for hard violations indicates a clean layout with no visual defects. See the Rendering Architecture for the full violation taxonomy.

Phase Timings

if (layout.Diagnostics?.PhaseTimings != null)
{
    foreach (var phase in layout.Diagnostics.PhaseTimings)
    {
        Console.WriteLine($"{phase.Name}: {phase.Duration.TotalMilliseconds}ms");
    }
}

Phase timings cover ordering, placement, gutter expansion, base routing, iterative optimization, and post-processing.


End-to-End Example

// Compile a workflow definition to a render graph
var compiler = new WorkflowRenderGraphCompiler();
var graph = compiler.Compile(workflowDefinition);

// Configure and run layout
var engine = new ElkSharpWorkflowRenderLayoutEngine();
var request = new WorkflowRenderLayoutRequest
{
    Direction = WorkflowRenderLayoutDirection.LeftToRight,
    Effort = WorkflowRenderLayoutEffort.Best,
    NodeSpacing = 40,
    LayerSpacing = 60,
};
var layout = await engine.LayoutAsync(graph, request);

// Render to SVG
var svgRenderer = new WorkflowRenderSvgRenderer();
var svgDoc = svgRenderer.Render(layout, workflowDefinition.Name);
await File.WriteAllTextAsync($"{workflowDefinition.Name}.svg", svgDoc.Svg);

// Export to PNG at 2x resolution
var pngExporter = new WorkflowRenderPngExporter();
await pngExporter.ExportAsync(svgDoc, $"{workflowDefinition.Name}.png", scale: 2f);

// Check for violations
var hardViolations = layout.Diagnostics?.ViolationReport?
    .Where(v => v.Penalty >= 100_000)
    .ToList();
if (hardViolations?.Any() == true)
{
    Console.WriteLine($"WARNING: {hardViolations.Count} hard violations detected");
}

Further Reading