Schematex

Block diagram

Signal-flow block diagrams with summing junctions, gain blocks, feedforward paths, and feedback loops. For control systems, signal processing, and systems engineering.

blockdiagram·§
↘ preview
100%
Unity Feedback Block diagram with 2 blocks, 1 summing junctions, 5 signals Unity Feedback r + + C(s) G(s) in
UTF-8 · LF · 12 lines · 197 chars✓ parsed·4.2 ms·2.9 KB SVG

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):

RoleFillMeaning
Plant / ProcesswhiteControlled 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 PositionOffset from CenterTypical SignalTypical 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) = outputNo 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):

SymbolMeaning
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):

Elementx-centery-center
Reference input label30100
Summing junction80100
Controller C(s)200100
Plant G(s)360100
Output branch480100
Sensor H(s)360220
Feedback return220→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 whitespace

DSL 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 -> out

Verify: 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 -> err

Verify: r, err→C are dashed lines (discrete), ZOH→G is a solid line (continuous).


6. Implementation Priority

PriorityFeatureComplexityUser Value
P0 (MVP)Block (transfer function box) + signal lines + arrowsLowCore
P0Simple unidirectional connection (->)LowCore
P0Signal labels on linesLowCore
P1Summing junction with +/− signsMediumHigh — core of control systems
P1Branch point (filled dot)LowHigh
P1Feedback path auto-routing (below forward path)MediumHigh
P1Block role colors (plant/controller/sensor)LowMedium
P2System boundary dashed boxLowMedium
P2Disturbance input (vertical injection)MediumMedium
P2Discrete signal lines (dashed)LowMedium
P2Multi-loop (nested feedback) layoutHighMedium
P3Transfer function fraction display (numerator/denominator)MediumLow
P3Signal flow graph (Mason's gain)HighLow

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.power

Parser: [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 -> merger

Layout: 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 -> sum1

Parser 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

PatternCurrentlyPhase 2 target
CRT TV (MIMO C.R.T. block)Inputs pile on left pinNamed ports, 4 sides used
3-phase inverter busThree parallel edgesOne bus signal, slash = 3
Smith predictor (forward + model feedback)OverlapSeparate corridors, no crossings
Nested boundary (controller vs plant)No groupingDashed boxes, labels