Stadium Design Tool¶
The Stadium Design Tool is a specialized admin interface for creating and configuring stadium seat maps. This is a complex graphical editor that enables stadium operators to define the complete seating layout including sectors, rows, individual seats, aisles, and special areas.
Overview¶
Stadium seat map design involves multiple layers of complexity:
- Stadium Outline - The overall shape and boundaries
- Sector Definition - Major sections of the stadium (stands, tribunes)
- Block/Sub-sector Layout - Divisions within sectors
- Row Configuration - Individual rows with varying seat counts
- Seat Placement - Individual seat positions with coordinates
- Special Areas - Aisles, stairs, technical positions, accessibility
- Pricing Zones - Mapping seats to price categories
- Visual Representation - SVG paths and coordinates for rendering
┌─────────────────────────────────────────────────────────────────────────────┐
│ Stadium Design Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stadium Container │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Sector A │ │ Sector B │ │ Sector C │ │ │
│ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │
│ │ │ │ Block 1 │ │ │ │ Block 1 │ │ │ │ Block 1 │ │ │ │
│ │ │ │ ┌───────┐ │ │ │ │ ┌───────┐ │ │ │ │ ┌───────┐ │ │ │ │
│ │ │ │ │Row 1 │ │ │ │ │ │Row 1 │ │ │ │ │ │Row 1 │ │ │ │ │
│ │ │ │ │1 2 3 4│ │ │ │ │ │1 2 3 4│ │ │ │ │ │1 2 3 4│ │ │ │ │
│ │ │ │ └───────┘ │ │ │ │ └───────┘ │ │ │ │ └───────┘ │ │ │ │
│ │ │ │ ┌───────┐ │ │ │ │ ┌───────┐ │ │ │ │ ┌───────┐ │ │ │ │
│ │ │ │ │Row 2 │ │ │ │ │ │Row 2 │ │ │ │ │ │Row 2 │ │ │ │ │
│ │ │ │ │1 2 3 4│ │ │ │ │ │1 2 3 4│ │ │ │ │ │1 2 3 4│ │ │ │ │
│ │ │ │ └───────┘ │ │ │ │ └───────┘ │ │ │ │ └───────┘ │ │ │ │
│ │ │ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Design Tool Interface¶
Main Layout¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ Stadium Design Tool - Maksimir Stadium [Save] [Preview] │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌────────────────────────────────────────────┐ ┌─────────────┐ │
│ │ Toolbox │ │ │ │ Properties │ │
│ │ ──────── │ │ │ │ ────────── │ │
│ │ │ │ │ │ │ │
│ │ [Select] │ │ │ │ Selection: │ │
│ │ [Pan] │ │ Canvas / Design Area │ │ Sector A │ │
│ │ [Zoom] │ │ │ │ │ │
│ │ ──────── │ │ (SVG Editor) │ │ Name: _____ │ │
│ │ [Sector] │ │ │ │ Code: _____ │ │
│ │ [Block] │ │ │ │ Price Zone: │ │
│ │ [Row] │ │ │ │ │ │
│ │ [Seat] │ │ │ │ │ │
│ │ ──────── │ │ │ │ Rows: [+] │ │
│ │ [Aisle] │ │ │ │ - Row 1 (30)│ │
│ │ [Stairs] │ │ │ │ - Row 2 (32)│ │
│ │ [Gate] │ │ │ │ - Row 3 (32)│ │
│ │ ──────── │ │ │ │ │ │
│ │ [Import] │ │ │ │ [Configure] │ │
│ │ [Export] │ │ │ │ [Delete] │ │
│ └──────────┘ └────────────────────────────────────────────┘ └─────────────┘ │
│ ┌───────────────────────────────────────────────────────────────────────────┤
│ │ Layers: [Stadium] [Sectors] [Rows] [Seats] [Annotations] Zoom: 100% │ │
│ └───────────────────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────────────────┘
Tool Modes¶
| Tool | Function | Keyboard |
|---|---|---|
| Select | Select and move elements (button shows text label "Select") | V |
| Draw | Draw/place seats on canvas (button shows text label "Draw") | D |
| Erase | Remove seats from canvas (button shows text label "Erase") | E |
| Fill | Fill rectangular area with seats (button shows text label "Fill") | F |
| Pan | Pan the canvas view | Space+Drag |
| Zoom | Zoom in/out | Scroll / Z |
| Sector | Draw sector polygon | S |
| Block | Draw block within sector | B |
| Row | Define row within block | R |
| Seat | Place individual seats | T |
| Aisle | Mark aisle/walkway | A |
| Stairs | Mark stairway areas | W |
| Gate | Mark entry/exit gates | G |
Tool Button Labels
The primary editing tools (Select, Draw, Erase, Fill) display text labels on their buttons for clarity. This helps administrators quickly identify tools without relying solely on icons.
Capacity Enforcement¶
When editing a seat map within a sector, the editor enforces the sector's configured capacity limit:
- The current seat count and sector capacity are displayed in the editor toolbar
- When the seat count reaches the sector capacity, the Draw and Fill tools are disabled
- Attempting to add seats beyond the limit shows a warning notification
- Administrators must increase the sector capacity or remove existing seats before adding more
Design Workflow¶
Phase 1: Stadium Outline¶
Purpose: Define the overall stadium boundary and orientation.
Steps:
- Create New Stadium or Import Base Template
- New: Start with empty canvas
-
Import: Load from SVG, DXF (CAD), or existing template
-
Define Stadium Boundary
- Draw outer polygon of stadium
- Set stadium orientation (north arrow)
- Define field/pitch area (rectangular)
-
Set coordinate system origin
-
Set Global Properties
- Stadium name and code
- City and country
- Stadium type (Football, Multi-purpose)
- Total capacity (read-only, auto-calculated from sector seat counts)
┌─────────────────────────────────────────────────────────────────┐
│ Stadium Properties │
├─────────────────────────────────────────────────────────────────┤
│ Name: [Stadion Maksimir ] │
│ Code: [MAKSIMIR ] │
│ City: [Zagreb ] Country: [Croatia ▼] │
│ Type: [● Football ○ Multi-purpose] │
│ Orientation: [North ▼] │
│ │
│ Dimensions: │
│ Length: [200] m Width: [180] m │
│ Field: [105 x 68] m │
│ │
│ Coordinate System: │
│ Origin: [Center of Field ▼] │
│ Units: [Meters ▼] │
│ Scale: [1:100] │
└─────────────────────────────────────────────────────────────────┘
Phase 2: Sector Definition¶
Purpose: Divide the stadium into major sections (stands/tribunes).
Sector Types:
| Type | Description | Example |
|---|---|---|
| Stand | Main seating tribune | North Stand, South Stand |
| Corner | Corner section | NE Corner, SW Corner |
| Premium | VIP/Premium area | Executive Box, Skybox |
| General | General admission | Standing area |
| Away | Visiting team supporters | Away Section |
| Technical | Non-public area | Press, Camera, Technical |
Drawing Sectors:
- Select Sector Tool (S)
- Click to place polygon vertices
- Double-click or press Enter to complete
- Assign sector properties in Properties panel
Sector Properties:
┌─────────────────────────────────────────────────────────────────┐
│ Sector Properties │
├─────────────────────────────────────────────────────────────────┤
│ Basic Information │
│ ───────────────── │
│ Name: [North Stand - Section A ] │
│ Code: [ISTOK-A ] (unique identifier) │
│ Type: [Stand ▼] │
│ Access Level: [Public ▼] │
│ │
│ Geometry │
│ ───────────────── │
│ Shape: Polygon (5 vertices) │
│ Area: 1,250 m² │
│ Orientation: Facing South (toward field) │
│ │
│ Calculated Capacity (read-only) │
│ ───────────────── │
│ Total Seats: 0 (auto-calculated from seat map) │
│ ℹ Capacity updates automatically as seats are added/removed. │
│ │
│ Pricing │
│ ───────────────── │
│ Default Zone: [Category A (Premium) ▼] │
│ │
│ Entry Points │
│ ───────────────── │
│ Gates: [Gate 1, Gate 2] [Configure...] │
│ │
│ [Delete Sector] [Apply Changes] │
└─────────────────────────────────────────────────────────────────┘
Phase 3: Block/Sub-sector Configuration¶
Purpose: Divide sectors into manageable blocks (often separated by aisles).
Large sectors are typically divided into blocks for: - Easier navigation - Separate entry points - Different pricing within same stand - Structural divisions (stairs, columns)
Block Definition:
┌─────────────────────────────────────────────────────────────────┐
│ Block Configuration - North Stand Section A │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Block 1 │ │ Block 2 │ │ Block 3 │ │
│ │ │ │ │ │ │ │
│ │ Rows │ ║ ║ │ Rows │ ║ ║ │ Rows │ │
│ │ 1-15 │Aisle│ 1-15 │Aisle│ 1-15 │ │
│ │ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Blocks in this sector: │
│ ┌────┬──────────────┬──────┬───────────┬─────────┐ │
│ │ # │ Name │ Rows │ Seats/Row │ Total │ │
│ ├────┼──────────────┼──────┼───────────┼─────────┤ │
│ │ 1 │ Block A1 │ 15 │ 28-32 │ 450 │ │
│ │ 2 │ Block A2 │ 15 │ 30-34 │ 480 │ │
│ │ 3 │ Block A3 │ 15 │ 28-32 │ 450 │ │
│ └────┴──────────────┴──────┴───────────┴─────────┘ │
│ │
│ Total Sector Capacity: 1,380 seats │
│ │
│ [Add Block] [Auto-Divide...] [Import Layout...] │
└─────────────────────────────────────────────────────────────────┘
Phase 4: Row Configuration¶
Purpose: Define rows within each block with seat counts and numbering.
Row Properties:
| Property | Description | Example |
|---|---|---|
| Identifier | Row name/number | "1", "A", "AA" |
| Seat Count | Number of seats in row | 32 |
| Start Number | First seat number | 1 |
| Direction | Numbering direction | LTR, RTL |
| Curve Radius | For curved rows | 150m |
| Row Spacing | Distance to next row | 0.8m |
| Seat Width | Standard seat width | 0.45m |
Row Configuration Interface:
┌─────────────────────────────────────────────────────────────────┐
│ Row Configuration - Block A1 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Generation Method: │
│ ○ Manual (define each row individually) │
│ ● Automatic (define pattern, system generates) │
│ ○ Import (from CSV/spreadsheet) │
│ │
│ ═══════════════════════════════════════════════════════════ │
│ │
│ Automatic Generation Settings: │
│ ───────────────────────────── │
│ Number of Rows: [15 ] │
│ Row Naming: [● Numeric (1,2,3) ○ Alpha (A,B,C)] │
│ Start From: [1 ] │
│ │
│ Seat Count Pattern: │
│ [● Fixed count ] [32 ] seats per row │
│ [○ Variable ] First row: [28] Last row: [36] │
│ [○ Custom pattern ] [28,30,32,32,34,34,36,36,36,36...] │
│ │
│ Numbering Direction: │
│ [● Left-to-Right (1,2,3...)] │
│ [○ Right-to-Left (...3,2,1)] │
│ [○ Center-Out (odd left, even right)] │
│ │
│ Geometry: │
│ Row Spacing: [0.85 ] m │
│ Seat Width: [0.45 ] m │
│ Row Curve: [● Straight ○ Curved, radius: [___] m] │
│ │
│ Preview: │
│ ───────────────────────────── │
│ Row 1: [1][2][3]...[30][31][32] (32 seats) │
│ Row 2: [1][2][3]...[30][31][32] (32 seats) │
│ Row 3: [1][2][3]...[30][31][32] (32 seats) │
│ ... │
│ Row 15: [1][2][3]...[30][31][32] (32 seats) │
│ │
│ Total: 480 seats │
│ │
│ [Cancel] [Generate Rows] │
└─────────────────────────────────────────────────────────────────┘
Complex Row Patterns:
Stadiums often have non-uniform seating:
Example: Curved Stand with Variable Row Lengths
Row 1 (closest to field): |1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20| (20 seats)
Row 2: |1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22| (22 seats)
Row 3: |1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24| (24 seats)
...
Row 15 (top): |1|2|3|4|5|...|30|31|32|33|34|35|36|37|38|39|40| (40 seats)
Phase 5: Individual Seat Placement¶
Purpose: Define precise seat coordinates for visualization and generate seat records.
Coordinate System:
┌─────────────────────────────────────────────────────────────────┐
│ Seat Coordinate System │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Global Stadium Coordinates: │
│ - Origin at field center (0,0) │
│ - X-axis: West (-) to East (+) │
│ - Y-axis: South (-) to North (+) │
│ - Units: Meters │
│ │
│ Sector Local Coordinates: │
│ - Origin at sector's reference point (usually bottom-left) │
│ - Relative positioning within sector │
│ │
│ Seat Position Data: │
│ - global_x, global_y: Stadium coordinates │
│ - local_x, local_y: Sector-relative coordinates │
│ - svg_x, svg_y: SVG viewport coordinates (for rendering) │
│ │
└─────────────────────────────────────────────────────────────────┘
Automatic Seat Placement:
System calculates seat positions based on: - Row geometry (straight or curved) - Seat width and spacing - Row starting position - Numbering direction
// Seat Position Calculation (pseudocode)
function calculateSeatPositions(row, sectorGeometry) {
const seats = [];
const rowCurve = row.curveRadius || Infinity;
const seatWidth = row.seatWidth || 0.45;
for (let i = 0; i < row.seatCount; i++) {
const seatNumber = row.direction === 'RTL'
? row.startNumber + row.seatCount - 1 - i
: row.startNumber + i;
// Calculate position along row
const positionAlongRow = i * seatWidth;
// Apply curve if specified
let x, y;
if (rowCurve === Infinity) {
// Straight row
x = row.startX + positionAlongRow;
y = row.startY;
} else {
// Curved row - calculate arc position
const angle = positionAlongRow / rowCurve;
x = row.centerX + rowCurve * Math.sin(angle);
y = row.centerY + rowCurve * Math.cos(angle);
}
seats.push({
row: row.identifier,
number: seatNumber,
x: x,
y: y,
type: 'standard'
});
}
return seats;
}
Manual Seat Adjustment:
For irregular layouts, seats can be manually positioned:
┌─────────────────────────────────────────────────────────────────┐
│ Seat Position Editor │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Selected Seat: Row 5, Seat 12 │
│ │
│ Position: │
│ X: [45.23 ] m Y: [12.67 ] m │
│ │
│ Adjust: │
│ [←] [-0.1m] [+0.1m] [→] │
│ [↑] [-0.1m] [+0.1m] [↓] │
│ │
│ Snap to Grid: [✓] Grid Size: [0.05] m │
│ │
│ Seat Properties: │
│ Type: [Standard ▼] │
│ - Standard │
│ - Technical (not for sale) │
│ - Accessibility (wheelchair) │
│ - Companion (next to accessibility) │
│ - Restricted View │
│ - Premium/VIP │
│ │
│ [Apply to Selection] │
└─────────────────────────────────────────────────────────────────┘
Phase 6: Special Areas & Obstacles¶
Purpose: Mark non-seating areas that affect layout.
Area Types:
| Type | Symbol | Description |
|---|---|---|
| Aisle | ═ | Walkway between seat blocks |
| Stairs | ▤ | Stairway access |
| Column | ● | Structural pillar (obstructs view) |
| Gate | ⊏⊐ | Entry/exit point |
| Camera | ◉ | Camera position (technical) |
| Railing | ─ | Safety barrier |
| Emergency Exit | ⛔ | Emergency egress |
Aisle Configuration:
┌─────────────────────────────────────────────────────────────────┐
│ Aisle/Walkway Configuration │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Type: [Vertical Aisle (between blocks) ▼] │
│ │
│ Position: │
│ Between: [Block A1 ▼] and [Block A2 ▼] │
│ Width: [1.2 ] m │
│ │
│ Extends: │
│ From Row: [1 ] To Row: [15 ] │
│ │
│ Impact on Seating: │
│ [✓] Removes seats from adjacent rows │
│ [✓] Creates seat numbering gap │
│ │
│ Visual: │
│ Row 5: |1|2|3|...|28| |AISLE| |1|2|3|...|30| │
│ Block A1 Block A2 │
│ │
│ [Cancel] [Apply] │
└─────────────────────────────────────────────────────────────────┘
Seat Gap Handling:
When aisles or obstacles interrupt rows:
Example: Row with center aisle
Row 5: |1|2|3|4|5|6|7|8|9|10|11|12|13|14| GAP |15|16|17|18|19|20|21|22|23|24|25|26|27|28|
Gap Configuration:
- After seat: 14
- Gap width: 2 seat positions
- Continues from: 15 (maintains sequence)
Alternative: Split numbering
- Left section: 1-14
- Right section: 1-14 (separate)
Phase 7: Seat Type Classification¶
Purpose: Mark seats with special characteristics.
Seat Types:
| Type | Code | Color | Description |
|---|---|---|---|
| Standard | STD | None | Regular seat |
| Technical | TECH | Purple | Press, camera, broadcast |
| Accessibility | ACC | Blue | Wheelchair position |
| Companion | COMP | Light Blue | Adjacent to accessibility |
| Restricted View | RV | Orange | Obstructed sightline |
| Premium | PREM | Gold | Enhanced comfort/service |
| Segregated | SEG | Red | Away fans section |
Bulk Classification Interface:
┌─────────────────────────────────────────────────────────────────┐
│ Bulk Seat Classification │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Selection Method: │
│ ○ By Position (click seats on map) │
│ ● By Range (specify row/seat range) │
│ ○ By Pattern (every Nth seat) │
│ │
│ Range Selection: │
│ Sector: [North Stand - A ▼] │
│ Block: [Block A1 ▼] │
│ Rows: [1 ] to [3 ] │
│ Seats: [1 ] to [4 ] │
│ │
│ Selection Preview: 12 seats │
│ Row 1: Seats 1-4 │
│ Row 2: Seats 1-4 │
│ Row 3: Seats 1-4 │
│ │
│ Assign Type: [Accessibility (wheelchair) ▼] │
│ │
│ Additional Settings: │
│ [✓] Mark adjacent seats as Companion │
│ [✓] Add to accessibility inventory │
│ │
│ [Cancel] [Apply Classification] │
└─────────────────────────────────────────────────────────────────┘
Phase 8: Pricing Zones¶
Purpose: Map seats to price categories.
Zone Definition:
┌─────────────────────────────────────────────────────────────────┐
│ Pricing Zone Configuration │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Available Price Zones: │
│ ┌──────┬──────────────────┬─────────┬─────────────────────────┐│
│ │ Zone │ Name │ Color │ Default Price ││
│ ├──────┼──────────────────┼─────────┼─────────────────────────┤│
│ │ A │ Premium │ Gold │ €100 ││
│ │ B │ Category 1 │ Blue │ €75 ││
│ │ C │ Category 2 │ Green │ €50 ││
│ │ D │ Category 3 │ Yellow │ €35 ││
│ │ E │ Category 4 │ Orange │ €20 ││
│ └──────┴──────────────────┴─────────┴─────────────────────────┘│
│ │
│ Zone Assignment: │
│ ───────────────── │
│ Method: [● By Sector ○ By Block ○ By Row Range ○ Custom] │
│ │
│ Sector Mapping: │
│ ┌──────────────────────────┬───────────────┐ │
│ │ Sector │ Zone │ │
│ ├──────────────────────────┼───────────────┤ │
│ │ West Stand - VIP │ [A - Premium ▼] │
│ │ West Stand - Upper │ [B - Cat 1 ▼] │
│ │ East Stand - Lower │ [C - Cat 2 ▼] │
│ │ North Stand │ [D - Cat 3 ▼] │
│ │ South Stand (Away) │ [E - Cat 4 ▼] │
│ └──────────────────────────┴───────────────┘ │
│ │
│ [Preview on Map] [Apply Zones] │
└─────────────────────────────────────────────────────────────────┘
Import & Export¶
Import Formats¶
| Format | Use Case | Description |
|---|---|---|
| SVG | Visual template | Stadium outline and sector shapes |
| DXF/DWG | CAD import | Architectural drawings |
| CSV | Seat data | Bulk import of seat coordinates |
| JSON | Full export | Complete stadium configuration |
| GeoJSON | Geographic | Stadium with real-world coordinates |
CSV Import Format¶
For importing seat data from spreadsheets:
sector_code,block_code,row,seat_number,type,x,y,price_zone
ISTOK-A,A1,1,1,standard,45.23,12.67,B
ISTOK-A,A1,1,2,standard,45.68,12.67,B
ISTOK-A,A1,1,3,standard,46.13,12.67,B
ISTOK-A,A1,1,4,accessibility,46.58,12.67,B
ISTOK-A,A1,1,5,companion,47.03,12.67,B
JSON Export Format¶
Complete stadium configuration:
{
"stadium": {
"id": "maksimir",
"name": "Stadion Maksimir",
"code": "MAKSIMIR",
"city": "Zagreb",
"country": "HR",
"capacity": 35000, // Auto-calculated from sector seat counts (read-only)
"coordinates": {
"lat": 45.8186,
"lng": 16.0186
}
},
"geometry": {
"viewBox": "0 0 1000 800",
"fieldRect": {
"x": 200,
"y": 150,
"width": 600,
"height": 400
}
},
"sectors": [
{
"id": "sector-a",
"code": "ISTOK-A",
"name": "East Stand - Section A",
"type": "stand",
"path": "M100,100 L200,100 L200,250 L100,250 Z",
"priceZone": "B",
"blocks": [
{
"id": "block-a1",
"code": "A1",
"rows": [
{
"identifier": "1",
"seatCount": 32,
"startNumber": 1,
"direction": "LTR",
"curveRadius": null,
"y": 12.67
}
]
}
]
}
],
"seats": [
{
"id": "seat-istok-a-1-1",
"sector": "ISTOK-A",
"block": "A1",
"row": "1",
"number": "1",
"type": "standard",
"priceZone": "B",
"coordinates": {
"x": 45.23,
"y": 12.67,
"svgX": 452.3,
"svgY": 126.7
}
}
],
"specialAreas": [
{
"type": "aisle",
"path": "M150,100 L150,250",
"width": 1.2
},
{
"type": "gate",
"id": "gate-1",
"position": { "x": 100, "y": 175 },
"name": "Gate 1"
}
],
"metadata": {
"createdAt": "2025-01-15T10:00:00Z",
"createdBy": "admin@hns.hr",
"version": "1.2.0",
"lastModified": "2025-11-20T14:30:00Z"
}
}
Validation & Preview¶
Validation Rules¶
| Rule | Severity | Description |
|---|---|---|
| Unique seat IDs | Error | Each seat must have unique sector/row/number |
| Row continuity | Warning | Gaps in row numbering detected |
| Capacity mismatch | Warning | Calculated capacity differs from target |
| Overlapping seats | Error | Seat coordinates overlap |
| Missing coordinates | Error | Seats without position data |
| Orphan blocks | Warning | Blocks not assigned to sectors |
| Price zone coverage | Warning | Seats without price zone |
Validation Interface¶
┌─────────────────────────────────────────────────────────────────┐
│ Stadium Validation Report │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ✓ Stadium configuration validated │
│ │
│ Summary: │
│ - Sectors: 12 defined │
│ - Blocks: 36 defined │
│ - Rows: 540 defined │
│ - Seats: 34,872 generated │
│ │
│ ══════════════════════════════════════════════════════════ │
│ │
│ ⚠ Warnings (3) │
│ ├─ Sector ISTOK-C: Calculated capacity (1,420) differs from │
│ │ target (1,500) by 80 seats │
│ ├─ Block B2, Row 7: Gap in seat numbering (14→16) │
│ └─ 23 seats have no price zone assigned │
│ │
│ ✗ Errors (1) │
│ └─ Duplicate seat: ISTOK-A, Row 5, Seat 12 appears twice │
│ │
│ [Show on Map] [Export Report] [Fix Issues] │
└─────────────────────────────────────────────────────────────────┘
Preview Mode¶
Test stadium visualization before publishing:
┌─────────────────────────────────────────────────────────────────┐
│ Preview Mode - Maksimir Stadium [Exit Preview]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ View As: │
│ [● Admin (seat operations)] │
│ [○ Customer (ticket purchase)] │
│ [○ Quota Holder (allocation view)] │
│ │
│ Test Scenarios: │
│ [✓] Show seat status colors │
│ [✓] Enable seat selection │
│ [✓] Display hover tooltips │
│ [✓] Test zoom/pan navigation │
│ │
│ Simulate Status Distribution: │
│ Available: [60 ]% Sold: [30 ]% Blocked: [10 ]% │
│ [Apply Distribution] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Stadium Preview Area │ │
│ │ (Interactive visualization) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Performance: │
│ - Seats rendered: 34,872 │
│ - Render time: 245ms │
│ - Memory usage: 12.4 MB │
│ │
└─────────────────────────────────────────────────────────────────┘
Versioning & History¶
Version Control¶
Stadium configurations support versioning for: - Tracking changes over time - Rolling back problematic changes - Comparing configurations between matches
┌─────────────────────────────────────────────────────────────────┐
│ Version History - Maksimir Stadium │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Current Version: 1.2.0 (Published) │
│ │
│ Version History: │
│ ┌─────────┬────────────────────┬─────────────┬───────────────┐ │
│ │ Version │ Date │ Author │ Changes │ │
│ ├─────────┼────────────────────┼─────────────┼───────────────┤ │
│ │ 1.2.0 │ 2025-11-20 14:30 │ admin@hns │ Added Block D4│ │
│ │ 1.1.0 │ 2025-09-15 10:00 │ stadium@hns │ Price zones │ │
│ │ 1.0.0 │ 2025-06-01 09:00 │ admin@hns │ Initial setup │ │
│ └─────────┴────────────────────┴─────────────┴───────────────┘ │
│ │
│ [View Diff] [Restore Version] [Create New Version] │
└─────────────────────────────────────────────────────────────────┘
Data Model¶
Database Schema¶
-- Stadium Template
CREATE TABLE stadium (
id UUID PRIMARY KEY,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
city VARCHAR(50),
country CHAR(2),
type VARCHAR(20) DEFAULT 'football',
base_capacity INTEGER, -- DEPRECATED: Auto-calculated as sum of sector capacities (read-only)
svg_template TEXT, -- Base SVG content
config_json JSONB, -- Full configuration
version VARCHAR(20) DEFAULT '1.0.0',
status VARCHAR(20) DEFAULT 'draft', -- draft, published, archived
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Sector Definition
CREATE TABLE stadium_sector (
id UUID PRIMARY KEY,
stadium_id UUID REFERENCES stadium(id),
code VARCHAR(20) NOT NULL,
name VARCHAR(100),
type VARCHAR(20), -- stand, corner, premium, etc.
svg_path TEXT, -- SVG path definition
price_zone_id UUID,
target_capacity INTEGER, -- Computed from seat count when seat map exists
display_order INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(stadium_id, code)
);
-- Block Definition
CREATE TABLE stadium_block (
id UUID PRIMARY KEY,
sector_id UUID REFERENCES stadium_sector(id),
code VARCHAR(20) NOT NULL,
name VARCHAR(100),
display_order INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(sector_id, code)
);
-- Row Configuration
CREATE TABLE stadium_row_config (
id UUID PRIMARY KEY,
block_id UUID REFERENCES stadium_block(id),
row_identifier VARCHAR(10) NOT NULL,
seat_count INTEGER NOT NULL,
start_seat_number INTEGER DEFAULT 1,
numbering_direction VARCHAR(10) DEFAULT 'LTR',
curve_radius DECIMAL(10,2), -- NULL for straight rows
row_y_position DECIMAL(10,4),
seat_width DECIMAL(5,3) DEFAULT 0.45,
row_order INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(block_id, row_identifier)
);
-- Seat Template (generated from row config)
CREATE TABLE stadium_seat_template (
id UUID PRIMARY KEY,
sector_id UUID REFERENCES stadium_sector(id),
block_id UUID REFERENCES stadium_block(id),
row_identifier VARCHAR(10) NOT NULL,
seat_number VARCHAR(10) NOT NULL,
seat_type VARCHAR(20) DEFAULT 'standard',
price_zone_id UUID,
x_coordinate DECIMAL(10,4),
y_coordinate DECIMAL(10,4),
svg_x DECIMAL(10,4),
svg_y DECIMAL(10,4),
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(sector_id, row_identifier, seat_number)
);
-- Special Areas
CREATE TABLE stadium_special_area (
id UUID PRIMARY KEY,
stadium_id UUID REFERENCES stadium(id),
area_type VARCHAR(20), -- aisle, stairs, gate, column
name VARCHAR(50),
svg_path TEXT,
properties JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Price Zones
CREATE TABLE price_zone (
id UUID PRIMARY KEY,
stadium_id UUID REFERENCES stadium(id),
code VARCHAR(10) NOT NULL,
name VARCHAR(50),
color VARCHAR(7), -- Hex color
default_price DECIMAL(10,2),
display_order INTEGER,
UNIQUE(stadium_id, code)
);
Related Documentation¶
- Stadium Template Management
- Sector Seat Map Configuration
- Stadium Visualization Component
- Admin Seat Operations Flow
- Admin Stadium Configuration Flow
Technology Decisions¶
Based on comprehensive technology research, the following stack has been selected for implementing the Stadium Design Tool.
Recommended Stack: Konva.js + TypeScript¶
| Component | Technology | Rationale |
|---|---|---|
| Rendering | Canvas (not SVG) | Required for 35,000-50,000 seat performance |
| Library | Konva.js | Best balance of features, performance, and ease of use |
| Language | TypeScript | Type safety for complex editor state |
| State Management | Immer + Custom | Immutable updates, undo/redo support |
| Build Tool | Vite | Fast development, single bundle output |
| Integration | Standalone SPA | Clean separation from Symfony backend |
| Framework | None (vanilla) | Minimal overhead, maximum flexibility |
Why Konva.js?¶
- Performance - Handles 35,000-50,000 seats with scene graph optimization
- Built-in Features - Zoom, pan, drag-drop, selection all native
- Official Example - Seats Reservation Widget
- Easy Integration - Simple script embed in Twig templates
- Active Community - ~10K GitHub stars, regular updates
- JSON Serialization - Native save/load for stadium configurations
Performance Requirements Met¶
| Seat Count | SVG | Canvas (Konva.js) | WebGL (PixiJS) |
|---|---|---|---|
| < 2,000 | Good | Good | Overkill |
| 2,000-10,000 | Degraded | Good | Good |
| 10,000-35,000 | Poor | Good | Excellent |
| 35,000-50,000 | Unusable | Good (Optimized) | Excellent |
| 50,000+ | N/A | Virtual rendering | Best |
Architecture Overview¶
┌─────────────────────────────────────────────────────────────┐
│ HNS Admin Portal (Symfony) │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Stadium Design Tool (Standalone SPA) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Konva.js Canvas │ │ │
│ │ │ - Stage (container) │ │ │
│ │ │ - Layers (sectors, seats, annotations) │ │ │
│ │ │ - Groups (blocks, rows) │ │ │
│ │ │ - Shapes (seats, aisles, gates) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ State Management (TypeScript + Immer) │ │
│ │ - Editor state (zoom, pan, tool mode) │ │
│ │ - Stadium data (sectors, blocks, rows, seats) │ │
│ │ - Undo/redo history stack │ │
│ │ - Selection state │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ JSON API │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Symfony Backend │ │
│ │ POST /api/stadiums/{id} │ │
│ │ GET /api/stadiums/{id} │ │
│ │ POST /api/stadiums/{id}/validate │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Build vs Buy Decision¶
Decision: Full Custom Build
| Aspect | Commercial (Seats.io) | Custom Build (Recommended) |
|---|---|---|
| Seat Selection UI | Excellent | Build needed |
| Stadium Design Tool | Basic | Full control |
| 50K+ Seat Performance | Proven | With Konva.js |
| OIB/Croatian Integration | Not available | Custom |
| Cost (5 years) | ~$150-300K | ~$400-600K |
| Time to Deploy | 2-4 weeks | 3-6 months |
Commercial solutions excel at seat selection but provide limited seat map design tools. None offer the sophisticated stadium template creation workflow required for HNS.
Fallback Option: PixiJS¶
If performance testing shows Konva.js struggling with 50K seats:
| Aspect | Konva.js | PixiJS |
|---|---|---|
| Rendering | Canvas 2D | WebGL |
| Performance | Good (50K) | Excellent (100K+) |
| Drawing tools | Native | Need custom |
| Learning curve | Easy | Hard |
| Development time | Shorter | Longer |
Recommendation: Start with Konva.js, migrate to PixiJS only if needed.
Detailed Technical Specification
For complete technical details including code examples, data structures, and implementation guidance, see Stadium Design Tool Technical Specification.
Last Updated: January 2026