@talabes/football-lineup-generator
v1.3.9
Published
A TypeScript library for generating visual football lineup diagrams from team positioning data. Fork of ncamaa/football-lineup-generator with bug fixes and improvements.
Downloads
107
Maintainers
Readme
Football Lineup Generator
A TypeScript library for generating visual football (soccer) lineup diagrams from team positioning data. Create beautiful, interactive lineup visualizations that can be easily embedded in web applications.
Features
- 🏈 Generate visual football field with accurate proportions
- 👥 Position players based on their assigned roles
- 🎨 Customizable colors, sizes, and styling
- 📱 Responsive and scalable canvas-based rendering
- 🔧 TypeScript support with full type definitions
- ⚡ Lightweight with no external dependencies
- 🏆 Support for substitutes and bench players
- 🎮 Interactive mode - Drag and drop players to customize formations
This Fork
This is a fork of ncamaa/football-lineup-generator with significant feature additions and bug fixes not present in the upstream library:
| Feature | Upstream | This fork | |---------|:--------:|:---------:| | Basic lineup rendering | ✅ | ✅ | | Substitute players (bench rendering) | ❌ | ✅ | | Interactive drag-and-drop players | partial | ✅ fixed | | Draggable ball on the field | ❌ | ✅ | | Canvas recording (WebM export) | ❌ | ✅ | | Built-in recording UI controls | ❌ | ✅ | | Programmatic position read/write APIs | ❌ | ✅ | | Playwright test suite | ❌ | ✅ |
New features in detail:
- Substitutes (
showSubstitutes): Renders bench players off-field with configurable positioning (left/bottom/right). - Interactive mode (
interactive: true): Drag-and-drop players across all three layout types (full/half/split pitch). The upstream had broken coordinate handling for non-full-pitch layouts — fixed here. - Ball (
ball: true): A draggable ball rendered on the field withonBallMovecallback and programmaticgetBallPosition()/setBallPosition()APIs. - Recording (
recording: true): Record canvas animations to WebM via MediaRecorder. Start, pause, resume, stop, and download from code or via the optional built-in overlay UI (recordingUI: true). - Programmatic APIs:
getAllPlayerPositions()returns every player's current canvas coordinates (including drag overrides);getCustomCoordinates(),setCustomCoordinate(), andclearCustomCoordinates()allow saving and restoring custom formations. - Test suite: Playwright tests covering rendering, interactive mode, ball behaviour, and the full public API surface.
Screenshots
| Full Pitch | Half Pitch | Split Pitch |
|:----------:|:----------:|:-----------:|
|
|
|
|
Development
Getting Started
Install dependencies:
npm installStart development environment:
npm run devThis will:
- Compile TypeScript in watch mode (auto-recompiles on file changes)
- Start an HTTP server on
http://localhost:3000 - Automatically open
example.htmlin your browser - Enable CORS for local development
Alternative commands:
npm start # Same as npm run dev npm run build # One-time TypeScript compilation npm run watch # TypeScript watch mode only npm run serve # HTTP server only
Development Workflow
- Make changes to TypeScript files in the
src/directory - Changes are automatically compiled and reflected in the browser
- Open
http://localhost:3000to see the example page - The example page loads the compiled library from
dist/index.js
Project Structure
src/
├── functions/ # Core rendering functions
├── index.ts # Main library entry point
├── renderer.ts # Football lineup renderer class
└── types.ts # TypeScript type definitionsInstallation
npm install football-lineup-generatorUsage
TypeScript/ESM
import { generateLineup, FootballLineupRenderer, Team, Position, LayoutType } from 'football-lineup-generator';CommonJS
const { generateLineup, FootballLineupRenderer, Team, Position, LayoutType } = require('football-lineup-generator');Quick Start
import { generateLineup, Team, Position, LayoutType } from 'football-lineup-generator';
// Define your lineup data
const lineupData = {
homeTeam: {
name: "Arsenal",
players: [
{
player: { id: 1, name: "Ramsdale", jerseyNumber: 1 },
team: Team.RED,
position: Position.GOALKEEPER
},
{
player: { id: 2, name: "White", jerseyNumber: 4 },
team: Team.RED,
position: Position.RIGHT_BACK
},
// ... more players
]
},
awayTeam: {
name: "Chelsea",
players: [
{
player: { id: 11, name: "Kepa", jerseyNumber: 1 },
team: Team.YELLOW,
position: Position.GOALKEEPER
},
// ... more players
]
}
};
// Generate the lineup canvas (now async)
const canvas = await generateLineup(lineupData, {
layoutType: LayoutType.SPLIT_PITCH,
backgroundImage: 'path/to/field-image.jpg'
});
// Add to your DOM
document.body.appendChild(canvas);API Reference
generateLineup(lineupData, config?)
Creates a canvas element with a football lineup visualization.
Parameters:
lineupData(LineupData): The lineup data containing both teams' player positionsconfig(LineupConfig, optional): Configuration options for the visualization
Returns: HTMLCanvasElement that can be added to the DOM
generateLineupFromPositioning(positioningData, homeTeamName, awayTeamName, config?)
Convenience function to create a lineup from backend positioning data format.
Parameters:
positioningData(Array): Array of positioning data from backendhomeTeamName(string): Name of the home teamawayTeamName(string): Name of the away teamconfig(LineupConfig, optional): Configuration options
Returns: HTMLCanvasElement
Configuration Options
interface LineupConfig {
width?: number; // Canvas width (default: 800)
height?: number; // Canvas height (default: 600)
layoutType?: LayoutType; // Layout type (default: FULL_PITCH)
showPlayerNames?: boolean; // Show player names (default: true)
showJerseyNumbers?: boolean; // Show jersey numbers (default: true)
fieldColor?: string; // Field background color (default: '#4CAF50')
lineColor?: string; // Field line color (default: '#FFFFFF')
homeTeamColor?: string; // Home team player color (default: '#FF5722')
awayTeamColor?: string; // Away team player color (default: '#2196F3')
fontSize?: number; // Text font size (default: 12)
playerCircleSize?: number; // Player circle radius (default: 20)
backgroundImage?: string | HTMLImageElement; // Custom background image
interactive?: boolean; // Enable interactive mode for drag-and-drop (default: false)
onPlayerMove?: (playerId: number, team: Team, x: number, y: number) => void; // Callback when player is moved
}Layout Types
enum LayoutType {
FULL_PITCH = "full_pitch", // Both teams positioned across the entire pitch
HALF_PITCH = "half_pitch", // Each team positioned in their respective half
SPLIT_PITCH = "split_pitch" // Two separate parallel pitches side by side
}Team and Position Enums
Team
enum Team {
RED = "red",
YELLOW = "yellow"
}Position
enum Position {
GOALKEEPER = "goalkeeper",
CENTER_BACK = "center_back",
LEFT_BACK = "left_back",
RIGHT_BACK = "right_back",
DEFENSIVE_MIDFIELDER = "defensive_midfielder",
CENTER_MIDFIELDER = "center_midfielder",
ATTACKING_MIDFIELDER = "attacking_midfielder",
LEFT_MIDFIELDER = "left_midfielder",
RIGHT_MIDFIELDER = "right_midfielder",
LEFT_WINGER = "left_winger",
RIGHT_WINGER = "right_winger",
CENTER_FORWARD = "center_forward",
LEFT_FORWARD = "left_forward",
RIGHT_FORWARD = "right_forward",
SUBSTITUTE = "substitute"
}Layout Options Examples
Full Pitch Layout (Default)
Both teams positioned across the entire field with traditional mirrored positioning.
const canvas = await generateLineup(lineupData, {
layoutType: LayoutType.FULL_PITCH
});Half Pitch Layout
Each team positioned only in their respective half for clearer visualization.
const canvas = await generateLineup(lineupData, {
layoutType: LayoutType.HALF_PITCH
});Split Pitch Layout
Two separate parallel pitches side by side, each showing one team's formation.
const canvas = await generateLineup(lineupData, {
layoutType: LayoutType.SPLIT_PITCH
});Background Image Support
Add custom field backgrounds using image URLs or loaded Image elements:
// Using image URL
const canvas = await generateLineup(lineupData, {
backgroundImage: 'https://example.com/field-texture.jpg'
});
// Using loaded Image element
const img = new Image();
img.src = 'path/to/custom-field.jpg';
img.onload = async () => {
const canvas = await generateLineup(lineupData, {
backgroundImage: img
});
document.body.appendChild(canvas);
};Advanced Example
import { generateLineup, Team, Position, LayoutType } from 'football-lineup-generator';
// Create a 4-3-3 formation
const lineupData = {
homeTeam: {
name: "Manchester City",
players: [
// Goalkeeper
{ player: { id: 1, name: "Ederson", jerseyNumber: 31 }, team: Team.RED, position: Position.GOALKEEPER },
// Defense
{ player: { id: 2, name: "Walker", jerseyNumber: 2 }, team: Team.RED, position: Position.RIGHT_BACK },
{ player: { id: 3, name: "Dias", jerseyNumber: 3 }, team: Team.RED, position: Position.CENTER_BACK },
{ player: { id: 4, name: "Stones", jerseyNumber: 5 }, team: Team.RED, position: Position.CENTER_BACK },
{ player: { id: 5, name: "Cancelo", jerseyNumber: 27 }, team: Team.RED, position: Position.LEFT_BACK },
// Midfield
{ player: { id: 6, name: "Rodri", jerseyNumber: 16 }, team: Team.RED, position: Position.DEFENSIVE_MIDFIELDER },
{ player: { id: 7, name: "De Bruyne", jerseyNumber: 17 }, team: Team.RED, position: Position.CENTER_MIDFIELDER },
{ player: { id: 8, name: "Silva", jerseyNumber: 20 }, team: Team.RED, position: Position.CENTER_MIDFIELDER },
// Forward
{ player: { id: 9, name: "Mahrez", jerseyNumber: 26 }, team: Team.RED, position: Position.RIGHT_WINGER },
{ player: { id: 10, name: "Haaland", jerseyNumber: 9 }, team: Team.RED, position: Position.CENTER_FORWARD },
{ player: { id: 11, name: "Grealish", jerseyNumber: 10 }, team: Team.RED, position: Position.LEFT_WINGER },
]
},
awayTeam: {
name: "Liverpool",
players: [
// Add Liverpool players here...
]
}
};
// Custom configuration with split pitch and background image
const config = {
width: 1000,
height: 700,
layoutType: LayoutType.SPLIT_PITCH,
fieldColor: '#2E7D32',
homeTeamColor: '#87CEEB',
awayTeamColor: '#DC143C',
fontSize: 14,
playerCircleSize: 25,
backgroundImage: 'https://example.com/stadium-field.jpg'
};
const canvas = await generateLineup(lineupData, config);
document.getElementById('lineup-container').appendChild(canvas);Interactive Mode
Enable interactive mode to allow users to drag and drop players to customize formations:
import { generateLineup, Team, Position, LayoutType } from 'football-lineup-generator';
const lineupData = {
// ... your lineup data
};
const config = {
layoutType: LayoutType.FULL_PITCH,
interactive: true, // Enable interactive mode
onPlayerMove: (playerId, team, x, y) => {
// This callback is triggered when a player is moved
console.log(`Player ${playerId} from team ${team} moved to (${x}, ${y})`);
// You can save the new positions to your backend here
savePlayerPosition(playerId, team, x, y);
}
};
const canvas = generateLineup(lineupData, config);
document.body.appendChild(canvas);Interactive Mode Features
- Drag and Drop: Click and drag any player to reposition them on the field
- Real-time Updates: The canvas re-renders instantly as you drag players
- Position Callbacks: Get notified when a player's position changes via the
onPlayerMovecallback - Touch Support: Works with both mouse and touch events for mobile devices
- Cursor Feedback: The cursor changes to indicate when you're hovering over a draggable player
Using the FootballLineupRenderer Class
For more control over the interactive features, use the FootballLineupRenderer class directly:
import { FootballLineupRenderer, Team, Position, LayoutType } from 'football-lineup-generator';
const canvas = document.createElement('canvas');
const renderer = new FootballLineupRenderer(canvas, {
interactive: true,
onPlayerMove: (playerId, team, x, y) => {
console.log('Player moved:', playerId, team, x, y);
}
});
renderer.render(lineupData);
document.body.appendChild(canvas);
// Get all custom coordinates
const customCoords = renderer.getCustomCoordinates();
// Set a specific player's position programmatically
renderer.setCustomCoordinate(playerId, team, x, y);
// Clear all custom positions (reset to default)
renderer.clearCustomCoordinates();
// Clean up event listeners when done
renderer.destroy();Backend Integration
If you're using the positioning data format from your backend:
import { generateLineupFromPositioning, Team, Position } from 'football-lineup-generator';
// Backend data format
const backendData = [
{
match_id: 1,
player_id: 1,
player_name: "Lionel Messi",
jersey_number: 10,
team: Team.RED,
position: Position.RIGHT_WINGER
},
// ... more positioning data
];
const canvas = generateLineupFromPositioning(
backendData,
"PSG",
"Barcelona"
);Browser Compatibility
This library works in all modern browsers that support HTML5 Canvas:
- Chrome 4+
- Firefox 2+
- Safari 3.1+
- Edge (all versions)
- Internet Explorer 9+
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Changelog
1.0.0
- Initial release
- Basic lineup generation
- Canvas-based rendering
- TypeScript support
- Customizable styling
