npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

Readme

Football Lineup Generator

npm version npm downloads GitHub stars GitHub forks GitHub issues License: MIT

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 with onBallMove callback and programmatic getBallPosition() / 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(), and clearCustomCoordinates() 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 | |:----------:|:----------:|:-----------:| | Full Pitch | Half Pitch | Split Pitch |

Development

Getting Started

  1. Install dependencies:

    npm install
  2. Start development environment:

    	npm run dev

    This will:

    • Compile TypeScript in watch mode (auto-recompiles on file changes)
    • Start an HTTP server on http://localhost:3000
    • Automatically open example.html in your browser
    • Enable CORS for local development
  3. 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:3000 to 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 definitions

Installation

npm install football-lineup-generator

Usage

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 positions
  • config (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 backend
  • homeTeamName (string): Name of the home team
  • awayTeamName (string): Name of the away team
  • config (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 onPlayerMove callback
  • 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