Block diagram
Signal-flow block diagrams with summing junctions, gain blocks, feedforward paths, and feedback loops. For control systems, signal processing, and systems engineering.
Control system block diagram standard: Laplace-domain transfer function representation + signal flow conventions + feedback loop layout.
Primary References:
- Ogata, K. (2010). Modern Control Engineering, 5th ed. — classic control system block diagram conventions
- Franklin, G.F., Powell, J.D., Emami-Naeini, A. (2018). Feedback Control of Dynamic Systems, 8th ed.
- Nise, N.S. (2020). Control Systems Engineering, 8th ed.
- IEEE Std 91-1984: Logic symbol conventions (arrowhead style)
- ISO 1219-1: Fluid power systems (block diagram influence)
1. Elements & Symbols
1.1 Core Element: Block (Transfer Function Box)
All system elements (controller, plant, sensor, etc.) are represented by rectangular boxes.
Standard size: 80px wide × 50px tall (default)
<!-- Standard block -->
<rect x="0" y="0" width="80" height="50"
fill="white" stroke="#333" stroke-width="2"
class="lt-block" data-id="G1"/>
<!-- Transfer function label (inside, centered) -->
<text x="40" y="22" font-size="14" font-family="serif" font-style="italic"
text-anchor="middle" dominant-baseline="central" class="lt-tf-label">G(s)</text>
<!-- Block name/description (optional, below TF) -->
<text x="40" y="38" font-size="10" text-anchor="middle" class="lt-block-name">Plant</text>
<!-- Input pin (left center) -->
<!-- x=0, y=25 -->
<!-- Output pin (right center) -->
<!-- x=80, y=25 -->System role color coding (optional theme):
| Role | Fill | Meaning |
|---|---|---|
| Plant / Process | white | Controlled object G(s) |
| Controller | #E3F2FD (light blue) | Controller C(s) |
| Sensor / Transducer | #F3E5F5 (light purple) | Sensor H(s) |
| Actuator | #E8F5E9 (light green) | Actuator |
| Filter / Compensator | #FFF8E1 (light yellow) | Filter/compensator |
1.2 Summing Junction
The most important EE-specific symbol, and the key visual distinction from a generic flowchart.
<!-- Summing junction: circle Ø24px -->
<circle cx="12" cy="12" r="12" fill="white" stroke="#333" stroke-width="2"
class="lt-summing-junction"/>
<!-- Plus (+) and minus (−) signs at input quadrants -->
<!-- Typical: + at left input, − at top/bottom input (negative feedback) -->
<text x="3" y="14" font-size="11" font-weight="bold">+</text>
<text x="16" y="14" font-size="11" font-weight="bold">−</text>
<!-- Sigma symbol alternative (some textbooks) -->
<!-- <text x="8" y="16" font-size="14" font-family="serif">Σ</text> -->Summing junction pin positions (relative to the circle center):
| Pin Position | Offset from Center | Typical Signal | Typical Polarity |
|---|---|---|---|
| Left | (-12, 0) | Reference input r(t) | + |
| Top | (0, -12) | Disturbance / feedforward | + or − |
| Bottom | (0, 12) | Feedback signal y_m(t) | − |
| Right | (12, 0) | Error signal e(t) = output | No sign (output) |
Multi-input summing junction: Same circle, all 4 input directions may be used. Input signs (+/−) are placed at the end of the corresponding input wire, 3px outside the circle.
1.3 Branch Point / Pickoff Point
A branch point is where an output signal splits, indicating the same signal flows to multiple destinations:
<!-- Small filled circle at branch point -->
<circle cx="x" cy="y" r="4" fill="#333" class="lt-branch-point"/>Rule: Branch points always lie on a wire; no additional junction element is required.
1.4 Signal Lines & Arrows
All signal lines must have arrows indicating direction (unlike generic flowcharts, EE block diagram arrows are semantic: the transfer function defines the input/output direction).
<defs>
<!-- Solid filled arrowhead (standard for control systems) -->
<marker id="lt-bd-arrow" markerWidth="10" markerHeight="8"
refX="9" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="#333"/>
</marker>
<!-- Open arrowhead (for disturbance / uncertain signals) -->
<marker id="lt-bd-arrow-open" markerWidth="10" markerHeight="8"
refX="9" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="none" stroke="#333" stroke-width="1.5"/>
</marker>
</defs>
<!-- Continuous signal line -->
<line x1="x1" y1="y" x2="x2" y2="y"
stroke="#333" stroke-width="2"
marker-end="url(#lt-bd-arrow)"
class="lt-signal-line"/>
<!-- Discrete/sampled signal (dashed) -->
<line x1="x1" y1="y" x2="x2" y2="y"
stroke="#333" stroke-width="2" stroke-dasharray="6,4"
marker-end="url(#lt-bd-arrow)"
class="lt-discrete-line"/>1.5 Signal Labels
Signal labels are placed 6px above the signal line, in serif font (transfer function style):
<text x="x_mid" y="y_line-8" font-size="12" font-family="serif" font-style="italic"
text-anchor="middle" class="lt-signal-label">e(t)</text>Standard signal naming conventions (Laplace domain):
| Symbol | Meaning |
|---|---|
R(s) | Reference input |
E(s) | Error signal = R − Y_m |
U(s) | Control input |
Y(s) | System output |
Y_m(s) | Measured output |
D(s) | Disturbance signal |
N(s) | Noise |
1.6 System Boundary Box
System boundaries are represented by dashed rectangles enclosing a subsystem:
<rect x="x" y="y" width="w" height="h"
fill="none" stroke="#333" stroke-width="1.5"
stroke-dasharray="8,5" rx="4"
class="lt-system-boundary"/>
<text x="x+10" y="y+16" font-size="12" font-weight="bold"
class="lt-boundary-label">Closed-Loop System</text>2. Layout Rules
2.1 Primary Signal Flow Direction
- Forward path: left → right (standard)
- Feedback path: right → left (routed below the forward path)
- Disturbance input: top → down (enters the forward path vertically)
2.2 Block Alignment
- All blocks on the forward path are aligned along the same centerline (same y-coordinate)
- The summing junction center is at the same height as the block centers
- Horizontal spacing: signal lines between blocks ≥ 40px
2.3 Standard Closed-Loop Layout
d(t)
↓
R(s) → [Σ] → [C(s)] → [G(s)] → Y(s)
↑←←←←←[H(s)]←←←←←←←←←←←
−Coordinate layout (typical 600px wide canvas):
| Element | x-center | y-center |
|---|---|---|
| Reference input label | 30 | 100 |
| Summing junction | 80 | 100 |
| Controller C(s) | 200 | 100 |
| Plant G(s) | 360 | 100 |
| Output branch | 480 | 100 |
| Sensor H(s) | 360 | 220 |
| Feedback return | — | 220→100 |
2.4 Feedback Path Routing
The feedback path must route around the forward path, not directly cross it:
Standard four-segment route (output → bottom → left → summing junction):
<!-- Segment 1: Vertical down from output pickup -->
<line x1="x_out" y1="y_fwd" x2="x_out" y2="y_fb"/>
<!-- Segment 2: Horizontal left (feedback path) -->
<line x1="x_out" y1="y_fb" x2="x_sum_x" y2="y_fb"/>
<!-- Segment 3: Vertical up to summing junction bottom -->
<line x1="x_sum_x" y1="y_fb" x2="x_sum_x" y2="y_sum"
marker-end="url(#lt-bd-arrow)"/>Bridge notation (feedback line crosses forward path but is not connected):
<!-- Forward path line (continuous) -->
<line x1="..." y1="y_fwd" x2="..." y2="y_fwd" stroke="#333" stroke-width="2"/>
<!-- Feedback line: white gap at crossing, then continue -->
<!-- White gap to create visual bridge -->
<rect x="x_cross-6" y="y_fb-4" width="12" height="8" fill="white"/>
<!-- Feedback line continues through gap area -->
<line x1="x_cross-8" y1="y_fb" x2="x_cross+8" y2="y_fb" stroke="#333" stroke-width="2"/>2.5 Multi-Loop Systems
Nested control loops (inner loop + outer loop):
- Inner loop: smaller block group, positioned closer to the plant
- Outer loop: encompasses the entire inner loop, summing junction further to the left
- Each feedback path is on a separate layer (y spacing ≥ 50px)
2.6 Cascade (Series) Blocks
Cascaded blocks (multiple blocks in series on the forward path):
- Equally spaced horizontally
- Single signal line passes through, no additional summing junction needed
- Equivalent transfer function = product of all individual block transfer functions
3. DSL Grammar (Block Diagram)
document = header statement*
header = "block" quoted_string? NEWLINE
statement = comment | block_def | signal_def | connect_def
| summing_def | boundary_def
comment = "#" [^\n]* NEWLINE
block_def = IDENTIFIER "=" "block" "(" tf_label ")" block_attrs? NEWLINE
tf_label = quoted_string # e.g., "G(s)", "C(s)", "H(s)"
block_attrs = "[" block_attr ("," block_attr)* "]"
block_attr = "name:" quoted_string # descriptive name
| "role:" role_type # visual color coding
| "width:" INT
| "height:" INT
role_type = "plant" | "controller" | "sensor" | "actuator" | "filter"
signal_def = IDENTIFIER "=" "signal" "(" quoted_string ")" signal_attrs? NEWLINE
signal_attrs = "[" "discrete" "]" # dashed line
summing_def = IDENTIFIER "=" "sum" "(" sum_input+ ")" NEWLINE
sum_input = ("+" | "-") IDENTIFIER # polarity + signal/block output
boundary_def = "boundary" quoted_string ":" NEWLINE INDENT
block_def*
DEDENT
connect_def = IDENTIFIER "->" IDENTIFIER label_clause? NEWLINE
| "in" "->" IDENTIFIER label_clause? NEWLINE # external input
| IDENTIFIER "->" "out" label_clause? NEWLINE # external output
label_clause = "[" quoted_string "]"
IDENTIFIER = /[a-zA-Z][a-zA-Z0-9_]*/
INT = /[0-9]+/
quoted_string = '"' /[^"]*/ '"'
NEWLINE = /\n/
INDENT = increase in whitespace
DEDENT = decrease in whitespaceDSL Example: Classic PID Closed-Loop Control
blockdiagram "PID Closed-Loop Control System"
# System components
C = block("C(s)") [name: "PID Controller", role: controller]
G = block("G(s)") [name: "Plant", role: plant]
H = block("H(s)") [name: "Sensor", role: sensor]
# Signals
r = signal("r(t)") # reference input
e = signal("e(t)") # error
u = signal("u(t)") # control input
y = signal("y(t)") # output
ym = signal("y_m(t)") # measured output
# Summing junction (negative feedback)
err = sum(+r, -ym)
# Forward path connections
in -> r
r -> err ["R(s)"]
err -> C ["E(s)"]
C -> G ["U(s)"]
G -> out ["Y(s)"]
# Feedback path
G -> H ["Y(s)"]
H -> err ["Y_m(s)"]DSL example: Cascaded Blocks with Disturbance
blockdiagram "Process with Disturbance"
C = block("C(s)") [role: controller]
G1 = block("G1(s)") [role: actuator]
G2 = block("G2(s)") [role: plant]
H = block("H(s)") [role: sensor]
d = signal("d(t)") # disturbance
err = sum(+r, -ym)
dist = sum(+G1, +d)
in -> err ["R(s)"]
err -> C
C -> G1
d -> dist # disturbance enters between G1 and G2
dist -> G2
G2 -> H
G2 -> out ["Y(s)"]
H -> err ["-"]4. SVG Structure
<svg class="lt-blockdiagram" data-diagram-type="block">
<defs>
<style>
.lt-block { fill: white; stroke: #333; stroke-width: 2; }
.lt-block-plant { fill: white; }
.lt-block-controller{ fill: #E3F2FD; }
.lt-block-sensor { fill: #F3E5F5; }
.lt-tf-label { font: italic 14px serif; fill: #333; }
.lt-block-name { font: 10px sans-serif; fill: #666; }
.lt-signal-line { stroke: #333; stroke-width: 2; fill: none; }
.lt-discrete-line { stroke: #333; stroke-width: 2;
stroke-dasharray: 6,4; fill: none; }
.lt-signal-label { font: italic 12px serif; fill: #333; }
.lt-summing-jxn { fill: white; stroke: #333; stroke-width: 2; }
.lt-sum-sign { font: bold 11px sans-serif; fill: #333; }
.lt-branch-pt { fill: #333; }
.lt-boundary { fill: none; stroke: #333; stroke-width: 1.5;
stroke-dasharray: 8,5; }
.lt-boundary-label { font: bold 12px sans-serif; fill: #333; }
</style>
<!-- Signal direction arrows -->
<marker id="lt-bd-arrow" markerWidth="10" markerHeight="8"
refX="9" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="#333"/>
</marker>
<marker id="lt-bd-arrow-open" markerWidth="10" markerHeight="8"
refX="9" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="none" stroke="#333" stroke-width="1.5"/>
</marker>
</defs>
<title>Block Diagram — [name]</title>
<desc>[description]</desc>
<!-- System boundary (if specified) -->
<g id="lt-boundaries">...</g>
<!-- Signal lines (drawn first, underneath blocks) -->
<g id="lt-signals">
<g data-from="C" data-to="G">
<line class="lt-signal-line" ... marker-end="url(#lt-bd-arrow)"/>
<text class="lt-signal-label" ...>U(s)</text>
</g>
</g>
<!-- Blocks -->
<g id="lt-blocks">
<g id="G" data-type="block" data-role="plant" transform="translate(x,y)">
<rect class="lt-block lt-block-plant" width="80" height="50"/>
<text class="lt-tf-label" ...>G(s)</text>
<text class="lt-block-name" ...>Plant</text>
</g>
</g>
<!-- Summing junctions -->
<g id="lt-summing-junctions">
<g id="err" data-type="summing" transform="translate(x,y)">
<circle class="lt-summing-jxn" cx="12" cy="12" r="12"/>
<text class="lt-sum-sign" x="3" y="14">+</text>
<text class="lt-sum-sign" x="16" y="14">−</text>
</g>
</g>
<!-- Branch points -->
<g id="lt-branch-points">
<circle class="lt-branch-pt" cx="x" cy="y" r="4"
data-signals="Y_G,Y_H"/>
</g>
</svg>5. Test Cases
Case 1: Simple Open-Loop
blockdiagram "Open Loop"
G = block("G(s)") [role: plant]
in -> G ["R(s)"]
G -> out ["Y(s)"]Verify: single block, input on the left, output on the right, signal lines have arrows.
Case 2: Unity Feedback (Negative)
blockdiagram "Unity Feedback"
C = block("C(s)") [role: controller]
G = block("G(s)") [role: plant]
err = sum(+r, -y)
in -> err ["R(s)"]
err -> C ["E(s)"]
C -> G ["U(s)"]
G -> out ["Y(s)"]
G -> err ["-"]Verify: summing junction on the left, feedback path routes from G output around to the bottom input of err, negative sign correct.
Case 3: Two-Block Cascade
blockdiagram "Cascade"
G1 = block("G1(s)")
G2 = block("G2(s)")
in -> G1 -> G2 -> outVerify: two blocks connected horizontally in series, signal line is continuous.
Case 4: PID with Sensor (Full System)
(See Section 3 DSL Example)
Verify: 5 elements (summing junction + C + G + H + branch point), feedback path below, all signal labels correct.
Case 5: Discrete-Time System
blockdiagram "Digital Control"
C = block("C(z)") [role: controller]
G = block("G(s)") [role: plant]
ZOH = block("ZOH") [role: actuator]
H = block("H(s)") [role: sensor]
err = sum(+r, -ym)
r = signal("r[k]") [discrete]
in -> err
err -> C -> ZOH
ZOH -> G -> out
G -> H -> errVerify: r, err→C are dashed lines (discrete), ZOH→G is a solid line (continuous).
6. Implementation Priority
| Priority | Feature | Complexity | User Value |
|---|---|---|---|
| P0 (MVP) | Block (transfer function box) + signal lines + arrows | Low | Core |
| P0 | Simple unidirectional connection (->) | Low | Core |
| P0 | Signal labels on lines | Low | Core |
| P1 | Summing junction with +/− signs | Medium | High — core of control systems |
| P1 | Branch point (filled dot) | Low | High |
| P1 | Feedback path auto-routing (below forward path) | Medium | High |
| P1 | Block role colors (plant/controller/sensor) | Low | Medium |
| P2 | System boundary dashed box | Low | Medium |
| P2 | Disturbance input (vertical injection) | Medium | Medium |
| P2 | Discrete signal lines (dashed) | Low | Medium |
| P2 | Multi-loop (nested feedback) layout | High | Medium |
| P3 | Transfer function fraction display (numerator/denominator) | Medium | Low |
| P3 | Signal flow graph (Mason's gain) | High | Low |
7. Phase 2 — Advanced Layout (Planned)
Phase 0/1 cover SISO control systems with forward + feedback/feedforward loops. Phase 2 addresses stress-test patterns that current 1D column layout handles poorly: nested multi-input blocks (MIMO), bus-level signals, and diagrams that need explicit 2D placement rather than auto-derived columns. These features are not yet implemented — DSL shapes below are design targets.
7.1 Named Input Ports (Multi-Input Blocks)
Real systems (CRT TV, aircraft attitude, power electronics) have blocks with
multiple distinguishable inputs — not just sum-of-signals. Today A -> B only
picks a side by feedback heuristic. We need:
crt = block("C.R.T.") [ports: video=left, horiz=top, vert=bottom, power=right]
video_out -> crt.video
line_out -> crt.horiz
ew_mod -> crt.vert
degauss -> crt.powerParser: [ports: name=side,...] where side ∈ {left, top, bottom, right}.
Layout allocates multiple anchors per edge on the named block. Edge syntax
A -> B.port addresses a specific pin.
7.2 Explicit 2D Grid Placement
When BFS columns produce awkward layouts (multi-input convergence nodes,
side-by-side independent subsystems), allow explicit row + col:
G1 = block("G1") [col: 2, row: 0]
G2 = block("G2") [col: 2, row: 1]
merger = block("merge") [col: 3, row: 0.5]
G1 -> merger
G2 -> mergerLayout: when col/row present, skip BFS for that node; mix with auto-placed
nodes by treating explicit rows as hints that push auto columns outward.
7.3 Bus Signals
A bus carries multiple parallel lines (e.g., RGB, 3-phase power, state vector). Visually: thicker stroke + slash-with-count annotation.
rgb = signal("RGB") [bus: 3]
in -> video_out ["RGB", bus: 3]Renderer: stroke-width ≥ 3px; draw a short diagonal slash at midpoint with
count label above it. Bus edges do not merge into non-bus endpoints without
an explicit demux block.
7.4 Orthogonal Routing with Obstacle Avoidance
Current routing is right-angle but naive — long feedback paths can cross forward blocks. Phase 2 routing:
- Compute node bounding boxes as obstacles.
- Route feedback/feedforward lanes in reserved corridors (one track per
fbRow, already allocated in Phase 1 layout). - For cross-row jumps, pick the corridor side (above/below) that minimizes crossings using a simple A*-like grid search at 10px resolution.
- Junction dots rendered wherever two edges share a point.
7.5 Disturbance Injection (Vertical)
Disturbances inject at a sum junction from above, visually offset:
d = disturbance("d(t)")
sum1 = sum(+u, +d)
d -> sum1Parser adds disturbance("label") primitive. Layout places disturbance label
directly above the target sum's top pin (small text, no block border).
7.6 System Boundary (Grouping)
Draw a dashed box around a named subsystem:
boundary "Plant" { G1, G2, H1 }Layout computes bbox of grouped nodes + padding; renderer emits a dashed
<rect> with the boundary label in the top-left.
7.7 Phase 2 Acceptance Tests
| Pattern | Currently | Phase 2 target |
|---|---|---|
| CRT TV (MIMO C.R.T. block) | Inputs pile on left pin | Named ports, 4 sides used |
| 3-phase inverter bus | Three parallel edges | One bus signal, slash = 3 |
| Smith predictor (forward + model feedback) | Overlap | Separate corridors, no crossings |
| Nested boundary (controller vs plant) | No grouping | Dashed boxes, labels |
Single-line diagram (SLD)
Power system one-line diagrams following IEEE 315. Substation and distribution layouts with transformers, breakers, disconnects, buses, and protective relays. For electrical engineering, utilities, and power distribution.
Entity structure diagram
Corporate entity structures and cap tables. Parent/subsidiary ownership with percentages, jurisdiction clustering, entity-type shapes — C-corp, LLC, trust, fund. For M&A, legal, tax, and financial planning.