Kyogijo Stadiums競技場

Every export of every entry point, with its signature and its doc comment. Made from the source when the site is built, so it cannot fall behind the code.

About the name

@johnmorrisdotca/kyogijo-stadiums 0.6.0 · 31 entry points · 1523 exports

@johnmorrisdotca/kyogijo-stadiums

abbreviate AFL_GROUNDS Arena ARENA_IDS arenaById arenaByName arenaByWikidata ArenaCapacity arenaCourtIn ArenaData ArenaName arenaNameIn arenaOfficialName arenaRinkIn ARENAS arenasByName ArenaSize ArenaSource arenaSourceUrl ArrowsLayer ArtisticPlan AthleticsFieldEvent AthleticsInfield AthleticsRace AthleticsStyle AthleticsTrackLook AxesOptions Background BACKGROUND_OPACITY BACKGROUND_PATTERNS BackgroundFit BackgroundOptions BackgroundPattern Bandwidth BaseballLeague BaseballRead BaseballSituation BasketballLeague Board Box BudoScore BudoSport BudoStyle capitals chooseLeague COLOR_PROPERTIES Colors Comet ContourLayer contrastOf convertLength COURT_THEMES CourtFigures CourtMarks CourtStyle CoverQuirk CrowdOptions CrowdShape crowdStands CrowdStands DeckLook DelaunayLayer describe Detail DiamondFigures DirectionOptions DivePlan DivingPlatform dojo Dojo DojoSpec drawBadge drawDojo drawKyogijo drawLayers drawScore drawSurface DRIVE_CODES DriveLeg DriveRead DriveResult DugoutSide EmblemDrawOptions EmblemId EmblemRef EmblemRenderer EmblemStyleName ERA_CURRENT ERA_SOURCES ERA_SPORTS EraChange eraChanges eraRange EraRules EraRulesOf EraSource EraSpan EraSport eraStates Family FAMILY_OF Feature FEATURE_INFO FEATURES Fence fenceFor FIELD_SPORTS FieldArea FieldGoalPlan FieldOutline fieldPlan FieldPlan FieldShape FieldSport FieldStyle fitLines FittedLines FlowLayer footballIn FootballLeague FootballRead FootballRules FootballSituation Formation FormationLayer FormationPlayer FormationPosition FormationSport formatTime frameMatrix GoalInfo GoalStyle GRIDIRON_GROUNDS Ground GROUND_IDS groundById groundByName groundByWikidata GroundCompetition GroundData GroundName groundNameIn groundOfficialName GROUNDS groundsByName groundsHosting GroundSource groundSourceUrl GroundTournament groupOf HARDWOOD_SMALL HardwoodOptions hasHistory HEAT_RAMP_NAMES HEAT_RAMPS HeatBase HeatBins HeatDatum HeatLabels HeatLegend HeatmapLayer HeatRamp HeatRampName HeatStatistic HexbinLayer HexOrientation HillQuirk HockeyLeague HockeyRead HockeySituation HOME_FIRST HOME_FIRST_LEAGUES homeSideFor HullLayer inkOn isFieldSport isPoolSport isPortrait isUnit KYOGIJO_ATHLETICS_STYLE KYOGIJO_BACKGROUND_STYLE KYOGIJO_BUDO_STYLE KYOGIJO_COURTS_STYLE KYOGIJO_CROWD_STYLE KYOGIJO_CURLING_STYLE KYOGIJO_FEATURE_STYLE KYOGIJO_FIELDS_STYLE KYOGIJO_HARDWOOD_STYLE KYOGIJO_HEAT_STYLE KYOGIJO_LOOK_STYLE KYOGIJO_MARK_STYLE KYOGIJO_NOTE_STYLE KYOGIJO_PAGEANT_MOTION KYOGIJO_PAGEANT_STYLE KYOGIJO_POOL_STYLE KYOGIJO_SITUATION_STYLE KYOGIJO_SKATING_STYLE KYOGIJO_STRINGS KYOGIJO_STYLE KYOGIJO_TEAM_STYLE KYOGIJO_TRACK_STYLE KYOGIJO_WALLS_STYLE KyogijoWords LanePlan Language Layer LayerBase LayerKind layerTable LayerTeam League LEAGUE_SOURCES leagueAt LeagueAt leagueById LeagueChange leagueChanges leagueFigures leagueIds LEAGUES leagueSeason leagueSize LeagueSource LeagueSpan LegendLayer LineDash LineDatum LinesLayer LineStyle LineupLayer LineupPlayer LineWidth Look LOOK_NAMES LOOK_PAIRS LookGroup LookName lookPalette LookPalette LookPaletteKey LOOKS lookStyle luminance MarkDatum MarkShape MlbRules MoveDatum Named NbaRules NetStyle NetworkLayer NetworkNode NhlRules officialName Orientation OUTCOMES OutcomeStyle outlinePath outlinePoints OverhangQuirk PaceOptions PageantOptions PageantStyle Paint Park PARK_IDS parkById parkByName parkByWikidata ParkData ParkDugouts parkNameIn parkOfficialName ParkPeriod ParkPeriodData parkPointsIn ParkQuirk parkQuirksIn PARKS parksByName ParkSource parkSourceUrl parkUnit ParkWall ParkWallData parkWallsIn parseTime PassDatum PlaceAlias PlaceNameKind plainName PlayerDatum PointDatum PointQuirk PointsLayer PointStyle PoloPlan POOL_SPORTS PoolKind PoolLook poolPlan PoolPlan PoolRace PoolSection PoolSport PoolStyle QuirkKind QuirkName QuirkOptions RaceShow rampColour RangeBox RangeInfo RangeName RangeNamed RANGES readSituation Reducer rescale Rescaler rgbOf RinkFigures Rope RopeColour rulesFor safeColour safeLogo safePicture Scale ScoreOptions seasonLabel ShootoutAttempt ShootoutRound Side Situation SituationContext SituationRead SituationTeam situationText Size SkatingDiscipline SkatingFurniture SkatingInfield SkatingRace SkatingStyle SOCCER SOCCER_PITCH_IDS SOCCER_PITCHES SOCCER_PROVIDER_IDS SOCCER_PROVIDERS soccerFrame SoccerFrame soccerLandmarks soccerPitchClass SoccerPitchClass SoccerPitchId SoccerProvider SoccerProviderId SoccerSize soccerSpec SoccerSpec SonarLayer Sport SPORTS StandGap StandOutline StandRing StandSector StatisticName statusLabel statusText StatusWord StripePattern SURFACE_UNITS SurfaceOptions Swimmer SwimmerStatus swimPlan SwimPlan TableOptions Tatami Team TeamMark TextDatum TextLayer TextStyle textWidth Theme Unit UNITS useEmblems VARIANTS VenueItem venueLayout VenueLayout VenueOptions VenuePool VERSION VoronoiLayer WALL_SEATS WallOptions WallPlace WaterJump winnerOf ZONE_SET_IDS ZONE_SETS ZoneSetId ZonesLayer

function abbreviate

abbreviate(name: string): string

A short form of a name: its initials when it has several words, else its first letters. Japanese names give their first two or three characters.

This is a guess at a label, not a code. For a national team it is often not the code the sport uses: "Spain" gives SPA, where FIFA's three-letter code is ESP ("Germany" gives GER, which happens to match; "Netherlands" gives NET, FIFA's is NED). Pass each team's abbr yourself (the FIFA, World Rugby or ICC code your data carries); the package bundles no table of country codes.

const AFL_GROUNDS

AFL_GROUNDS: readonly AflGround[]

The grounds whose length and width the AFL's venue pages print (read 2026-10-13), each within the Laws' range. The AFL's page for Adelaide Oval prints 190 by 125 m, a length beyond the 185 m the Laws allow, so that ground is not here.

type Arena

type Arena = ArenaData;

An arena as the package holds it: the data, with the sizes in the unit they were published in (see arenaRinkIn and arenaCourtIn for them in feet).

const ARENA_IDS

ARENA_IDS: readonly string[]

The keys of ARENAS, in the order of the data: the arenas in use first, then those of the past.

function arenaById

arenaById: (id: string | undefined) => Arena | undefined

The arena with a key, or undefined.

function arenaByName

arenaByName: (name: string | undefined) => Arena | undefined

The arena a name belongs to (the best match when several answer to it), or undefined.

function arenaByWikidata

arenaByWikidata: (wikidata: string | undefined) => Arena | undefined

The arena whose Wikidata item is Q894567, or undefined.

type ArenaCapacity

interface ArenaCapacity { hockey?: number; basketball?: number; }

The seats an arena holds for each sport, as the infobox of its article gives them (the first number it gives for the sport).

function arenaCourtIn

arenaCourtIn: (arena: Arena | undefined, year?: number) => { length: number; width: number; } | undefined

The size of an arena's court in feet, in a year or today, when the sources publish one that differs from the NBA's 94 by 50 feet; else undefined.

type ArenaData

interface ArenaData { /** The key: `boston-garden`. */ id: string; /** The Wikidata item of the arena (`Q894567`), the key to join an arena to other data. */ wikidata: string; /** The everyday name in English (`Boston Garden`, `TD Garden`); the title of the English article is one of the `aliases`. */ name: string; /** Names the arena had for some years, in order of time: where two share a year the later one is the n…

An arena as the data gives it: the rink and the court are the sizes published where they differ from the league's.

type ArenaName

interface ArenaName { name: string; from: number; to: number; }

A name an arena had for some years, first to last.

function arenaNameIn

arenaNameIn(arena: Arena, year?: number, language?: Language): string

The name an arena had in a year (the name of the time in English; in Japanese there is one name for every year).

function arenaOfficialName

arenaOfficialName(arena: Arena, language?: Language, year?: number): string | undefined

The name an arena officially has in a language, when it is not the everyday name; else undefined.

function arenaRinkIn

arenaRinkIn: (arena: Arena | undefined, year?: number) => { length: number; width: number; } | undefined

The size of an arena's rink in feet, in a year or today (the latest), when the sources publish one that differs from the NHL's; else undefined and the NHL's 200 by 85 feet is the rink.

Example

import { arenaById, arenaRinkIn } from "@johnmorrisdotca/kyogijo-stadiums/arenas";
console.log(arenaRinkIn(arenaById("boston-garden")!), arenaRinkIn(arenaById("matthews-arena")!, 1926), arenaRinkIn(arenaById("td-garden")!));
// { length: 190.9, width: 83 } { length: 220, width: 90 } undefined

const ARENAS

ARENAS: Readonly<Record<string, ArenaData>>

Every arena by its key: the homes of the NHL's, the NBA's, the WNBA's and the PWHL's clubs, today's and the old ones, each with where it stands, when it was in use, its names, its seats for each sport and, where the sources publish one that differs from the league's, the size of its rink or its court.

Example

import { ARENAS } from "@johnmorrisdotca/kyogijo-stadiums/arenas";
const garden = ARENAS["boston-garden"];
console.log(garden.name, garden.city, garden.from, garden.to, garden.capacity?.hockey);
// Boston Garden Boston 1928 1995 14448

function arenasByName

arenasByName: (name: string | undefined) => Arena[]

The arenas that answer to a name, best match first (see placesByName): the everyday names, then the official and cited names, then the former names and the nicknames.

type ArenaSize

interface ArenaSize { length: number; width: number; unit: "ft" | "m"; /** The first and the last year it had this size, where the source gives them. */ from?: number; to?: number; /** Where it was read: the article and its revision, and in a few words what it says (not a copied passage). */ source: string; }

A published size of an ice rink or a basketball court, in the unit it was published in, with where it was read and, if the source dates it, the years it had that size.

type ArenaSource

interface ArenaSource { title: string; revision: number; }

Where an arena's facts were read: an article of Wikipedia at a revision.

function arenaSourceUrl

arenaSourceUrl(source: ArenaSource, language?: "en" | "ja"): string

The address of the revision of an article that an arena's facts were read from.

type ArrowsLayer

interface ArrowsLayer extends LayerBase { kind: "arrows"; data: readonly LineDatum[]; style?: LineStyle; }

type ArtisticPlan

interface ArtisticPlan extends Common { sport: "artisticswimming"; area: "olympic" | "minimum"; length: number; width: number; /** For the minimum area: the deep part, in metres from the left wall and the top wall, and its size. */ deep: { x: number; y: number; length: number; width: number } | undefined; }

An artistic swimming area to draw.

type AthleticsFieldEvent

type AthleticsFieldEvent = "high-jump" | "pole-vault" | "long-jump" | "triple-jump" | "shot-put" | "discus-throw" | "hammer-throw" | "javelin-throw";

The field events a drawing can mark in the infield, and alone at a large scale (layout).

type AthleticsInfield

type AthleticsInfield = "pitch" | "grass" | "none";

What lies inside the track: the soccer pitch the package draws (pitch), plain mown grass, or nothing but the track's own surface.

type AthleticsRace

type AthleticsRace = | "60m" | "60m-hurdles" | "100m" | "100m-hurdles" | "110m-hurdles" | "200m" | "400m" | "400m-hurdles" | "800m" | "1000m" | "1500m" | "mile" | "2000m" | "3000m" | "5000m" | "10000m" | "2000m-steeplechase" | "3000m-steeplechase" | "4x100m" | "4x400m";

The races a drawing can mark: where each starts, how it is staggered or curved, its hurdles or its exchange zones. A race is named by its distance, in kebab case.

type AthleticsStyle

interface AthleticsStyle { /** The lanes of the oval: 4 to 9 outdoors (8 by default; the Manual says nine is the most there should be), 4 to 6 on the indoor track (6 by default). */ lanes?: number; /** The lanes of the straight that runs on beyond the oval's, 6 to 10 (the oval's number by default), as the straight of a 10-lane stadium is built (Manual 2.2.2). */ straightLanes?: number; /** The colour of the track: `…

What an athletics drawing takes beyond a surface: the number of lanes, the colour of the track, what is in the infield, which races and which field events are marked. Every part is optional; the standard 400 m track of World Athletics, with eight lanes, a red track, a soccer pitch inside it and every field event in its usual place, is drawn without any.

type AthleticsTrackLook

type AthleticsTrackLook = "red" | "blue" | "terracotta" | "green";

The colour of the track: Mondo red (the usual), Berlin's blue (the 2009 World Championships), the terracotta of a club track, or green. A colour of your own is colors.surface... or colors.track.

type AxesOptions

interface AxesOptions { /** A frame of `FRAMES` (or one of your own) for the sport: the ticks are its numbers. The surface's own (feet, or `units`) when missing. */ frame?: string | Frame; /** The distance between ticks, or the values to put one at, for both axes or for `x` and `y` each. A round step that gives about eight ticks when missing. */ ticks?: number | readonly number[] | { x?: number | readonly number[]; …

The axes of axes: a frame, where the ticks stand and whether they are numbered.

type Background

type Background = BackgroundPattern | "none" | BackgroundOptions;

A ground: a pattern's name, "none", or the options in full.

const BACKGROUND_OPACITY

BACKGROUND_OPACITY: Readonly<Record<BackgroundPattern, number>>

The opacity each pattern is drawn at when opacity says nothing.

const BACKGROUND_PATTERNS

BACKGROUND_PATTERNS: readonly BackgroundPattern[]

The patterns, in the order a menu lists them.

type BackgroundFit

type BackgroundFit = "cover" | "contain" | "stretch";

How a photograph fills the ground: cover fills it and cuts what overhangs, contain shows all of it, stretch fills it out of shape.

type BackgroundOptions

interface BackgroundOptions { /** A pattern drawn over the ground, in a low opacity. */ pattern?: BackgroundPattern; /** A colour under the pattern and the photograph (a hex colour, `rgb()`, `hsl()`, a name or `var(--name)`). Without it the ground keeps its own colour. */ color?: string; /** The opacity of the pattern, 0 to 1. Each pattern has its own subtle one. */ opacity?: number; /** The size of the pattern's ti…

A ground in full. Every part is optional; with none, there is no ground.

type BackgroundPattern

type BackgroundPattern = "felt" | "grass" | "wood" | "ice" | "paper" | "grid" | "dots";

The patterns: a tile of shapes repeated over the ground.

type Bandwidth

type Bandwidth = number | readonly [number, number] | "scott";

The shape of a density's bandwidth.

type BaseballLeague

type BaseballLeague = LeagueBase<"baseball", DiamondFigures> & { game: "baseball" | "softball" };

A league with a diamond of its own.

type BaseballRead

interface BaseballRead { kind: "baseball"; bases?: [boolean, boolean, boolean]; outs?: number; inning?: number; half?: "top" | "bottom"; balls?: number; strikes?: number; }

Baseball, read.

type BaseballSituation

interface BaseballSituation { /** The runners: `[first, second, third]`, `true` for a base with a runner on it. */ bases?: readonly [boolean, boolean, boolean] | readonly boolean[]; /** The outs, 0 to 3. */ outs?: number; /** The inning, 1 or more. */ inning?: number; /** `top` (the visitors bat) or `bottom` (the home team bats). */ half?: "top" | "bottom"; /** The count: balls 0 to 3 (four is a walk and is not a co…

Baseball's situation (softball and NPB too).

type BasketballLeague

type BasketballLeague = LeagueBase<"basketball", CourtFigures>;

A league with a basketball court of its own.

type Board

interface Board { kind: "springboard" | "platform"; height: number; /** The plummet: x from the pool wall the boards stand at, y across from the top wall. */ x: number; y: number; length: number; width: number; }

One board of a diving well: a springboard or a platform, with its plummet (metres from the wall, then across from the top) and its size.

type Box

interface Box { x: number; y: number; w: number; h: number; }

An axis-aligned box, in feet.

type BudoScore

interface BudoScore { /** Judo, karate: the number of ippon (judo: 1 ends the contest; the IJF's scoreboard writes 100). */ ippon?: number; /** Judo (10 on the scoreboard), karate (2 points). Two waza-ari are ippon in judo. */ wazaari?: number; /** Judo (1 on the scoreboard), karate (1 point). */ yuko?: number; /** Judo: the shido, three of which are hansoku-make. Karate: the warnings (chui), up to three. */ shido?:…

What each team has scored, as the sport counts it. Every part is optional; what is not given is left out of the board.

type BudoSport

type BudoSport = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";

The budō: the contest areas of judo and karate, the kendo shiai-jo, the sumo dohyō, the kyūdōjō, and a dojo of tatami.

type BudoStyle

interface BudoStyle { /** A named look: the colours of the mats and of the floor. Judo: `yellow-red` (the IJF's own, the default), `yellow-blue`, `blue-grey`, `green-beige` (the traditional tatami), `green-red` (green mats with a red danger zone) and `black-red`. Karate: `blue-red` (the default), `blue-black`, `green-beige`. Kendo: `wood` (the default), `blond`. Sumo: `clay`. Kyūdō: `wood`. Dojo: `green-beige` (the …

What a budō drawing takes beyond a surface. Every part is optional: a judo contest area is the IJF's of 10 m with a safety area of 4 m, a karate area the WKF's kumite square, a kendo shiai-jo of 10 m, a dohyō of the Japan Sumo Association and a kyūdōjō of 28 m with five shooters, drawn without any.

function capitals

capitals(text: string): string

The text in capitals when it has Latin letters; any other text is left as it is.

function chooseLeague

chooseLeague(o: Pick<SurfaceOptions, "sport" | "league">): { sport: Sport; league: League | undefined; }

Which league the options ask for: the league, and the sport it is drawn as. No league is the sport's own. A league the sport's own (nba for nba) is the sport as it is drawn without one; one of the football leagues that is a sport of its own (cfl) is that sport. A league of another family, or one that does not exist, is an error that says what to use.

Example

import { chooseLeague } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(chooseLeague({ sport: "nba", league: "fiba" }).league?.name.en, chooseLeague({ sport: "nfl", league: "cfl" }).sport);
// FIBA basketball court cfl

const COLOR_PROPERTIES

COLOR_PROPERTIES: Readonly<Record<keyof Colors, string>>

The CSS custom property each colour of Colors sets.

type Colors

interface Colors { /** The ground round the surface. */ apron?: string; /** The playing surface: turf, ice, wood, outfield grass. */ surface?: string; /** The second tone of mown stripes and of a worn floor. */ surfaceAlt?: string; /** Painted lines. */ line?: string; /** Hockey's blue lines and the centre circle. */ blue?: string; /** Hockey's red lines, creases and face-off circles. */ red?: string; /** The boards…

The colours of the drawing. Each key is a CSS custom property, --ky-<key in kebab case>, set on the svg. Any CSS colour that is a hex colour, rgb(), hsl(), a colour name or var(--name) is drawn; anything else is ignored.

type Comet

interface Comet { /** The opacity at the start, 0 to 1. 0.05 by default. */ from?: number; /** Draw it as a shape that thickens towards the end, not as a stroke of one width. */ taper?: boolean; }

The look of a comet: a line that fades in along its length, and may also thicken.

type ContourLayer

interface ContourLayer extends HeatBase { kind: "contour"; /** The kernel's width, in the layer's units (one number, or one for each axis), or `scott` (the default: Scott's rule, each axis's spread times n^(-1/6)). */ bandwidth?: Bandwidth; /** A multiple of the bandwidth: under 1 follows the data more closely, over 1 smooths it. 1 by default. */ smooth?: number; /** The grid the density is computed on, nodes across…

A kernel density estimate of the marks, drawn as filled bands or lines (hockey_rink's contour and contourf, mplsoccer's kdeplot).

function contrastOf

contrastOf(a: string | undefined, b: string | undefined): number | undefined

The contrast ratio of two colours (WCAG 2.2): 1 to 21, or undefined when a colour cannot be read without a browser.

function convertLength

convertLength(value: number, from: Unit, to: Unit): number

A length in another unit.

Example

import { convertLength } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
console.log(convertLength(100, "yd", "ft"), convertLength(200, "ft", "m").toFixed(2));
// 300 60.96

const COURT_THEMES

COURT_THEMES: Readonly<Record<"night" | "maple" | "twoTone" | "sunburst" | "tournament", CourtStyle>>

Five looks for a basketball court, made of plain colours and no one's marks, to start from: the paint, the border, the wood and the words on the baselines are the parts a court's design is made of. Pass one as court, or spread it and change a part.

Example

import { COURT_THEMES, drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
const svg = drawKyogijo({ sport: "nba", court: { ...COURT_THEMES.twoTone, baselineText: "HARBOUR CITY" } });
console.log(svg.includes("--ky-key:#1e4f8f"));
// true

type CourtFigures

interface CourtFigures { /** Baseline to baseline, inside the lines. */ length: number; /** Sideline to sideline, inside the lines. */ width: number; /** The free-throw lane (restricted area), side to side. */ laneWidth: number; /** A lane that is wider at the baseline than at the free-throw line (a trapezoid): its width at the baseline. */ laneEndWidth?: number; /** The free-throw line, from the baseline. */ freeTh…

The figures of a basketball court. Lengths, in the unit of the table they are in.

type CourtMarks

interface CourtMarks { /** The marks that divide the free-throw lane, from the baseline. The first is the block. */ lane: number[]; /** The lower defensive box's marks: from the lane, from the baseline, and how long; or none. */ box: { fromLane: number; fromBaseline: number; length: number } | null; /** The marks 28 feet from the baseline on the sidelines: where, and how long. */ sideline: { fromBaseline: number; le…

The small marks the NBA's court diagram has along the lane and the sidelines, drawn in the full detail. Lengths.

type CourtStyle

interface CourtStyle { /** The wood: the floor's main tone. */ wood?: string; /** The second tone of the planks. */ woodAlt?: string; /** The ground outside the court. */ apron?: string; /** The ring between the court's lines and the apron, the out-of-bounds border. */ border?: string; /** The paint of both keys. */ key?: string; /** The paint of the left key, over `key`. */ keyLeft?: string; /** The paint of the ri…

The look of a basketball court. Every part is optional, and each falls back to the theme's.

type CoverQuirk

interface CoverQuirk extends QuirkBase { kind: "ivy" | "basket"; from: number; to: number; }

Ivy on the wall, or a basket on top of it, along a stretch of the fence.

type CrowdOptions

interface CrowdOptions { /** * The venue: `bowl` (stands on every side), `sides` (the long sides only), `horseshoe` (three sides, the open end at the right, or at the left with `open: "left"`; a * ballpark's outfield with a gap at the batter's eye) or `grandstand` (a ballpark's infield stands only). Each sport has its usual one. */ shape?: CrowdShape; /** The end of a `horseshoe` that is open: `right` (the usual) or…

The stands and the crowd round a surface (crowd). Every part is optional. true is the sport's usual venue, 85 percent full, with an away end, drawn in detail. See docs/crowd.md.

type CrowdShape

type CrowdShape = "bowl" | "sides" | "horseshoe" | "grandstand";

The shape of a venue's stands round the playing surface.

function crowdStands

crowdStands(o: SurfaceOptions): CrowdStands | undefined

The stands round a surface as data: the rings of seats with their outlines, heights and rake, the blocks of visitors and the places left open, in feet in the surface's wide frame, so that another view (a 3D one) can raise them. undefined when the options ask for no crowd (or for a special game, which has none).

Example

import { crowdStands } from "@johnmorrisdotca/kyogijo-stadiums";
const stands = crowdStands({ sport: "soccer", crowd: true });
console.log(stands?.rings.map((ring) => ring.id).join(" "), stands?.shape, stands?.stadium);
// lower upper bowl purpose

type CrowdStands

interface CrowdStands { sport: Sport; shape: CrowdShape; /** The stadium a soccer pitch stands in: `purpose` (its own), `nfl` or `cfl` (a gridiron's), or nothing for another sport. */ stadium?: "purpose" | "nfl" | "cfl"; /** The playing surface, in the wide frame. */ box: Box; rings: StandRing[]; /** Boxes where the stands are left out (the open end of a horseshoe, the ends of two sides). */ covers: [x0: number, y0:…

The stands round a surface as data.

type DeckLook

type DeckLook = "plain" | "tiled";

How the deck is painted: one colour, or in tiles.

type DelaunayLayer

interface DelaunayLayer extends PlayersBase { kind: "delaunay"; /** One triangulation for each team (`team`, the default) or one for all the players. */ by?: "team" | "all"; /** Fill the triangles, not only draw their edges. */ triangles?: boolean; }

The Delaunay triangulation of the players: triangles whose circle holds no other player, a mesh of near neighbours.

function describe

describe(o: SurfaceOptions | ScoreOptions): { title: string; description: string; }

A drawing's title and description, in a language.

type Detail

type Detail = "full" | "simple";

How much of the surface's markings are drawn. simple leaves out the small ones, such as hash marks.

type DiamondFigures

interface DiamondFigures { /** The distance between bases (the side of the square). */ base: number; /** The side of a base. */ baseSize: number; /** The front of the pitcher's plate, from the back point of home plate. */ pitcher: number; /** The pitcher's plate: how long (across) and how deep. */ pitchersPlate: { width: number; depth: number }; /** The mound's radius, or a softball pitcher's circle's. */ moundRadiu…

The figures of a diamond. Lengths, in the unit of the table they are in.

type DirectionOptions

interface DirectionOptions { /** The end the home team attacks, as the surface is drawn wide: `left` or `right`. In the first period, unless `period` says another. */ home?: Side; /** The end the away team attacks. */ away?: Side; /** * The period the arrows are for. The teams change ends by the sport's rule (hockey after every period, football after the first and the third quarters, * basketball at the half), so `h…

Which way a team attacks, and in which period, for the arrow of direction.

type DivePlan

interface DivePlan extends Common { sport: "diving"; length: number; width: number; boards: Board[]; /** The metres behind the wall that the stands reach, for the view. */ behind: number; }

A diving well to draw.

type DivingPlatform

type DivingPlatform = 1 | 3 | 5 | 7.5 | 10;

The boards of a diving well that can be asked for.

function dojo

dojo(spec?: DojoSpec): Dojo

A dojo (道場): the floor in mats, laid as real tatami units, with the walls, doors, windows, shrine and kamiza. mats is the floor: a number is its area in jō (8, 12, 20, 50, 100 and 200 give the usual shapes), [across, along] the whole mats across and along, { width, length, unit } a size in metres, feet, mats or jō; pattern is how it is laid (traditional: no four corners meet; competition, checker, border).

Example

import { dojo } from "@johnmorrisdotca/kyogijo-stadiums/budo";
const d = dojo({ jo: 20 });
console.log(d.jo, d.width.toFixed(2), d.length.toFixed(2), d.counts.full, d.counts.half, d.pattern);
// 20 7.28 4.55 18 4 traditional

type Dojo

interface Dojo { /** The spec it was made from. */ spec: DojoSpec; /** The mat standard: a league of the `dojo` sport (`chukyoma`, `kyoma`, `edoma`, `danchima`, `judo-2x1`) and the mat's size in metres. */ standard: string; mat: { length: number; width: number }; /** The floor in metres: its width (x, the longer side) and its length (y). */ width: number; length: number; /** The floor's area in jō: the area over the…

A dojo made from a spec: the floor as data.

type DojoSpec

interface DojoSpec { /** * The floor. A number is its area in jō (畳): 8, 12, 20, 50, 100 or 200 give the usual shapes. `[across, along]` is the number of whole mats across the floor and along it * (`[5, 4]` is 20 jō). `{ width, length, unit }` is a size in metres (the default), feet, `mat` (as the array) or `jo`. 20 jō by default. */ mats?: DojoSize; /** The area in jō, as `mats: 20` says it. */ jo?: number; /** `tr…

A dojo (道場): the floor in mats, laid out as real tatami units, and what stands round it. Every part is optional: 20 jō of traditional mats with the kamiza at the north, no walls. dojo() makes its plan, and drawKyogijo({ sport: "dojo", dojo }) draws it.

function drawBadge

drawBadge(options: ScoreOptions): string

Draw a score as one compact strip, for a list. The same as drawScore with size: "badge".

function drawDojo

drawDojo(spec?: DojoSpec, more?: Partial<SurfaceOptions>): string

Draw a dojo: drawKyogijo with the spec as the options' dojo, and any other option the caller gives.

function drawKyogijo

drawKyogijo(o: SurfaceOptions | ScoreOptions): string

Draw a surface, or a score on a surface, as an SVG string. With home and away it is a score; without them, the bare surface. The same options always give the same string.

Example

import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
const svg = drawKyogijo({
  sport: "nhl",
  away: { name: "Harbour City Gulls", abbr: "HCG", score: 3, color: "#1f5fa8" },
  home: { name: "Prairie Ridge Bison", abbr: "PRB", score: 2, color: "#b5452c" },
  status: "final-ot",
});
console.log(svg.startsWith("<svg"), svg.includes("Harbour City Gulls"));
// true true

function drawLayers

drawLayers(options: SurfaceOptions | ScoreOptions, layers: readonly Layer[]): string

The layers of a drawing as one SVG group, in the surface's own frame, for a caller who composes the drawing's parts. Put it inside the surface's group (the one with the quarter turn, when the drawing is tall) and the marks lie on the surface, cut to its outline, with the text still upright. The same options and layers always give the same string; drawKyogijo draws exactly this when it is given layers.

Example

import { drawLayers } from "@johnmorrisdotca/kyogijo-stadiums";
const group = drawLayers({ sport: "nhl", id: "rink" }, [
  { kind: "points", name: "shots", data: [{ x: 169, y: 20.5, outcome: "goal", team: "home" }, { x: 150, y: 40, outcome: "shot", team: "home" }] },
]);
console.log(group.startsWith('<g class="ky-layers"'), group.includes("2 shots"));
// true true

function drawScore

drawScore(options: ScoreOptions): string

Draw a score on a surface.

function drawSurface

drawSurface(options: SurfaceOptions): string

Draw a bare surface. The same as drawKyogijo without teams.

const DRIVE_CODES

DRIVE_CODES: Readonly<Record<DriveResult, string>>

The short code each drive result is written with at the head of its arrow.

type DriveLeg

interface DriveLeg { /** The team that had the ball. The team in possession when missing. */ team?: SituationTeam; /** Yards from the team's own goal line where it began. */ from: number; /** Yards from the team's own goal line where it ended. */ to: number; /** How it ended. */ result?: DriveResult; }

One drive of a drive chart: where it started and where it ended, in yards from the team's own goal line (0 the goal line it defends, 100 the one it attacks; 110 on the CFL's field), and how it ended.

type DriveRead

interface DriveRead { team: SituationTeam; from: number; to: number; result?: DriveResult; toward: "left" | "right"; }

A drive, read: in yards from the team's own goal line, and the end it heads for.

type DriveResult

type DriveResult = "touchdown" | "field-goal" | "missed-field-goal" | "punt" | "turnover" | "interception" | "fumble" | "downs" | "safety" | "end-of-half";

How a drive of a football game ended.

type DugoutSide

type DugoutSide = "first" | "third";

The sides of the field a dugout is on: along the first base line or the third.

type EmblemDrawOptions

interface EmblemDrawOptions { style?: EmblemStyleName; /** The colour of the emblem's main part: a crest takes the team's colour. */ tint?: string; /** A second colour: a crest's band. */ accent?: string; /** The letters of a crest, up to three. */ initials?: string; /** `inherit`, `auto` (by the device's setting), `light` or `dark`. */ theme?: "inherit" | "auto" | "light" | "dark"; size?: number; }

What kyogijo asks of a renderer. It is a subset of dougu-sports' EmblemOptions.

type EmblemId

type EmblemId = Known<DouguId>;

The ids of dougu-sports' emblems ("trophy:cup", "crest:shield"), when that package is installed, so that an editor completes them; any other string otherwise. An id may end in /metal, /line or /color, and a trophy or an award may be written without its group ("cup"). Nothing is imported at run time.

type EmblemRef

type EmblemRef = EmblemId | `${EmblemId}/${EmblemStyleName}` | (string & {});

An emblem as a logo, a mark or a pageant's emblem is given: its id, with or without a style, or any string (an address, a data address or an SVG document).

type EmblemRenderer

type EmblemRenderer = (id: string, options?: EmblemDrawOptions) => string;

A function that draws an emblem from its id as a document of SVG, and throws for an id it does not know: renderEmblem of @johnmorrisdotca/dougu-sports/emblems is one. The id may end in /metal, /line or /color, and the renderer reads that suffix.

type EmblemStyleName

type EmblemStyleName = "metal" | "line" | "color";

The styles an emblem is drawn in: full colour with metallic gradients, one outline in the page's ink, or flat fills.

const ERA_CURRENT

ERA_CURRENT: 2026

The season whose rules are "today's", and the one a drawing without a year shows: the data were last checked in 2026. A change scheduled for a later season is planned and is drawn only when that season is asked for.

const ERA_SOURCES

ERA_SOURCES: Readonly<Record<string, EraSource>>

Every page a change was read from, by id. Wikipedia is cited by revision, for facts only.

const ERA_SPORTS

ERA_SPORTS: readonly EraSport[]

The sports with a history, in the order a menu lists them.

type EraChange

interface EraChange { /** `nhl.trapezoid.2005`. */ id: string; sport: EraSport; /** The marking's name: a key of the sport's rules. */ feature: string; /** The first season, or `null` for the state the sport started with. */ from: number | null; value: unknown; what: string; ja: string; sources: string[]; planned: boolean; approx: boolean; }

A change in the markings: one span of one marking that started in some season.

function eraChanges

eraChanges(sport: EraSport): EraChange[]

Every change of a sport's markings, in season order: what moved, from which season, in a sentence in English and in Japanese, and the sources that say so. The state a sport started with is not a change and is left out (see eraStates).

Example

import { eraChanges } from "@johnmorrisdotca/kyogijo-stadiums/eras";
const nhl = eraChanges("nhl");
console.log(nhl.length, nhl[0].id, nhl[0].from);
// 12 nhl.centreLine.1943 1943

function eraRange

eraRange(sport: EraSport): { from: number; to: number; }

The seasons a sport's history is drawn for: from the league's first to today's, or to a planned season when one is announced.

type EraRules

interface EraRules<S extends EraSport = EraSport> { sport: S; /** The season asked for (the year it starts in); today's when none was. */ year: number; /** Its name: `1979–80`. */ season: string; /** The markings. Every one is in feet, except the mound's height in inches. */ rules: EraRulesOf[S]; /** The states in force that are not today's, each as the change that began it (a marking in `gaps` is not listed). */ di…

The markings in force in a season, with what differs from today's and what is not known.

type EraRulesOf

interface EraRulesOf { nhl: NhlRules; nba: NbaRules; nfl: FootballRules; cfl: FootballRules; mlb: MlbRules; }

The rules of a sport in a season.

type EraSource

interface EraSource { id: string; /** The page: the article's title for Wikipedia, else the page's own. */ title: string; /** Who publishes it. */ by: string; /** For Wikipedia (CC BY-SA 4.0): the revision of the article, so that the figure can be checked. */ revision?: number; url: string; /** What was taken from it. */ note?: string; }

Where a change was read.

type EraSpan

interface EraSpan<T> { /** The first season it was in force, or `null` for "from the start" (the state the earliest season of the sport had). */ from: number | null; value: T; /** What it was, in a sentence. */ what: string; /** The same in Japanese. */ ja: string; /** Keys of `ERA_SOURCES`; at least one. */ sources: string[]; /** A rule announced for a season that has not been played yet. It is drawn only when that…

One state of a marking, from a season on.

type EraSport

type EraSport = "nfl" | "cfl" | "nhl" | "nba" | "mlb";

The sports whose markings have a history here. The IFAF's surface is 2021's and has none.

function eraStates

eraStates(sport: EraSport): EraChange[]

The state each marking of a sport started with: the spans that begin "from the start".

type Family

type Family = "basketball" | "hockey" | "football" | "baseball" | "curling" | "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball" | "lacrosse" | "rugby" | "afl" | "gaelic" | "cricket" | "fieldhockey" | "handball" | "athletics" | "skating" | "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";

The families of surface a league is a variant of.

const FAMILY_OF

FAMILY_OF: Readonly<Record<Sport, Family | undefined>>

The family of surface each sport draws; soccer's variants are its pitches (pitch), not leagues.

type Feature

interface Feature { /** What a caller names it by: `blue-lines`. */ name: string; /** * What its colour is given to: the custom properties the part is painted with (`--ky-red`), or a CSS property (`stroke`) for a part the stylesheet paints in a * colour that is not a property. */ props: readonly string[]; /** What it is, for the docs. */ about: string; }

A part of a surface that can be coloured on its own.

const FEATURE_INFO

FEATURE_INFO: Readonly<Record<string, readonly Feature[]>>

The features of each sport, with what each colours. A sport whose surface has none named (one added since) is missing here.

const FEATURES

FEATURES: Readonly<Record<string, readonly string[]>>

The names of the features of each sport: the keys featureColors takes. Football's three fields have the same, and so have the five net sports.

Example

import { FEATURES } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(FEATURES.nhl.includes("blue-lines"), FEATURES.nfl.length);
// true 12

type Fence

interface Fence { left: number; leftCenter: number; center: number; rightCenter: number; right: number; }

The distances to a ballpark's fence in feet, at the foul lines, between and in the middle.

function fenceFor

fenceFor(park: Park | undefined, fence: Partial<Fence> | undefined, year?: number, generic?: readonly FencePoint[]): FencePoint[]

The fence points a drawing uses: a park's (as it stood in year, when the park has dated fences), with any of the five usual distances in fence laid over them, or the usual 330, 375, 400, 375 and 330 feet of a generic field (or the generic points a league's rules give).

const FIELD_SPORTS

FIELD_SPORTS: readonly FieldSport[]

The field sports, in the order a menu lists them.

type FieldArea

interface Area extends Named { cls: "ky-area" | "ky-zone" | "ky-dirt" | "ky-key" | "ky-boards" | AthleticsArea | SkatingArea | BudoArea; /** Fill the path's insides by the even-odd rule: a ring (the track between its kerb and its outer edge). */ evenodd?: boolean; /** A rectangle `[x0, y0, x1, y1]`, an ellipse's centre and radii, a path of your own, or a frame: the ring between a rounded rectangle and the one `thick…

A painted area, under the lines: a rectangle or an ellipse filled with a class of the stylesheet (ky-area, the tint of a goal area, or ky-zone).

type FieldGoalPlan

interface GoalPlan { end: "left" | "right"; x: number; y: number; width: number; depth: number; out: 1 | -1; }

One goal: where the middle of its mouth stands on the goal line, how wide it is, how deep, and which way it opens' back is (out: -1 behind a left goal line).

type FieldOutline

type Outline = { kind: "rect"; radius?: number } | { kind: "ellipse" } | { kind: "oval"; a: number; half: number };

The shape of the playing surface itself: a rectangle (its corners rounded, as a floor's are), an ellipse (a cricket ground), or an oval cut at each end by a straight line, as an Australian football ground is: the ellipse of semi-axes a along the ground and half the width across, cut where it is half either side of the middle.

function fieldPlan

fieldPlan(o: Pick<SurfaceOptions, "sport" | "league" | "field" | "units" | "detail">): FieldPlan

The plan the options ask for. A league picks the rulebook (the sport's own first one by default); field a size within its range and the sport's own parameters; detail: "simple" leaves out the small marks.

type FieldPlan

interface FieldPlan { sport: FieldSport; /** The id of the league (rulebook) it follows. */ league: string; /** The length and the width of the playing surface in feet, as the box the drawing is laid out in (a rugby field's includes the in-goal areas). */ length: number; width: number; outline: Outline; /** The ground round the surface by default, in feet. */ margin: number; /** The width the ordinary line is painte…

What a builder of a sport knows of its surface.

type FieldShape

type Shape = LineShape | ArcShape | RingShape | BoxShape | PolyShape | DotShape | BarShape | PathShape;

Every shape that is a marking.

type FieldSport

type FieldSport = "lacrosse" | "rugby" | "afl" | "gaelic" | "cricket" | "fieldhockey" | "handball";

The field sports: lacrosse (the field and the box), rugby (union and league), Australian football, Gaelic football and hurling, cricket, field hockey and team handball.

type FieldStyle

interface FieldStyle { /** The length of the surface (rugby: try line to try line; Australian football: goal line to goal line; Gaelic games, hockey, handball, lacrosse: end line to end line) and its width, in `units`: within the league's range; a size outside it is an error that says the range. The greatest the rules allow by default. */ length?: number; width?: number; /** Rugby: how deep each in-goal area is, wit…

The size and the variants of a field sport's surface (lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey, handball): a size within the range the league's rulebook allows (length, width, in units, feet by default), and the sport's own parameters. Every part is optional.

function fitLines

fitLines(name: string, maxWidth: number, maxHeight: number, maxSize: number): FittedLines

Set a name in the biggest size that fits a box, on one line or, at a space, on two.

Example

import { fitLines } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(fitLines("GOLDEN STATE WARRIORS", 40, 12, 8).lines);
// [ 'GOLDEN STATE', 'WARRIORS' ]

type FittedLines

interface FittedLines { lines: string[]; size: number; width: number; height: number; }

The lines a name is set in, the font size that fits them, and the width of the widest.

type FlowLayer

interface FlowLayer extends LayerBase { kind: "flow"; data: readonly MoveDatum[]; /** The grid: a number of cells across the surface's longer side (square cells, 12 by default), `[across, down]`, or `{ x, y }` as for a heat map. */ bins?: number | readonly [number, number]; /** Leave out a cell with fewer movements than this. 2 by default. */ minCount?: number; /** The length of an arrow: `mean` (the default: in pro…

The average direction of the movements in each cell of a grid, as an arrow (a quiver plot).

function footballIn

footballIn(sport: "nfl" | "cfl" | "ifaf", year?: number): FootballSpec

A football field's spec in a season: the NFL's or the CFL's as FOOTBALL has it today, with the length, the end zones, the hash marks and the goal posts of that season. The IFAF's has no history and is returned as it is.

Example

import { footballIn } from "@johnmorrisdotca/kyogijo-stadiums/eras";
console.log(footballIn("cfl", 1985).endZone / 3, footballIn("cfl", 1986).endZone / 3, footballIn("nfl", 1960).hash / 3);
// 25 20 20

type FootballLeague

type FootballLeague = LeagueBase<"football", FootballFigures>;

A league with a football field of its own.

type FootballRead

interface FootballRead { kind: "football"; yards: number; downs: number; ballOn?: number; toGo?: number | "goal"; down?: number; possession?: SituationTeam; /** The end zone the offence heads for. */ toward: "left" | "right"; redZone: boolean; drive: DriveRead[]; }

Football, read.

type FootballRules

interface FootballRules { /** Goal line to goal line. */ length: number; /** The depth of each end zone. */ endZone: number; /** The hash marks' distance from each sideline, or `null` before there were hash marks. */ hash: number | null; /** Where the goal posts stand. */ posts: "goal-line" | "end-line"; /** Two poles joined by a crossbar (`h`), or one curved support behind the crossbar (`slingshot`). */ postShape: …

A football field's markings in a season: the NFL's or the CFL's. Lengths are feet.

type FootballSituation

interface FootballSituation { /** The ball, in yards from the offence's own goal line: 35 is its own 35, 88 the opponent's 12. 0 to 100 (0 to 110 on the CFL's field). */ ballOn?: number; /** The yards to a first down, or `"goal"` when the goal line is nearer than the first-down line. */ toGo?: number | "goal"; /** The down, 1 to 4 (1 to 3 on the CFL's field). */ down?: number; /** The team with the ball. */ possessi…

Football's situation (NFL, CFL, IFAF and the leagues of other rulebooks). The CFL has three downs and a field of 110 yards with end zones of 20.

type Formation

interface Formation { id: string; sport: FormationSport; /** The frame the positions are in: `opta` for soccer, `nhl-api`, `nba-stats`, `nfl-tracking`, `retrosheet`. */ frame: string; name: { en: string; ja: string }; /** What the formation is, in a sentence: the side of the ball, the strategy. */ about: { en: string; ja: string }; positions: readonly FormationPosition[]; /** Places beside the surface's play that a …

A formation: where each position stands. Points in the numbers of a registered frame, for a team that attacks the right end of the surface as it is drawn wide (a football formation: the offence going right, with the ball at midfield).

type FormationLayer

interface FormationLayer extends LayerBase { kind: "formation"; /** The id of a formation in `FORMATIONS` (`4-4-2`), or a formation of your own. */ formation: string | Formation; /** The team that colours the marks (`home`, `away`). */ team?: LayerTeam; /** Players for some or all of the positions. */ data?: readonly FormationPlayer[]; /** What is written in a mark: the `position`'s id (the default without players),…

A formation drawn on the surface: a labelled mark for each position, with the players on them when data has some.

type FormationPlayer

interface FormationPlayer extends MarkDatum { /** The id of the position the player stands on. */ position: string; number?: string | number; name?: string; }

A player to stand on a position.

type FormationPosition

interface FormationPosition { /** The position's id: `GK`, `LB`, `QB`. Unique within a formation. */ id: string; x: number; y: number; /** How big the mark of a lineup is here, against the others: 1 by default. A second line's marks are smaller. */ size?: number; }

A named place of a formation, in the numbers of the formation's frame (see Formation).

type FormationSport

type FormationSport = "soccer" | "nhl" | "nba" | "nfl" | "mlb" | "rugby" | "afl" | "cricket" | "volleyball" | "handball" | "waterpolo" | "fieldhockey" | "netball";

The sports that have formations.

function formatTime

formatTime(seconds: number): string

A time in seconds as swimming writes it, to the hundredth: 26.12, 1:46.91, 14:31.02. A time that rounds to the next minute carries into it.

Example

import { formatTime } from "@johnmorrisdotca/kyogijo-stadiums/replay";
console.log(formatTime(26.121), formatTime(106.9), formatTime(59.996));
// 26.12 1:46.90 1:00.00

function frameMatrix

frameMatrix(f: SoccerFrame, spec: Pick<SoccerSpec, "length" | "width">): [number, number, number, number, number, number]

The matrix [a, b, c, d, e, f] of an SVG transform that turns a provider's numbers into metres on a pitch of this size, the left goal line at x 0 and the top touchline at y 0. A group with this transform takes the provider's raw x and y: that is the ky-data group in a drawing made with pitchType.

type GoalInfo

interface GoalInfo { sport: Sport; /** The end the goal is at: `left` or `right`, as the surface is drawn wide. */ end: Side; /** The middle of the goal's mouth on the goal line (the base of a basket's backboard). */ x: number; y: number; /** The width of the mouth, across the surface. */ width: number; /** How far the goal reaches from `x` (a net's depth behind the goal line, a basket's arm and rim out from its bac…

Where a goal stands, for a function of your own that draws it (goalMarkup) and for goalMarkup's built-in styles. Every number is in feet, in the drawing's own wide frame (x along the surface from the left, y across it from the top), whatever units the options use.

type GoalStyle

type GoalStyle = "box" | "line" | "strip" | "none";

How a goal is drawn, seen from above. A soccer goal: the net as a box behind the goal line, a line on it, a solid strip, or not at all. A hockey net: the same four (box is the open frame behind the goal line, strip a filled net). A basketball basket: box is the backboard, its arm and the rim, line the backboard only, strip a solid backboard and a filled rim, none neither. A football field's posts: box is the crossbar with its stem, line the crossbar alone, strip bolder posts, none no posts.

const GRIDIRON_GROUNDS

GRIDIRON_GROUNDS: Readonly<Record<string, "nfl" | "cfl">>

The grounds of the package that are the home of an NFL or a CFL club, where a soccer pitch stands inside a gridiron's stands. From each ground's article.

type Ground

interface Ground extends Omit<GroundData, "pitch"> { /** The pitch, goal line to goal line and touchline to touchline, in metres to the centimetre. */ pitch: { length: number; width: number }; /** The unit the article published the size in; `m` for a ground at the default size. */ pitchUnit: "m" | "yd"; }

A ground, with its pitch in metres: the published size, or the international 105 by 68 m of a ground that has none.

const GROUND_IDS

GROUND_IDS: readonly string[]

The keys of GROUNDS: the men's World Cup final venues in order of the tournaments, the other venues of 2026, the Women's final venues not among them, then club grounds.

function groundById

groundById: (id: string | undefined) => Ground | undefined

The ground with a key, or undefined.

function groundByName

groundByName: (name: string | undefined) => Ground | undefined

The ground a name belongs to (see groundsByName; the best match when several answer to it), or undefined.

function groundByWikidata

groundByWikidata: (wikidata: string | undefined) => Ground | undefined

The ground whose Wikidata item is Q155174, or undefined.

type GroundCompetition

type GroundCompetition = "world-cup" | "womens-world-cup";

The competitions a ground can have staged: the men's World Cup and the Women's.

type GroundData

interface GroundData { /** The key: `maracana`. */ id: string; /** The Wikidata item of the ground (`Q155174`), the key to join a ground to other data. */ wikidata: string; /** The everyday name in English, the one English sports media use (`Reliant Stadium`, `San Siro`); the title of the English article is one of the `aliases`. */ name: string; /** * Names the ground had for some years, in order of time: where two …

A soccer ground as the data gives it: the pitch is the size the article publishes, in the unit it publishes it in.

type GroundName

interface GroundName { name: string; from: number; to: number; }

A name a ground had for some years, first to last.

function groundNameIn

groundNameIn(ground: Ground, year?: number, language?: Language): string

A ground's name in a year: the name it had then, or its everyday name, in English. In Japanese there is one name for every year.

Example

import { GROUNDS, groundNameIn } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
console.log(groundNameIn(GROUNDS["soccer-city"], 2010), "/", groundNameIn(GROUNDS["soccer-city"], 2012));
// Soccer City / FNB Stadium

function groundOfficialName

groundOfficialName(ground: Ground, language?: Language, year?: number): string | undefined

The name a ground officially has in a language, when it is not the everyday name (Parc Olympique Lyonnais for Groupama Stadium), in the year given or today; else undefined.

const GROUNDS

GROUNDS: Readonly<Record<string, Ground>>

Every ground by its key: the venues of the men's World Cup finals and of the 2026 tournament, of the Women's World Cup finals, and well-known club grounds, each with its pitch at its own size where the English article publishes one.

Example

import { GROUNDS, GROUND_IDS } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
const { name, city, pitch, pitchSize } = GROUNDS["old-trafford"];
console.log(name, city, pitch.length, pitch.width, pitchSize, GROUND_IDS.length);
// Old Trafford Manchester 105 68 published 64

function groundsByName

groundsByName: (name: string | undefined) => Ground[]

The grounds that answer to a name, best match first: the everyday names, then the official and cited names, the sponsors' and the names the ground had in some years, then the former names and the nicknames. A name is compared without case, accents, spaces or punctuation, in full-width or half-width, and in Japanese with hiragana or katakana and with or without the long vowel mark.

Example

import { groundByName, groundsByName } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
console.log(groundByName("Giuseppe Meazza Stadium")?.id, groundByName("日産スタジアム")?.id, groundByName("Estadio Jornalista Mario Filho")?.id, groundsByName("stadium australia").length);
// san-siro nissan-stadium-yokohama maracana 1

function groundsHosting

groundsHosting: (competition: GroundCompetition, year: number) => Ground[]

The grounds of this list that staged matches of a World Cup, in the order of GROUND_IDS.

type GroundSource

interface GroundSource { title: string; revision: number; }

Where a ground's facts were read: an article of Wikipedia at a revision.

function groundSourceUrl

groundSourceUrl(source: GroundSource, language?: "en" | "ja"): string

The address of the revision of an article that a ground's facts were read from.

type GroundTournament

interface GroundTournament { competition: GroundCompetition; year: number; /** The final was played here. */ final?: true; }

A tournament a ground staged matches of.

function groupOf

groupOf: (sport: Sport | string) => Group | undefined

The group a sport is painted with, or undefined for a surface with no palette of its own (it takes the look's base).

const HARDWOOD_SMALL

HARDWOOD_SMALL: 480

The width in pixels under which a drawing is a thumbnail, and its boards a blur: the auto detail is plain below it.

type HardwoodOptions

interface HardwoodOptions { /** The seed of the boards' lengths and tones and of the grain: the same seed always gives the same floor. 1 by default. */ seed?: number; /** * How much is drawn. `fine`: the boards' seams and ends, the grain, the baskets' soft shadows, the table and the benches. `plain`: broad boards and one shadow, for a * thumbnail. `auto` (the default) is `plain` when the drawing has `detail: "simple…

The options of a hardwood floor. Every part is optional.

function hasHistory

hasHistory: (league: League | undefined) => boolean

True when a league has markings a source dates, so that year changes its drawing.

const HEAT_RAMP_NAMES

HEAT_RAMP_NAMES: readonly HeatRampName[]

Names of the built-in ramps.

const HEAT_RAMPS

HEAT_RAMPS: Readonly<Record<HeatRampName, HeatRampDef>>

The built-in ramps. On a light page a sequential ramp runs from a pale colour to a dark one, because the more there is the more ink it takes; on a dark page it runs from near the ground to a bright one, because the more there is the more light it gives. A diverging ramp has its neutral middle at the ground's colour and its two ends at full strength.

type HeatBase

interface HeatBase extends LayerBase { data: readonly HeatDatum[]; /** What each cell or zone shows: `count` (the default), `sum`, `mean`, `median`, `max` or `min` of the marks' `value`, `percent` (a share of all the marks, 0 to 100), `rate` (the percent of the marks whose `outcome` is a success; see `success`) or a function `(values, { count, hits, total }) => number | null`. */ statistic?: HeatStatistic; /** The s…

What the heat layers take beyond a layer's places.

type HeatBins

interface HeatBins { /** A number of bins across, or a list of edges in the layer's coordinates. */ x?: number | readonly number[]; y?: number | readonly number[]; }

Bins on a grid: a number of bins, or the edges between them.

type HeatDatum

interface HeatDatum extends MarkDatum { x: number | null; y: number | null; }

A point to count: where it is, and optionally a number it carries (value: a distance, an expected-goals figure, a speed) and an outcome (made, goal) for the rate statistic.

type HeatLabels

interface HeatLabels { /** What to write, a line each: the `value`, the `name` of a zone, the `count` of marks in it, its `share` of all of them (a percent) or the `ratio` of hits to marks (`13/31`). The value by default. */ show?: readonly ("value" | "name" | "count" | "share" | "ratio")[]; /** Decimal places of the value. A whole number for a count, and one place for a percent below 10, by default. */ decimals?: n…

Where to write the number of a cell or zone, and which.

type HeatLegend

interface HeatLegend { /** Where it sits on the surface: `bottom` (the middle of the lower edge) by default, or a corner. A ballpark's default is its lower right, outside the foul lines. */ position?: "bottom" | "top" | "bottom-left" | "bottom-right" | "top-left" | "top-right"; /** The legend's heading. The name of the statistic by default. */ title?: string; /** About how many ticks. 5 by default. */ ticks?: number…

A colour bar with ticks, drawn on the surface.

type HeatmapLayer

interface HeatmapLayer extends HeatBase { kind: "heatmap"; /** The bins. A number is how many across the surface's longer side, with square cells; `[across, down]` for both; or `{ x, y }` each a count or edges. About 24 across by default. */ bins?: number | readonly [number, number] | HeatBins; /** Grid only part of the surface, in the layer's coordinates: the attacking half, say. The whole surface by default. */ ex…

A binned statistic on a grid laid over the surface (hockey_rink's heatmap, mplsoccer's bin_statistic with heatmap).

type HeatRamp

type HeatRamp = HeatRampName | readonly string[] | { light: readonly string[]; dark?: readonly string[] };

A ramp for a layer: the name of a built-in one, a list of colour stops (the same on a light and a dark page), or a light and a dark list. Stops are anything safeColour accepts.

type HeatRampName

type HeatRampName = "ember" | "ocean" | "mono" | "balance" | "tide";

The built-in ramps. The sequential ones go from the least to the most; the diverging ones from the lowest through a neutral middle to the highest.

type HeatStatistic

type HeatStatistic = StatisticName | Reducer;

A statistic: a name, or a function of a bin's values (the median is the median of them; a function gets them all).

type HexbinLayer

interface HexbinLayer extends HeatBase { kind: "hexbin"; /** The distance from a hexagon's centre to its corner, in the layer's units. A twenty-eighth of the surface's length by default. */ size?: number; /** A corner up (`pointy`, the default) or an edge up (`flat`). */ orientation?: HexOrientation; }

A binned statistic on hexagons (hockey_rink's and mplsoccer's hexbin).

type HexOrientation

type HexOrientation = "pointy" | "flat";

How a hexagon sits: a corner up (pointy) or an edge up (flat).

type HillQuirk

interface HillQuirk extends QuirkBase { kind: "hill"; angle: number; feet: number; width_ft: number; slope_deg?: number; }

An incline in the field: against the fence at an angle and a distance, as wide as the article says (width_ft), and as steep as it says.

type HockeyLeague

type HockeyLeague = LeagueBase<"hockey", RinkFigures>;

A league with an ice rink of its own.

type HockeyRead

interface HockeyRead { kind: "hockey"; /** The skaters, when known. */ away?: number; home?: number; /** The team that is attacking (more skaters, or the goalkeeper pulled, or the other team of a delayed penalty), and the end it heads for. */ attacker?: SituationTeam; toward?: "left" | "right"; /** The attacker's skaters and the other team's, in that order, when both are known. */ strength?: [number, number]; /** Mo…

Hockey, read.

type HockeySituation

interface HockeySituation { /** The visitors' skaters on the ice, a whole number from 3 to 6. */ awaySkaters?: number; /** The home team's skaters on the ice. */ homeSkaters?: number; /** The time left on the penalty (or the power play), as `m:ss` or `m:ss.s`, or a whole number of seconds. Written beside the strength. */ clock?: string | number; /** A net with no goalkeeper in it: `"home"` or `"away"` for the team t…

Hockey's situation. The skaters are the players on the ice without the goalkeeper, 3 to 6 (a goalkeeper pulled for an extra attacker is a team with 6). A strength that is 5 against 5 with nothing else to say draws nothing. See docs/situation.md.

const HOME_FIRST

HOME_FIRST: Readonly<Record<Sport, boolean>>

Whether each sport lists the home team first by default. true puts the home team on the left, false on the right. A league can differ from its sport (see HOME_FIRST_LEAGUES); an explicit homeSide always wins over both.

const HOME_FIRST_LEAGUES

HOME_FIRST_LEAGUES: Readonly<Partial<Record<Sport, Readonly<Record<string, boolean>>>>>

The leagues whose convention is not their sport's: the international federation's lacrosse lists the home team first.

function homeSideFor

homeSideFor(sport: Sport, league?: string): Side

The side the home team is on by default for a sport, and a league of it when it has a convention of its own: left (home first) or right (away first).

type HullLayer

interface HullLayer extends PlayersBase { kind: "hull"; /** One hull for each team (`team`, the default) or one for all the players. */ by?: "team" | "all"; }

The convex hull of the players: the smallest convex shape that holds them, for each team.

function inkOn

inkOn(color: string | undefined): string

White or near-black, whichever reads better on the colour. White when the colour cannot be read.

function isFieldSport

isFieldSport: (sport: string) => sport is FieldSport

True for a sport whose surface is a field sport's.

function isPoolSport

isPoolSport: (sport: unknown) => sport is PoolSport

True for a sport whose surface is a pool.

function isPortrait

isPortrait(o: Pick<SurfaceOptions, "orientation" | "aspect"> & { sport?: SurfaceOptions["sport"]; }): boolean

True when a drawing with these options lies in portrait.

function isUnit

isUnit: (unit: unknown) => unit is Unit

True when unit is one of the six.

const KYOGIJO_ATHLETICS_STYLE

KYOGIJO_ATHLETICS_STYLE: string

The rules of an athletics drawing's colours. A drawing that names its track (athletics.track) has its data-track, whose rules come after a look's so that the track wins.

const KYOGIJO_BACKGROUND_STYLE

KYOGIJO_BACKGROUND_STYLE: string

The rules of a ground, written into a drawing that has one (and in KYOGIJO_STYLE, for a page that supplies the stylesheet): the pattern's ink and light.

const KYOGIJO_BUDO_STYLE

KYOGIJO_BUDO_STYLE: string

Every budō sport's rules and every look, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYOGIJO_COURTS_STYLE

KYOGIJO_COURTS_STYLE: string

Every net sport's rules and every look, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYOGIJO_CROWD_STYLE

KYOGIJO_CROWD_STYLE: string

The rules of the stands and the crowd, written into a drawing that has one (and in KYOGIJO_STYLE, for a page that supplies the stylesheet), light and dark.

const KYOGIJO_CURLING_STYLE

KYOGIJO_CURLING_STYLE: string

The rules of a curling sheet and its board: the ice and the lines, the house in its colours (--ky-rg-o, -m, -i and -b for the 12-foot band, the 8-foot, the 4-foot and the button), the hacks, the free guard zone, the pebble, the hammer and the board's grid. Kept apart so that a drawing of another sport does not carry them: a curling drawing writes them in after the base rules, and KYOGIJO_STYLE has them for a page.

const KYOGIJO_FEATURE_STYLE

KYOGIJO_FEATURE_STYLE: string

The rules of every named feature: a colour in --ky-f-<name> on the drawing goes to the part. A drawing that colours a feature carries the rules it needs; a page that draws with style: "none" puts this in once.

const KYOGIJO_FIELDS_STYLE

KYOGIJO_FIELDS_STYLE: string

The rules of every field sport's colours, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYOGIJO_HARDWOOD_STYLE

KYOGIJO_HARDWOOD_STYLE: string

The rules of the hardwood floor's classes, written into a drawing that has one (and in KYOGIJO_STYLE, for a page that supplies the stylesheet).

const KYOGIJO_HEAT_STYLE

KYOGIJO_HEAT_STYLE: string

The rules of the heat layers (the ground under a heat map, its zones' and bands' outlines and the legend), kept apart so that a drawing without one does not carry them: a drawing that has a heat layer writes them in beside the layer, and KYOGIJO_STYLE has them for a page.

const KYOGIJO_LOOK_STYLE

KYOGIJO_LOOK_STYLE: string

The stylesheet of every look, for a page that draws with style: "none".

const KYOGIJO_MARK_STYLE

KYOGIJO_MARK_STYLE: string

The rules of a round team mark: the disc under the picture (the team's colour, or a plain one), the white ring and the hairline outside it, and the initials. The colours are custom properties with fallbacks (--ky-mark-ring, --ky-mark-edge, --ky-mark-plain), so a page can set them. A drawing with a round mark writes these rules in; KYOGIJO_STYLE has them for a page that draws with style: "none".

const KYOGIJO_NOTE_STYLE

KYOGIJO_NOTE_STYLE: string

The rules of the annotations (the arrows of direction, the axes of axes and the labels and leaders of landmarks): words in the ink of the page with an outline in the apron's colour, thin leaders, and the arrows in the home and away colours. A drawing that has an annotation writes them in; KYOGIJO_STYLE has them for a page.

const KYOGIJO_PAGEANT_MOTION

KYOGIJO_PAGEANT_MOTION: string

The entrance (animate: "unfurl" or "fade"), in CSS only, and only for a reader who has not asked for less motion: a carpet rolls out from its own end, a stage and the emblem grow, and fade brings each in. The end state is the drawing's own, so a reader who has asked for less sees it at once, and a renderer that reads no CSS animation (the canvas, the PNG) draws it finished. The second team's picture and then the emblem come a little later.

const KYOGIJO_PAGEANT_STYLE

KYOGIJO_PAGEANT_STYLE: string

The pageant's colours, light and dark.

const KYOGIJO_POOL_STYLE

KYOGIJO_POOL_STYLE: string

The rules of every pool, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYOGIJO_SITUATION_STYLE

KYOGIJO_SITUATION_STYLE: string

The situation's colours and shapes.

const KYOGIJO_SKATING_STYLE

KYOGIJO_SKATING_STYLE: string

The rules of a skating drawing's colours.

const KYOGIJO_STRINGS

KYOGIJO_STRINGS: Record<Language, KyogijoWords>

The words of the package, by language.

const KYOGIJO_STYLE

KYOGIJO_STYLE: string

Every rule of the package, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYOGIJO_TEAM_STYLE

KYOGIJO_TEAM_STYLE: string

The rules of the team layers (the cells, triangles and hulls, the roses of a sonar, the arrows of a flow field and the marks of a formation), kept apart like the heat layers' so that a drawing without one does not carry them: a drawing that has one writes them in beside the layer, and KYOGIJO_STYLE has them for a page.

const KYOGIJO_TRACK_STYLE

KYOGIJO_TRACK_STYLE: string

The rules of every named track, for a page that draws with style: "none".

const KYOGIJO_WALLS_STYLE

KYOGIJO_WALLS_STYLE: string

The rules of a ballpark's walls and quirks (walls and quirks): the four shades of the wall bands (--ky-wall-1 to -4, from under 10 feet to 30 and over), the hill, the overhang, the ivy, the poles and bullpens, the backstop's bar, the dugouts, the names and the legend. Kept apart so that a drawing without them does not carry them: a drawing that has them writes them in after the base rules, and KYOGIJO_STYLE has them for a page.

type KyogijoWords

interface KyogijoWords { /** The name of each surface. */ surface: Record<Sport, string>; /** What each surface is, for the description: a sentence fragment. */ about: Record<Sport, string>; /** The standard statuses. */ status: Record<"final" | "final-ot" | "final-so" | "final-aet" | "final-pens" | "live" | "half" | "extra-time" | "extra-end" | "penalties" | "postponed" | "tiebreak" | "match-tiebreak" | "retired" |…

The words the drawing says itself, in English and Japanese.

type LanePlan

interface LanePlan { number: number; y: number; top: number; bottom: number; }

A lane: its number, the middle of its marking on the floor, and its two edges, in metres from the top wall.

type Language

type Language = "en" | "ja";

The language of the words the drawing says itself: its title, its description and its status labels.

type Layer

type Layer = PointsLayer | LinesLayer | ArrowsLayer | TextLayer | HeatmapLayer | HexbinLayer | ZonesLayer | ContourLayer | LegendLayer | NetworkLayer | FormationLayer | LineupLayer | VoronoiLayer | DelaunayLayer | HullLayer | FlowLayer | SonarLayer;

A layer of data on a surface.

type LayerBase

interface LayerBase { /** A name for the layer in ids and in the page; letters, digits and hyphens. */ id?: string; /** What the marks are, for the summary: `shots`, `passes`. */ name?: string; /** The accessible title of the layer. Made from the count and `name` when missing, such as "24 shots". */ title?: string; /** The accessible description. Made from the counts by outcome and team when missing. */ description?…

What every layer takes.

type LayerKind

type LayerKind = Layer["kind"];

The kinds of layer.

function layerTable

layerTable(layers: readonly Layer[], options?: TableOptions): string

An HTML table for each layer: the data the drawing shows, as text, for a screen reader or for a page that wants its numbers. The coordinates are the ones given (in the layer's frame and unit), and a caption says which. Hidden layers are in it too. Every cell is escaped.

Example

import { layerTable } from "@johnmorrisdotca/kyogijo-stadiums";
const html = layerTable([{ kind: "points", name: "shots", from: "nhl-api", data: [{ x: 69, y: 22, outcome: "goal", title: "Goal, 2nd period" }] }]);
console.log(html.includes("<th scope=\"col\">Outcome</th>"), html.includes("Goal, 2nd period"));
// true true

type LayerTeam

type LayerTeam = "home" | "away" | (string & {});

Which team a mark belongs to: home or away take the layer's team colours (--ky-layer-home, --ky-layer-away); any other text is a mark of no team.

type League

type League = BasketballLeague | HockeyLeague | FootballLeague | BaseballLeague | CurlingLeague | NetLeague | LacrosseLeague | RugbyLeague | AflLeague | GaelicLeague | CricketLeague | FieldHockeyLeague | HandballLeague | AthleticsLeague | SkatingLeague | BudoLeague;

A league: a variant of a family of surface.

const LEAGUE_SOURCES

LEAGUE_SOURCES: Readonly<Record<string, Readonly<LeagueSource>>>

Every document a league's figures were read from, by id.

function leagueAt

leagueAt(sport: Sport, id: string, year?: number): LeagueAt

A league's figures as they were in a season. Without a year, or for a league with no dated history, they are the rulebook's as it is now.

Example

import { leagueAt } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueAt("nba", "ncaa-men", 2010).differences.map((change) => change.id));
// [ 'ncaa-men.restricted.start' ]

type LeagueAt

interface LeagueAt<L extends League = League> { league: L; /** The season asked for, or `undefined` for today's. */ year: number | undefined; /** The figures in the unit the league keeps them in. */ figures: L["dimensions"]; /** The markings that differ from today's. */ differences: LeagueChange[]; /** Markings whose history starts later than the season asked for (the first cited state is used). */ gaps: string[]; }

The league's figures in a season: the rulebook's as it is now, with each marking that has a dated history as it stood then.

function leagueById

leagueById(sport: Sport, id: string): League | undefined

A league by its sport and id, or undefined.

type LeagueChange

interface LeagueChange { /** `ncaa-men.threePoint.2019`. */ id: string; league: string; /** The marking's name: a key of the league's dimensions. */ feature: string; /** The first season, or `null` for the state before the first change that is cited. */ from: number | null; value: unknown; what: string; ja: string; sources: string[]; }

One change of a league's markings: a state of one marking from a season on.

function leagueChanges

leagueChanges(sport: Sport, id: string): LeagueChange[]

Every change a source dates in a league's markings, in season order, each with a sentence in English and in Japanese and its sources. A league with no cited history has none (a drawing with a year is then the league's rulebook as it is now).

Example

import { leagueChanges } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueChanges("nba", "ncaa-men").map((change) => change.id));

function leagueFigures

leagueFigures(league: League): [string, string][]

The key figures of a league in words, each in the unit its rulebook uses: for a table of the leagues (docs/variants.md) and for a test that holds the drawing to the rulebook.

Example

import { LEAGUES, leagueFigures } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
const fiba = LEAGUES.nba.find((league) => league.id === "fiba")!;
console.log(leagueFigures(fiba).slice(0, 2).map(([name, value]) => `${name}: ${value}`).join("; "));
// court: 28 m by 15 m; lane: 4.9 m wide

function leagueIds

leagueIds: (sport: Sport) => string[]

The ids of a sport's leagues.

const LEAGUES

LEAGUES: Readonly<Record<Sport, readonly League[]>>

The leagues each sport can draw, in the order a menu lists them, the sport's own first (the three football sports share their list, the football leagues). Soccer and the pools have none.

function leagueSeason

leagueSeason(family: Family, year: number): string

The name of a season as the family writes it: 1979–80 for basketball and hockey, which play across the new year, else 1979.

function leagueSize

leagueSize(sport: Sport, id: string, unit?: Unit): { length: number; width: number; unit: Unit; }

The length and the width of a league's surface in a unit (the league's own when none is given): a football field with its end zones, end line to end line. A diamond has no fixed size, its fence is its size.

Example

import { leagueSize } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueSize("nba", "nba"));
// { length: 94, width: 50, unit: 'ft' }

type LeagueSource

interface LeagueSource { id: string; /** The document: "NCAA Men's and Women's Basketball Rules and Interpretations". */ title: string; /** Who publishes it. */ by: string; /** The edition: a season or a year. */ edition: string; /** The address it was read at, on the date in `read`. */ url: string; /** The date it was read (yyyy-mm-dd). */ read: string; /** What was taken from it. */ note?: string; }

A rulebook (or a page that publishes its figures).

type LeagueSpan

interface LeagueSpan<T> { /** The first season it was in force, or `null` for the state before the first change that is cited. */ from: number | null; value: T; /** What it was, in a sentence. */ what: string; /** The same in Japanese. */ ja: string; /** Keys of `LEAGUE_SOURCES`; at least one. */ sources: string[]; }

The state of one marking from a season on, in a league's own history: the season it began, what it was, and its sources.

type LegendLayer

interface LegendLayer extends Omit<LayerBase, "from" | "attacking" | "surfaceSize" | "anchors" | "limits"> { kind: "legend"; for?: string; ramp?: HeatRamp; domain?: readonly [number, number]; center?: number; steps?: number; position?: HeatLegend["position"]; ticks?: number; /** The opacity of the legend, 0 to 1. */ opacity?: number; /** Size and place the legend against this part of the surface, as `HeatLegend.regi…

A colour bar on its own: for another layer (for, the id of an earlier or later heat layer) or with a ramp and a domain of its own.

type LineDash

type LineDash = "solid" | "dashed" | "dotted" | "dash-dot" | readonly number[];

How lines are dashed: solid, dashed, dotted, dash-dot, or dash and gap lengths of your own, in units.

type LineDatum

interface LineDatum extends MarkDatum { x?: number | null; y?: number | null; x2?: number | null; y2?: number | null; /** A path through several points, `[x, y]` each, in place of `x`, `y`, `x2` and `y2`. */ points?: readonly (readonly [number, number])[]; /** How far the line bows, as a fraction of its length. Positive bows to the right of the way it goes as the surface is drawn wide. */ curve?: number; /** Make th…

A line or an arrow: from (x, y) to (x2, y2), or through points.

type LinesLayer

interface LinesLayer extends LayerBase { kind: "lines"; data: readonly LineDatum[]; style?: LineStyle; }

type LineStyle

interface LineStyle { /** The width of the stroke in the layer's units, or a scale from `value`. */ width?: number | Scale; scale?: number; color?: Paint; opacity?: number; /** `auto` (the default) dashes the lines of a hollow outcome. */ dash?: "auto" | "solid" | "dashed" | "dotted"; /** How far lines bow, as a fraction of their length. */ curve?: number; /** Draw lines as waves, for a carry: `true` for the usual w…

How lines and arrows look.

type LineupLayer

interface LineupLayer extends LayerBase { kind: "lineup"; /** `home` (the default) or `away`: the team's colour, and the end it attacks (the right for `home`, the left for `away`) unless `attacking` says. A football lineup's `side` is only the colour: the offence goes right unless `attacking` says, and a defence faces it. Baseball's fielders do not turn. */ side?: "home" | "away"; /** The id of a formation in `FORMA…

A team's lineup drawn on the surface: its players placed by position code in a named formation, as discs or jerseys, each with its number in the mark and its name under it. Two teams are two layers, one for each side. A lineup is not cut to the surface's outline (a fielder at the foul line keeps his whole jersey) unless clip is "surface".

type LineupPlayer

interface LineupPlayer extends MarkDatum { /** The position's code: `GK`, `SS`, `C`, `QB`, `9`, `P4` (a volleyball court's zone). The formation's own ids, and the usual short ones (`CB` for a centre-back of either side, `D` for a defenceman, `OF` for an outfielder), as `LINEUP_CODES` lists. */ pos: string | number; name?: string; number?: string | number; /** A flag or crest for a small badge on the mark: an SVG as …

A player of a lineup, placed by a position's code: nothing else about where he stands is needed.

type LineWidth

type LineWidth = number | { scale: number } | { width: number };

The width of the lines: a multiple of the width they are drawn at (2, or { scale: 2 }), or an absolute width in units ({ width: 0.5 }).

type Look

interface Look { /** What it is for, in a sentence. */ about: string; light: Mode; dark: Mode; }

A named look.

const LOOK_NAMES

LOOK_NAMES: readonly LookName[]

The names of the looks, in the order a menu lists them.

const LOOK_PAIRS

LOOK_PAIRS: Readonly<Record<Group | "all", readonly (readonly [PaletteKey, PaletteKey, number])[]>>

The pairs of a palette that must read: what is painted on what, as [ink, ground, least contrast] (WCAG 2.2's 3:1 for a line, 4.5:1 for text). The words of the drawing are on the apron; a line is on the surface and, where its sport paints them under it, on the second tone of the mowing or planks, the key, the dirt, the infield and the foul ground.

type LookGroup

type Group = "football" | "nhl" | "nba" | "mlb" | "soccer" | "curling" | "net" | "pool" | "athletics";

The surfaces that are painted alike: football's three fields are one.

type LookName

type LookName = "grass" | "night" | "chalkboard" | "blueprint" | "print" | "broadcast" | "high-contrast" | "line-drawing";

The looks a drawing can have, alongside court (a basketball court's own). See LOOKS.

function lookPalette

lookPalette(look: LookName, mode: "light" | "dark", sport: Sport | string): Palette

A look's palette for a sport in one mode: the look's base with what the sport's group changes.

type LookPalette

type Palette = Partial<Record<PaletteKey, string>>;

Colours by part.

type LookPaletteKey

type PaletteKey = | "ink" | "line" | "apron" | "surface" | "alt" | "zone" | "boards" | "red" | "blue" | "dirt" | "foul" | "infield" | "crease" | "key" | "court" | "post" | "pylon" | "rgo" | "rgm" | "rgi" | "rgb" | "hog" | "hack" | "fgz" | "pebble" | "free" | "net" | "area" | "ropeg" | "ropeb" | "ropey" | "roper" | "ropew" | "pad" | "block" | "track" | "sand" | "mat" | "water" | "concrete" | "trackpad" | "markY" | "m…

The parts of a palette, each a custom property of the stylesheet.

const LOOKS

LOOKS: Readonly<Record<LookName, Look>>

The named looks. Each is a light and a dark palette for every sport, as custom properties on .kyogijo[data-palette="<name>"]; pass one as look.

Example

import { LOOKS, drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
const svg = drawKyogijo({ sport: "nhl", look: "chalkboard" });
console.log(Object.keys(LOOKS).length, svg.includes('data-palette="chalkboard"'));
// 8 true

function lookStyle

lookStyle(name: LookName): string

The stylesheet of one look, light and dark, at no weight. A drawing with a look carries its look's.

function luminance

luminance(rgb: [number, number, number]): number

The relative luminance of a colour, 0 (black) to 1 (white).

type MarkDatum

interface MarkDatum { /** Your id for the mark: written on it as `data-mark-id`, so that a listener can link it to a row of your table. The mark's index is written when there is none. */ id?: string | number; /** The tooltip, and what a screen reader reads for the mark. */ title?: string; /** `home`, `away` or another team. */ team?: LayerTeam; /** What happened, as a word of your own (`goal`, `miss`, `complete`). T…

What every mark can carry.

type MarkShape

type MarkShape = "circle" | "square" | "triangle" | "diamond" | "cross" | "x" | "star";

The shapes a point can be drawn as. A team's logo is logo.

type MlbRules

interface MlbRules { /** The side of first, second and third base, in feet (home plate is not a base of this size). */ baseSize: number; /** * Where the pitcher throws from: a chalked box whose front edge is `distance` from the back point of home plate, with its * `width` and `depth`, or (from 1893) the rubber, whose front edge is `distance` from it. */ pitcher: { kind: "box" | "plate"; distance: number; width: numb…

The baseball field's markings in a season. Lengths are feet, the mound's height inches.

type MoveDatum

interface MoveDatum extends MarkDatum { x: number | null; y: number | null; x2?: number | null; y2?: number | null; dx?: number | null; dy?: number | null; /** The mover, for a sonar of each player. */ id?: string | number; name?: string; number?: string | number; }

A movement: from (x, y) to (x2, y2), or the vector (dx, dy) from (x, y).

type Named

interface Named { en: string; ja: string; }

The words of a league in both languages.

type NbaRules

interface NbaRules { /** The width of the free-throw lane. */ keyWidth: number; /** The three-point arc's radius from the basket (the corners stay at 22 feet), or none. */ threePoint: { radius: number } | null; /** The restricted area (the no-charge arc) under each basket. */ restrictedArea: boolean; }

The NBA court's markings in a season. Lengths are feet.

type NetStyle

interface NetStyle { /** A named look: for tennis `hard-blue` (the default), `hard-green`, `clay` or `grass`; volleyball `wood` (the default for indoors) or `blue`, and `sand` for the beach; badminton `green` (the default), `blue` or `wood`; pickleball `blue-green` (the default), `green` or `red`; table tennis `blue` (the default) or `green`; padel `blue` (the default), `green` or `terracotta`; squash `wood` (the de…

The look and the variants of a net sport's court (tennis, volleyball, badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball). Every part is optional. The lines and the sizes are the rulebook's whatever the look; a look is the colour of the ground and of the surface.

type NetworkLayer

interface NetworkLayer extends LayerBase { kind: "network"; data: readonly PassDatum[]; /** Places of your own for some players, over the averages. */ nodes?: readonly NetworkNode[]; /** Leave out links of fewer passes than this. 1 by default. */ minPasses?: number; /** Draw a link for each direction, bowed apart and with a head, and not one for the pair. */ directed?: boolean; /** The width of a node (a share of th…

A pass network: a node for each player at the average place of the passes he made and received, sized by touches, and a link for each pair sized by passes.

type NetworkNode

interface NetworkNode { id: string | number; x: number; y: number; team?: LayerTeam; name?: string; number?: string | number; }

A player of a pass network whose place is given and not worked out from the passes.

type NhlRules

interface NhlRules { /** The centre red line across the middle of the ice. */ centreLine: boolean; /** The goal line's distance from the end boards. */ goalLine: number; /** The blue line's distance from the end boards. */ blueLine: number; /** The goaltender's trapezoid behind the net, or none. */ trapezoid: { atGoalLine: number; atBoards: number } | null; /** The gap between the two hash marks beside an end-zone f…

The NHL rink's markings in a season. Lengths are feet.

function officialName

officialName(place: PlaceNames, language: Language, shown?: string, year?: number): string | undefined

The name a place officially has, in a language, when it is not the everyday name: the official alias in that language that stands in year (without a year, the one that stands today). In English, where there is none in English, a name in the place's own language that is written in Latin letters stands for it (Estadio Banorte). undefined when there is none or when it is the name shown.

Example

import { PARKS, officialName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
const koshien = PARKS["hanshin-koshien-stadium"];
console.log(officialName(koshien, "en"), "/", officialName(koshien, "ja"));
// Hanshin Koshien Stadium / 阪神甲子園球場

type Orientation

type Orientation = "landscape" | "portrait" | "auto";

How the surface lies. landscape is wide, with the teams left and right; portrait is tall, turned a quarter, with the teams top and bottom and every word still upright. auto follows aspect (the container's width over its height), and the helpers for the browser measure the container and draw again when it changes.

const OUTCOMES

OUTCOMES: Readonly<Record<string, OutcomeStyle>>

How the standard outcomes look. A word that is not here draws as a filled mark; style.outcomes of a layer adds words and changes these. A hollow point is drawn open and a hollow line is dashed, so that an outcome is never told by colour alone.

type OutcomeStyle

interface OutcomeStyle { /** Draw a point hollow and a line dashed. */ hollow?: boolean; /** A shape for this outcome, over the layer's. */ shape?: MarkShape; /** A multiple of the mark's size: 1.4 makes a goal stand out. */ scale?: number; }

How an outcome looks: a hollow mark (or a dashed line) is a miss, a failed pass or a shot that did not score.

function outlinePath

outlinePath(o: Outline, reverse?: boolean): string

An outline as a path, drawn clockwise, or counter-clockwise when reverse (a hole in a band that is filled with the non-zero rule).

function outlinePoints

outlinePoints(o: Outline): Pt[]

An outline as points, for a view that raises the stands: arcs are cut into steps of about 7.5 degrees.

type OverhangQuirk

interface OverhangQuirk extends QuirkBase { kind: "overhang"; from: number; to: number; depth_ft: number; }

A stand that hangs over the fence into the field by depth_ft, along a stretch of it.

type PaceOptions

interface PaceOptions { /** Write the pace in seconds per 100 m on each bar. On by default. */ labels?: boolean; }

The split pace bars drawn in the lanes: each split of a race as a bar whose length is the share of the lane that split is, and whose colour is its pace against the field's.

type PageantOptions

interface PageantOptions { /** `carpet` (the default) or `stage`. */ style?: PageantStyle; /** A carpet's border band: `dark` (the default), `team` (each team's colour), or a colour. */ border?: "dark" | "team" | (string & {}); /** A carpet's border band as a share of its shorter side, 0.02 to 0.15. 0.06 by default. */ borderWidth?: number; /** A carpet's fabric: a gentle, fixed wave of light and shade over it. On b…

The pageant: each team's flag or logo laid out large over its own half of the surface, as a stadium lays giant flags and crests on the pitch for a final's ceremony, and an emblem or a trophy in the centre circle. Give pageant: "carpet", "stage" or true (a carpet), or these options. Off by default, and a drawing without it is the same text as before. It needs a score (home and away) and the full or compact size.

type PageantStyle

type PageantStyle = "carpet" | "stage";

How a pageant lays out each team's picture: carpet is its flag or logo laid flat over its half, as a rectangle with a border; stage is its crest on a large round disc with a ring.

type Paint

type Paint = | string | { by: "team"; home?: string; away?: string; other?: string } | { by: "value"; stops: readonly string[]; domain?: readonly [number, number]; missing?: string } | { by: "outcome"; colors: Readonly<Record<string, string>>; other?: string };

A colour, or a rule for choosing one. A text is any colour safeColour accepts. by: "team" uses the home and away colours of the layer, by: "value" runs through stops as a datum's value goes from the least to the greatest of the domain, and by: "outcome" looks the datum's outcome up in colors. A colour a datum carries itself (color) always wins.

type Park

interface Park extends Omit<ParkData, "points" | "unit" | "periods" | "walls"> { /** [angle from dead centre, distance in feet]. */ points: FencePoint[]; periods?: ParkPeriod[]; /** How high the fence stands along stretches of it, in feet. */ walls?: ParkWall[]; }

A ballpark, with its fence in feet.

const PARK_IDS

PARK_IDS: readonly string[]

The keys of PARKS: the current MLB parks, then the past ones by their first season, then the NPB's, each in the order of the sources.

function parkById

parkById: (id: string | undefined) => Park | undefined

The park with a key, or undefined.

function parkByName

parkByName: (name: string | undefined) => Park | undefined

The park a name belongs to (see parksByName; the best match when several answer to it), or undefined.

function parkByWikidata

parkByWikidata: (wikidata: string | undefined) => Park | undefined

The park whose Wikidata item is Q49136, or undefined.

type ParkData

interface ParkData { /** The key: `fenway-park`. */ id: string; /** The Wikidata item of the ground (`Q49136`), the key to join a park to other data. */ wikidata: string; /** The everyday name in English, the one English sports media use (`Koshien Stadium`, `Oracle Park`); the title of the Wikipedia article is one of the `aliases`. */ name: string; /** The everyday name in Japanese (`甲子園球場`, `マツダスタジアム`), as Japanese…

A ballpark's fence as the figures give it.

type ParkDugouts

interface ParkDugouts { home?: DugoutSide; visitors?: DugoutSide; }

Which dugout is on which line, where the article says.

function parkNameIn

parkNameIn: (park: Park, language?: Language) => string

A park's name in a language: the everyday name in English, or in Japanese.

function parkOfficialName

parkOfficialName: (park: Park, language?: Language, year?: number) => string | undefined

The name a park officially has in a language, when it is not the everyday name (Hanshin Koshien Stadium for Koshien Stadium), in the year given or today; else undefined.

type ParkPeriod

interface ParkPeriod { from: number; to: number; points: FencePoint[]; }

A fence of some seasons, in feet.

type ParkPeriodData

interface ParkPeriodData { from: number; to: number; /** [angle from dead centre, distance in the park's unit], from the left field line to the right. */ points: [number, number][]; }

A fence that stood for some seasons of a past park: the figures an article dates, from the first season to the last.

function parkPointsIn

parkPointsIn(park: Park, year: number | undefined): FencePoint[]

A park's fence in a season: the period that names the season, else the park's last fence.

type ParkQuirk

type ParkQuirk = HillQuirk | OverhangQuirk | PointQuirk | CoverQuirk;

A quirk of a ballpark that its article describes with figures.

function parkQuirksIn

parkQuirksIn(park: Park, year?: number): ParkQuirk[]

The quirks a park has in a season: those the article dates for that season, else those of today.

const PARKS

PARKS: Readonly<Record<string, Park>>

Every park by its key: the thirty current MLB ballparks, forty-three past ones, the twelve NPB home parks, and eight secondary or past NPB grounds.

function parksByName

parksByName: (name: string | undefined) => Park[]

The parks that answer to a name, best match first: the everyday names, then the official and cited names and the sponsors', then the former names and the nicknames. A name is compared without case, accents, spaces or punctuation, in full-width or half-width, and in Japanese with hiragana or katakana and with or without the long vowel mark.

Example

import { parksByName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
console.log(parksByName("Camden Yards")[0]?.id, parksByName("甲子園")[0]?.id, parksByName("ハマスタ")[0]?.id);
// oriole-park-at-camden-yards hanshin-koshien-stadium yokohama-stadium

type ParkSource

interface ParkSource { title: string; revision: number; }

Where a park's figures were read: an article of Wikipedia at a revision.

function parkSourceUrl

parkSourceUrl(source: ParkSource, language?: "en" | "ja"): string

The address of the revision of an article that a park's figures were read from.

function parkUnit

parkUnit: (id: string | undefined) => Unit | undefined

The unit a park's figures were published in: metres for the parks of Japan (converted to feet once, in PARKS), feet for the rest. undefined for a key that is not a park.

type ParkWall

interface ParkWall extends Omit<ParkWallData, "height"> { height_ft: number; }

A stretch of the fence and how high it stands, in feet.

type ParkWallData

interface ParkWallData { /** The stretch's angles from dead centre: the left field line is -45 and the right field line 45 (our reading of the article's words, see docs/references.md). */ from: number; to: number; /** The height of the wall in the park's unit. */ height: number; /** A name the wall has in the article (`Green Monster`); a wall without one is named by its place in the field. */ label?: string; /** Whe…

A stretch of the fence and how high it stands, as the figures give it (the height in the park's unit).

function parkWallsIn

parkWallsIn(park: Park, year?: number): ParkWall[]

The stretches of fence a park has in a season, with their heights in feet: the heights the article dates for that season, else those of today.

function parseTime

parseTime(value: unknown): number | undefined

A time in seconds from a number or from text: 26.12, "26.12", "1:46.91". undefined when it is neither.

type PassDatum

interface PassDatum extends MarkDatum { /** The id of the passer and of the receiver (a number or a name; the same id is the same player). */ from: string | number; to: string | number; /** Where the pass was made from, and where it was received. */ x?: number | null; y?: number | null; x2?: number | null; y2?: number | null; /** The names for the labels and the table, by id. The last one given for an id is used. */…

A pass: who passed to whom, from where to where. The places are the passer's and, if given, the receiver's.

type PlaceAlias

interface PlaceAlias { name: string; /** The language of the name: `en`, `ja`, `es`, `de`... */ lang: string; kind: PlaceNameKind; /** The first and the last year the name was in use, where a source dates it. */ from?: number; to?: number; /** Where the name was read: an article and its revision, or a Wikidata item and property (`Wikidata Q49136 P1448`). */ source?: string; /** For a `wikipedia` alias, the revision …

Another name of a place: a title, an earlier or a sponsor's name, a nickname.

type PlaceNameKind

type PlaceNameKind = "official" | "wikipedia" | "sponsor" | "former" | "nickname";

What an alias is. official is the name the owner or operator gives it (when it is not the everyday one), wikipedia the title of an article that is cited, sponsor a naming-rights name, former an earlier name and nickname a short form or a name people use.

function plainName

plainName(text: string): string

A name made fit to compare: width, case, accents, kana (hiragana is katakana), the old and new forms of the same sound (ヂ, ヅ, ヴ), the long vowel mark and every space, dot and mark of punctuation put aside, so that Estádio Maracanã and estadio maracana are one name, as are ヤンキー・スタジアム and ヤンキースタジアム.

type PlayerDatum

interface PlayerDatum extends MarkDatum { x: number | null; y: number | null; /** The player's id, name and shirt number, for a label and for the table. */ id?: string | number; name?: string; number?: string | number; }

A player at a place. team (home, away or another) is what colours the player's cell, triangles and hull.

type PointDatum

interface PointDatum extends MarkDatum { x: number | null; y: number | null; /** A shape for this mark alone, or `logo` for the logo of its team (or its own `logo`). */ shape?: MarkShape | "logo"; /** The width of the mark, in the layer's units. */ size?: number; /** The address of a logo, for the shape `logo`. */ logo?: string; }

A point to mark.

type PointQuirk

interface PointQuirk extends QuirkBase { kind: "pole" | "flagpole" | "bullpen"; angle: number; feet: number; /** The height of a pole, where the article gives it. */ height_ft?: number; /** Whose bullpen it is, where the article says. */ side?: "home" | "visitors"; }

A foul pole, a flagpole or a bullpen at an angle and a distance from home plate.

type PointsLayer

interface PointsLayer extends LayerBase { kind: "points"; data: readonly PointDatum[]; style?: PointStyle; }

type PointStyle

interface PointStyle { shape?: MarkShape | "logo"; /** The width of a mark in the layer's units, or a scale from `value`. A default that follows the size of the surface. */ size?: number | Scale; /** A multiple of the default size. */ scale?: number; /** The colour of the fill. The home and away colours for marks that carry a team, the layer colour for the rest. */ fill?: Paint; /** The colour of the edge. A pale or…

How points look.

type PoloPlan

interface PoloPlan extends Common { sport: "waterpolo"; length: number; width: number; /** The side of the official table, as the field lies wide. */ table: "top" | "bottom"; }

A water polo field to draw.

const POOL_SPORTS

POOL_SPORTS: readonly PoolSport[]

The sports of the pool, in the order a menu lists them.

type PoolKind

type PoolKind = "competition" | "warm-up";

The swimming pool of a competition, or a warm-up pool: the same lanes and ropes, without the starting blocks, the flags and the false start rope.

type PoolLook

type PoolLook = "flat" | "water" | "tiled";

How the water is painted: flat (the default) is one clean colour; water lays a faint, seeded pattern of light on it, deeper blue at the middle of a long pool; tiled shows the tiles of the floor through the water as well.

function poolPlan

poolPlan(o: Pick<SurfaceOptions, "sport" | "pool">): PoolPlan

The pool the options ask for, of the sport's kind.

type PoolPlan

type PoolPlan = SwimPlan | PoloPlan | DivePlan | ArtisticPlan;

type PoolRace

interface PoolRace { /** The name of the event: "Women's 200 m butterfly, final". */ event?: string; /** The distance in metres. Worked out from the splits (the number of splits times the distance between them) when it is missing, else the length of the course. */ distance?: number; /** The metres between two splits: 50 in a long course pool and 25 in a short course one, unless this says another. */ splitEvery?: num…

A race: the event, its distance, and the swimmers in their lanes.

type PoolSection

interface PoolSection { /** The water's depth in metres. The rules' least for the pool: 2.5 for a 50 m pool, 2.0 for a 25 m one. */ depth?: number; /** How much taller than long the section is drawn. 2.5 by default; the section is not to scale otherwise. */ exaggerate?: number; }

A side section of a swimming pool: its depth profile, the blocks, the flags and the false start rope.

type PoolSport

type PoolSport = "swimming" | "waterpolo" | "diving" | "artisticswimming";

The sports of the pool: the swimming pool, the water polo field, the diving well and the artistic swimming area.

type PoolStyle

interface PoolStyle { /** Swimming: `long` (50 m, the default) or `short` (25 m). */ course?: "long" | "short"; /** Swimming: the lanes, 1 to 10. 8 by default. Ten lanes are numbered 0 to 9. */ lanes?: number; /** Swimming: a temporary pool (26 m wide, ten lanes of 2.5 m) in place of a permanent one (25 m, lanes 0 and 9 of 2.4 m). Only for a ten-lane pool. */ temporary?: boolean; /** Swimming: `competition` (the def…

The look, the size and the data of a pool. Every part is optional. The sport (swimming, waterpolo, diving, artisticswimming) says which pool it is; the parts that belong to one pool are said so in their descriptions.

type QuirkKind

type QuirkKind = "hill" | "overhang" | "pole" | "flagpole" | "bullpen" | "ivy" | "basket";

What a quirk of a ballpark is.

type QuirkName

type QuirkName = "hill" | "overhang" | "pole" | "flagpole" | "bullpen" | "ivy" | "basket" | "backstop" | "dugouts";

The things in a ballpark that quirks draws besides the walls.

type QuirkOptions

interface QuirkOptions { /** Only these; all of the park's by default. */ only?: readonly QuirkName[]; /** Write each quirk's name beside it. */ labels?: boolean; }

Which quirks of a ballpark are drawn (quirks), and whether they carry their names.

type RaceShow

interface RaceShow { names?: boolean; flags?: boolean; results?: boolean; reaction?: boolean; splits?: boolean; medals?: boolean; seeds?: boolean; }

What the labels of a race show. Everything that has data is shown unless it says false.

function rampColour

rampColour(stops: readonly string[], t: number): string | undefined

A colour a fraction t (0 to 1) of the way through stops, as a hex colour, or the nearest stop when one cannot be read without a browser (var(--x), a name that is not a common one). Stops that are not colours are ignored; with none left, undefined.

type RangeBox

interface RangeBox { x0: number; x1: number; y0: number; y1: number; /** A frame of `FRAMES` (or one of your own) the numbers are in. The surface's own when missing. */ frame?: string | Frame; /** The end the box's points are attacking, for a frame whose numbers belong to the team's attack (see `toSurface`). */ attacking?: Side; }

A box of your own to crop to: the corners x0, y0 and x1, y1, in the drawing's frame (feet, or units: x along the surface from the left, y across it from the top; a ballpark's x is across the field and its y minus the distance out from home plate), or, with frame, in that frame's own numbers and unit.

type RangeInfo

interface RangeInfo { id: RangeName; end?: Side; /** The leagues the range is for, when only some of the sport's have it (rugby union's 22 and rugby league's 20-metre area; the fielding circle of the ICC's grounds). Every league when missing. */ leagues?: readonly string[]; }

One range of a sport: its name, and the end it is at by default when it is at an end (left or right, as the surface is drawn wide). A range with no end is in the middle or is the whole of something.

type RangeName

type RangeName = | "red-zone" | "between-the-20s" | "end-zone" | "field-of-play" | "own-half" | "opponent-half" | "offensive-zone" | "defensive-zone" | "neutral-zone" | "half" | "half-court" | "paint" | "final-third" | "defensive-third" | "middle-third" | "penalty-area" | "infield" | "outfield" | "sheet" | "house" | "folded" | "in-goal" | "twenty-two" | "twenty" | "crease" | "forward-fifty" | "centre-square" | "larg…

The parts of a surface a drawing can be cropped to (range). Football: red-zone, between-the-20s, end-zone, field-of-play, own-half, opponent-half. Hockey: offensive-zone, defensive-zone, neutral-zone, half. Basketball: half-court, paint. Soccer: half, final-third, defensive-third, middle-third, penalty-area. Baseball: infield, outfield. The net sports: half-court. Curling: sheet, half, house, folded. Rugby: half, twenty-two (union) or twenty (league), in-goal. Lacrosse: half, crease. Australian football: half, forward-fifty, centre-square. Gaelic games: half, large-rectangle. Cricket: pitch, circle. Field hockey: half, circle, twenty-three. Handball: half, goal-area. Swimming: half, pool-end. Water polo: half, goal-area. Diving: springboards, platforms. Artistic swimming: half, centre-stage. Judo and karate: contest-area, start. Sumo: ring. Kyudo: shajo, yamichi, azuchi. Dojo: kamiza, half. Athletics: bend, home-straight, back-straight, finish, start, infield, a field event (high-jump, pole-vault, long-jump, triple-jump, shot-put, discus-throw, hammer-throw, javelin-throw) and water-jump. RANGES lists them for each sport.

type RangeNamed

interface RangeNamed { name: RangeName; /** The end of the surface the range is at, as the surface is drawn wide: `left` or `right` (a curling sheet's also `top` and `bottom`: the left end and the right). Each range has its own default (`RANGES`); a range with no end ignores it. */ end?: SheetEnd; }

A named range at the end of your choice: { name: "offensive-zone", end: "left" } is the offensive zone of a team that attacks the left.

const RANGES

RANGES: Readonly<Record<Sport, readonly RangeInfo[]>>

The named ranges of each sport, in the order a menu lists them: { id, end }, with end the end the range is at when range gives none (left or right, as the surface is drawn wide; a range with no end is in the middle or is the whole of something and ignores one). Offensive ranges are at the right by default, as a frame's numbers are for a team that attacks the right (see toSurface); defensive and own ranges at the left; the others, such as a half, at the left, as a curling sheet's house is.

function readSituation

readSituation(situation: unknown, ctx: SituationContext): SituationRead | undefined

The situation a drawing's options ask for, decided: which team attacks and which end, what is lit. undefined when the options have none, when the sport has no situation (only hockey, baseball and football do), or when nothing in it is a value the sport can have.

type Reducer

type Reducer = (values: readonly number[], info: BinInfo) => number | null;

A custom statistic: the finite values of a bin's samples (an empty list when none had one), and the bin's counts. Return null for a bin that has no value.

function rescale

rescale(from: SoccerProviderId | SoccerFrame, to: SoccerProviderId | SoccerFrame, pitch?: PitchMetres): Rescaler

The map from one coordinate system to another, piecewise linear between the landmarks (it is built on piecewise of @johnmorrisdotca/kyogijo-stadiums/frames), so that a point on a line of the pitch (the goal line, the goal area, the penalty mark, the penalty area, the halfway line, the touchline, the posts) lands on the same line in the other system. Between two landmarks the map is linear; beyond the goal lines and the touchlines (a ball out of play) it runs on along the last piece. Give a pitch size in metres for a provider whose numbers follow the pitch (Tracab, SkillCorner, Second Spectrum, Metrica, international). The same map, through the surface and in feet, is convert of the frames module.

Example

import { rescale } from "@johnmorrisdotca/kyogijo-stadiums";
const toStatsBomb = rescale("opta", "statsbomb");
// The middle of Opta's penalty-box line, 17 units from the goal line, is on StatsBomb's, 18 yards from it.
const [x, y] = toStatsBomb(100 - 17, 50);
console.log(x, y);
// 102 40

type Rescaler

interface Rescaler { (x: number, y: number): [number, number]; /** Only the x of a point. */ x(value: number): number; /** Only the y of a point. */ y(value: number): number; /** The map the other way: from the second provider's coordinates to the first's. */ inverse: Rescaler; from: SoccerFrame; to: SoccerFrame; }

A map from one provider's coordinates to another's.

function rgbOf

rgbOf(value: string | undefined): [number, number, number] | undefined

The red, green and blue of a colour, when it can be read without a browser (a hex colour, rgb() or a common name).

type RinkFigures

interface RinkFigures { /** End boards to end boards. */ length: number; width: number; /** The radius of the boards' corners. */ cornerRadius: number; /** The goal line, from the end boards. */ goalLine: number; /** The blue lines, from the end boards. */ blueLine: number; /** The face-off circles' radius. */ circleRadius: number; /** An end-zone face-off spot, from the goal line, and from the rink's long axis. */ …

The figures of an ice rink. Lengths, in the unit of the table they are in.

type Rope

interface Rope { y: number; colour: RopeColour; }

A lane rope: where it lies across the pool (metres from the top wall) and its colour.

type RopeColour

type RopeColour = "green" | "blue" | "yellow";

The colours of the ropes of a pool, by name.

function rulesFor

rulesFor<S extends EraSport>(sport: S, year?: number): EraRules<S>

The markings of a sport's surface in a season: what the rule books and the leagues had painted on it then. Without a year it is today's (ERA_CURRENT). A season is named by the year it starts in (1979 is the 1979–80 season). differences lists what is not as it is today, gaps the markings that no source covers for the season, and approx those whose measure is not cited.

Example

import { rulesFor } from "@johnmorrisdotca/kyogijo-stadiums/eras";
const nba = rulesFor("nba", 1995);
console.log(nba.season, nba.rules.threePoint, nba.rules.restrictedArea, nba.differences.length);
// 1995–96 { radius: 22 } false 2

function safeColour

safeColour(value: unknown): string | undefined

The colour as it may be written into a style attribute, or undefined. A hex colour, rgb(), rgba(), hsl(), hsla(), a colour name or var(--name) is drawn; anything else is ignored, so nothing from a form or an address can end the attribute or add a rule.

Example

import { safeColour } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(safeColour("#1f5fa8"), safeColour("red;fill:url(x)"));
// #1f5fa8 undefined

function safePicture

safePicture(value: unknown): string | undefined

A picture's address as it may be written into an image's href, or undefined (what safeLogo checks after it has looked for an emblem's id). An https: or http: address, a relative path, an image data: address, or an SVG document as text (written as an image, which cannot run scripts).

Example

import { safePicture } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(safePicture("https://example.com/gulls.svg"), safePicture("javascript:alert(1)"));
// https://example.com/gulls.svg undefined

type Scale

interface Scale { domain?: readonly [number, number]; range: readonly [number, number]; /** For a size: scale the area and not the width, so that twice the value covers twice the ground. On by default. */ area?: boolean; }

A number mapped to another range: domain is the values (the data's least and greatest when missing) and range what they become.

type ScoreOptions

interface ScoreOptions extends SurfaceOptions { home: Team; away: Team; /** `full` (the default), `compact` or `badge`. */ size?: Size; /** The state of the game: a `StatusWord`, or text such as "2nd period" or "Bottom 9th". */ status?: StatusWord | (string & {}); /** The competition and round: "Cup Final, Game 7". */ competition?: string; /** The date, as it is to be written. */ date?: string; /** A line of text fo…

What a score drawing takes beyond a surface.

function seasonLabel

seasonLabel(sport: EraSport, year: number): string

The name of a season as the sport writes it: 1979–80 for the NHL and the NBA, which play across the new year, and 1974 for the NFL, the CFL and baseball. The year is the one the season starts in.

Example

import { seasonLabel } from "@johnmorrisdotca/kyogijo-stadiums/eras";
console.log(seasonLabel("nba", 1979), seasonLabel("nhl", 1999), seasonLabel("nfl", 1974));
// 1979–80 1999–00 1974

type ShootoutAttempt

type ShootoutAttempt = boolean | null | undefined;

One shootout attempt: true a goal (●), false a miss or a save (○), null or left out one not yet taken.

type ShootoutRound

type ShootoutRound = readonly [away: ShootoutAttempt, home: ShootoutAttempt];

One round of a shootout: the visitors' attempt and then the home team's, as the NHL's shootout is shot (the visitors shoot first).

type Side

type Side = "left" | "right";

A side of the drawing as the eye sees it. Which side the home team is on follows the sport (see homeSideFor) unless homeSide says otherwise.

type Situation

type Situation = HockeySituation | BaseballSituation | FootballSituation;

A game's situation: the sport decides which of the three it is.

type SituationContext

interface SituationContext { /** The sport as drawn (`chooseLeague(o).sport`): `nhl`, `mlb`, `nfl`, `cfl` or `ifaf`; a situation is for these only. */ sport: Sport; /** The side the home team's banner is on. */ homeSide: Side; /** The attack arrows' options, which say which end each team attacks. */ direction?: DirectionOptions; /** The football field: the yards between the goal lines and the downs a team has. */ fo…

What a reading needs to know of the drawing it is for.

type SituationRead

type SituationRead = HockeyRead | BaseballRead | FootballRead;

A situation, read.

type SituationTeam

type SituationTeam = "home" | "away";

A team of the game: the home team or the visitors.

function situationText

situationText(options: { sport: Sport; league?: string; homeSide?: Side; direction?: DirectionOptions; language?: Language; situation?: Situation; }, language?: Language): string | undefined

The short label of a situation as the drawing writes it: hockey's 5v4 PP 1:42, baseball's Top 7 · 2 out · 3-2, football's 3rd & 7. undefined when there is none to write.

Example

import { situationText } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(situationText({ sport: "nhl", situation: { awaySkaters: 5, homeSkaters: 4, clock: "1:42" } }));
console.log(situationText({ sport: "mlb", situation: { inning: 7, half: "top", outs: 2, balls: 3, strikes: 2 } }));
console.log(situationText({ sport: "nfl", situation: { ballOn: 88, down: 3, toGo: 7 } }));
// 5v4 PP 1:42
// Top 7 · 2 out · 3-2
// 3rd & 7 · OPP 12

type Size

type Size = "full" | "compact" | "badge";

The size of a score drawing: the surface with the score on it, the surface with a strip under it, or one strip.

type SkatingDiscipline

type SkatingDiscipline = "long-track" | "short-track" | "figure";

The disciplines a drawing can be of: the standard 400 m speed skating track, the short track oval or the figure skating rink. league says it too: isu-long-track, isu-short-track, isu-figure.

type SkatingFurniture

type SkatingFurniture = "judges" | "referee" | "technical-panel" | "kiss-and-cry";

The things that stand at the rink of a figure skating competition: the judges and the referee at the rinkboard, the technical panel, and the kiss and cry.

type SkatingInfield

type SkatingInfield = "floor" | "ice";

What lies inside the long track: the ice of the warm-up lane carried on to the infield, or the floor of the arena.

type SkatingRace

type SkatingRace = LongTrackRace | ShortTrackRace;

Every race a drawing can mark.

type SkatingStyle

interface SkatingStyle { /** Long track: the radius of the inner edge of the inner lane, 25 m to 26 m (25 m by default). The straight follows, so that a lap is 400 m. */ innerRadius?: number; /** Long track: the width of the outer lane, 4 m or more (4 m by default). */ laneWidth?: number; /** Long track: the warm-up lane inside the competition lanes. On by default. */ warmUp?: boolean; /** Long track: what lies insi…

What a skating drawing takes beyond a surface. Every part is optional: the standard 400 m track with an inner radius of 25 m, two lanes of 4 m, the warm-up lane, the blocks and the padding is drawn without any, and so are the short track's standard oval with its five tracks and the figure skating rink of 60 m by 30 m.

const SOCCER

SOCCER: { readonly goalWidth: 7.32; readonly goalHeight: 2.44; readonly goalAreaReach: 5.5; readonly penaltyReach: 16.5; readonly spot: 11; readonly centreRadius: 9.15; readonly arcRadius: 9.15; readonly cornerRadius: 1; readonly flagHeight: 1.5; readonly limits: { readonly length: readonly [90, 120]; readonly width: readonly [45, 90]; readonly internationalLength: readonly [100, 110]; readonly internationalWidth: r…

Law 1 of the IFAB Laws of the Game (2025/26), the markings of a full pitch: the goal area reaches 5.5 m from the posts and into the field, the penalty area 16.5 m, the penalty mark is 11 m from the goal line, the arcs and the centre circle have a radius of 9.15 m, the corner arc 1 m; the goal is 7.32 m between the posts and 2.44 m high; the lines are at most 12 cm wide. The Laws give yards beside each (6, 18, 12, 10 and 8 yards and 8 feet).

const SOCCER_PITCH_IDS

SOCCER_PITCH_IDS: readonly SoccerPitchId[]

The ids of the presets, in the order a menu lists them.

const SOCCER_PITCHES

SOCCER_PITCHES: Readonly<Record<SoccerPitchId, SoccerSpec>>

The presets: the international pitch of 105 by 68 m (the Laws' usual, the Premier League's and UEFA's categories 3 and 4), the least size the MLS requires (110 by 70 yards) and the NCAA's best (120 by 75 yards), the least and most the Laws allow, the FA's 9-, 7- and 5-a-side pitches and FIFA's futsal court. Each is a full SoccerSpec.

Example

import { SOCCER_PITCHES } from "@johnmorrisdotca/kyogijo-stadiums";
const { length, width, name } = SOCCER_PITCHES.fifa;
console.log(name, length, width);
// International (FIFA) 105 68

const SOCCER_PROVIDER_IDS

SOCCER_PROVIDER_IDS: readonly SoccerProviderId[]

The ids of the providers, in the order a menu lists them.

const SOCCER_PROVIDERS

SOCCER_PROVIDERS: Readonly<Record<SoccerProviderId, SoccerProvider>>

The providers. Where a provider's own page gives the figures (StatsBomb's and Wyscout's diagrams, Impect's glossary, Metrica's and SkillCorner's READMEs) they are those; Opta's, Tracab's and Second Spectrum's specifications are not public, so theirs are the figures the open-source libraries mplsoccer, ggsoccer and kloppy agree on, which docs/sources.md says.

function soccerFrame

soccerFrame(frame: SoccerProviderId | SoccerFrame | string | undefined, pitch?: PitchMetres): SoccerFrame | undefined

The frame of a provider (by id), or a frame of your own as it is, for a pitch of this size in metres (which matters only to a provider whose numbers follow the pitch). An id that is not a provider, or a frame that cannot be drawn, gives undefined.

Example

import { soccerFrame } from "@johnmorrisdotca/kyogijo-stadiums";
const opta = soccerFrame("opta");
console.log(opta?.xRight, opta?.yTop);
// 100 100

type SoccerFrame

interface SoccerFrame { id: string; name: string; /** The unit of the numbers, for people. */ unit: string; xLeft: number; xRight: number; yTop: number; yBottom: number; /** How far the penalty area reaches from the goal line, and half its width. */ penaltyDepth: number; penaltyHalfWidth: number; /** How far the goal area reaches, and half its width. */ goalAreaDepth: number; goalAreaHalfWidth: number; /** The penal…

A pitch as a data provider writes it down. Everything is in the provider's own unit.

xLeft and xRight are x at the two goal lines, with the team attacking left to right; yTop and yBottom are y at the touchlines as they lie in a picture with the first at the top. So the frame says both where the origin is and which way y runs: Opta (y up) has yTop: 100, yBottom: 0; StatsBomb (y down) has yTop: 0, yBottom: 80. The depths are measured from the goal line along x, the half widths from the middle of the pitch along y.

function soccerLandmarks

soccerLandmarks(f: SoccerFrame): { x: number[]; y: number[]; }

Where the landmarks of a frame stand on each axis, in its units, from the left goal line to the right and from the top touchline to the bottom.

function soccerPitchClass

soccerPitchClass(length: number, width: number): SoccerPitchClass

Where a pitch of this size stands under Law 1: international (100 to 110 m by 64 to 75 m, for international matches), within-laws (90 to 120 m by 45 to 90 m, and the touchline longer than the goal line) or outside-laws.

Example

import { soccerPitchClass } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(soccerPitchClass(105, 68), soccerPitchClass(95, 60), soccerPitchClass(60, 40));
// international within-laws outside-laws

type SoccerPitchClass

type SoccerPitchClass = "international" | "within-laws" | "outside-laws";

Which of the Laws' ranges a length and a width fall in.

type SoccerPitchId

type SoccerPitchId = "fifa" | "uefa" | "mls" | "ncaa" | "law-min" | "law-max" | "9-a-side" | "7-a-side" | "5-a-side" | "futsal";

The ids of the presets.

type SoccerProvider

interface SoccerProvider { id: SoccerProviderId; name: string; /** The unit of its numbers, for people. */ unit: string; /** The unit as the frames registry names it. */ frameUnit: FrameUnit; /** Where (0, 0) lies, in words. */ origin: string; /** True when its extents follow the size of the pitch (Tracab's centimetres from the centre): the frame is made for a size. */ sized: boolean; /** The page of the provider's …

A provider: its name, the page its figures were read from, and its frame (which for some depends on the size of the pitch).

type SoccerProviderId

type SoccerProviderId = "opta" | "statsbomb" | "wyscout" | "tracab" | "metrica" | "skillcorner" | "secondspectrum" | "impect" | "uefa" | "international";

The providers, by the id the options use.

type SoccerSize

interface SoccerSize { length: number; width: number; /** `m` (the default), `yd`, `ft`, `in`, `cm` or `mm`. */ unit?: "m" | "yd" | "ft" | "in" | "cm" | "mm"; }

A pitch given by size: any positive length and width, in metres, yards or feet.

function soccerSpec

soccerSpec(pitch?: SoccerPitchId | SoccerSize | string, defaultUnit?: NonNullable<SoccerSize["unit"]>): SoccerSpec

The pitch an option asks for: a preset's id, or a size. An unknown id, or a size that is not a positive number (or is more than 400 m), gives the international pitch. A size has the Laws' markings of a full pitch, sized to fit: the areas are never wider than the pitch.

Example

import { soccerSpec } from "@johnmorrisdotca/kyogijo-stadiums";
const pitch = soccerSpec({ length: 110, width: 70, unit: "yd" });
console.log(pitch.length.toFixed(2), pitch.width.toFixed(2));
// 100.58 64.01

type SoccerSpec

interface SoccerSpec { id: string; /** The name written in the title. */ name: string; format: SoccerFormat; /** Goal line to goal line (the touchline). */ length: number; /** Touchline to touchline (the goal line). */ width: number; /** The penalty area: how far it reaches into the field from the goal line, and its width along the goal line. */ penaltyDepth: number; penaltyWidth: number; /** The goal area (the six-…

A pitch: its size and every marking. All lengths in metres.

type SonarLayer

interface SonarLayer extends LayerBase { kind: "sonar"; data: readonly MoveDatum[]; /** What a rose is of: `all` (one rose, at the middle of the movements' starts; the default), `player` (one for each `id`, at the average start of his), `grid` (one for each cell of a grid) or `zone` (one for each zone of `zones`). */ by?: "all" | "player" | "grid" | "zone"; /** How many directions a rose is cut into. 12 by default. …

A rose for each group of movements: how many go in each direction, or how long they are on average.

type Sport

type Sport = "nfl" | "cfl" | "ifaf" | "nhl" | "nba" | "mlb" | "soccer" | "curling" | "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball" | PoolSport | FieldSport | "athletics" | "skating" | BudoSport;

The surfaces: three football fields, a rink, a court, a ballpark, a soccer pitch, a curling sheet, twelve net, racket and court sports (tennis, volleyball, badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball), four pools (swimming, water polo, diving and artistic swimming), seven field sports (lacrosse, rugby, Australian football, Gaelic football and hurling, cricket, field hockey and handball) and the athletics stadium (the 400 m track with its infield, and the covered 200 m track).

const SPORTS

SPORTS: readonly Sport[]

The sports that can be drawn, in the order a menu lists them.

type StandGap

interface StandGap { kind: "tunnel" | "bench" | "media" | "camera"; box: [x0: number, y0: number, x1: number, y1: number]; }

A place left open or built on: a tunnel through the lower stands, a bench, the camera platform of the media side. A box in the wide frame.

type StandOutline

type Outline = | { kind: "rrect"; x0: number; y0: number; x1: number; y1: number; r: number } | { kind: "ellipse"; cx: number; cy: number; rx: number; ry: number } | { kind: "poly"; points: Pt[] };

The outline of a part of the stands, in feet in the surface's wide frame: a rectangle with rounded corners, an ellipse, or a polygon.

type StandRing

interface StandRing { /** `lower`, `upper`, `courtside`, `grandstand` or `bleachers`, with the tier's number where a ring has parts. */ id: string; tier: "courtside" | "gallery" | "lower" | "upper" | "grandstand" | "bleachers" | "wall"; /** The band is `region` less `hole` (nothing for a polygon that is the band itself). */ region: Outline; /** Further pieces of the band that are apart from `region` (a ballpark's bl…

A tier of the stands: a band of seats between two outlines.

type StandSector

interface StandSector { ring: string; kind: "away" | "tifo-home" | "tifo-away"; points: Pt[]; }

A block of the stands that is not the home fans': the visitors', or a card display. A polygon in the wide frame.

type StatisticName

type StatisticName = "count" | "sum" | "mean" | "median" | "percent" | "max" | "min" | "rate";

The statistics of a bin. percent is each bin's share of all the samples (0 to 100); rate is the share of a bin's samples that are hits (0 to 100).

function statusLabel

statusLabel(status: string | undefined, language?: Language): string | undefined

The words of a status in a language: a standard one is translated, any other text is kept as it is.

function statusText

statusText(o: Pick<ScoreOptions, "home" | "away" | "status" | "homeSide"> & Partial<Pick<ScoreOptions, "sport" | "league">>, language?: Language): string | undefined

The words of a status, with the penalty shoot-out beside them when both teams have one: "Final/AET", or "Final · 4-2 pens" (the shoot-out score in the order of the drawing's sides: the home team's first when it is on the left, which is the default for soccer and the other sports that list the home team first). Pass sport (and league) for the sport's default; with neither, the away team's is first unless homeSide is left.

type StatusWord

type StatusWord = "final" | "final-ot" | "final-so" | "final-aet" | "final-pens" | "live" | "half" | "extra-time" | "extra-end" | "penalties" | "postponed" | "tiebreak" | "match-tiebreak" | "retired" | "walkover";

The standard words of a status. end-6 (or "End 6") is a curling end, set-3 a set and game-2 a game, innings-2 (or "Innings 2") a cricket innings. Any other text is written as it is.

type StripePattern

type StripePattern = "bands" | "wide" | "narrow" | "lengthwise" | "checks" | "diagonal" | "circles" | "rings" | "fan" | "none";

How grass is mown: bands across the surface (the usual), wide ones or narrow ones, along it, in checks, on the diagonal, in rings round the middle (circles), or none. A ballpark is mown in rings by default (arcs about home plate) and may also be mown in a fan (wedges out from home plate). A football field and a ballpark take the same options as a soccer pitch; a surface that is not grass ignores them.

const SURFACE_UNITS

SURFACE_UNITS: Readonly<Record<Sport, Unit>>

The unit each surface's rulebook states its dimensions in: the NFL's and the CFL's fields in yards (and the IFAF's, which follows them), the NHL's rink, the NBA's court and the baseball field in feet, a soccer pitch in metres (the Laws'), a tennis court, a pickleball court and a racquetball court in feet (the ITF's, USA Pickleball's and the IRF's), a volleyball court, a badminton court, a table, a padel court, a squash court, a sepak takraw court, a teqball court, a roundnet court and a netball court in metres (FIVB's, BWF's, ITTF's, FIP's, World Squash's, ISTAF's, FITEQ's, the International Roundnet Federation's and World Netball's), a curling sheet in metres (World Curling's rules give metres with feet beside them; Curling Canada's give feet first, and its league curling-canada has feet), a rugby field, an Australian football ground, a Gaelic field, a hockey pitch and a handball court in metres (the rulebooks'), a swimming pool, a water polo field, a diving well and an artistic swimming area in metres (World Aquatics') and a cricket ground in yards (the Laws' and the ICC's; the NLL's box floor and the PLL's and the NCAA men's fields are in the leagues' own feet and yards). A Japanese park's fence is measured in metres and converted once: see parkUnit.

type SurfaceOptions

interface SurfaceOptions { sport: Sport; /** `auto` (the default), `light` or `dark`. */ theme?: Theme; /** `landscape` (the default), `portrait` or `auto`. */ orientation?: Orientation; /** For `orientation: "auto"`: the container's width over its height. Without it `auto` draws landscape. */ aspect?: number; /** Give the parts of the drawing `part` names (`surface`, `plate`, `away`, `home`, `score`), for styling i…

What every drawing takes.

type Swimmer

interface Swimmer { /** The lane, as the pool numbers them (1 to 8, or 0 to 9 in a ten-lane pool). */ lane: number; name: string; /** A short form for the label: the country's three letters, or a club's. */ abbr?: string; /** The flag or crest, drawn small beside the name. */ flag?: string; /** The swimmer's colour (a cap, a club), drawn as a stripe on the label. */ color?: string; /** The entry time. */ seed?: numb…

One swimmer in a lane: who is in it, and, once the race is swum, what happened. The caller passes the flag (an https: address, an image data: address or an SVG document as text, held to the rules of a team's logo); the package bundles no flags. Times are seconds (26.12) or text ("1:46.91").

type SwimmerStatus

type SwimmerStatus = "dq" | "dns" | "dnf";

Why a swimmer has no time.

function swimPlan

swimPlan(o: Pick<SurfaceOptions, "pool" | "sport">, defaults?: Partial<Pick<VenuePool, "course" | "kind" | "lanes">>): SwimPlan

The swimming pool the options ask for: pool.course (long, 50 m, or short, 25 m) and pool.lanes (8 by default). The width is the rules' for that many lanes: 25 m for eight in a 50 m pool (Part Two 15.6.1), 25 m for ten with lanes 0 and 9 of 2.4 m (Part Two 15.6.2.1), or 26 m for a temporary pool (Part Two 15.6.2.2), and lanes of 2.5 m with spaces of 0.5 m for the other pools.

type SwimPlan

interface SwimPlan extends Common { sport: "swimming"; kind: PoolKind; course: "long" | "short"; /** The length between the touch panels and the width across the lanes and the spaces, in metres. */ length: number; width: number; /** The lanes from the top of the drawing down: lane 1 (or 0) is at the bottom, on the right when one faces the course from the start (Part Two 15.8.6). */ lanes: LanePlan[]; /** The metres …

A swimming pool to draw.

type TableOptions

interface TableOptions { language?: "en" | "ja"; /** The most rows to write for one layer. 500 by default; the caption says when rows are left out. */ maxRows?: number; /** A class for each table. `ky-table` by default. */ className?: string; /** The drawing's options (at least `sport`, and `park`, `pitch`, `units` and `language` where they apply): needed for the tables of heat maps, hexagon maps, zone charts and co…

What layerTable takes.

type Tatami

type Tatami = Piece;

One mat of a dojo's floor, or a part of one, in metres from the floor's top left corner.

type Team

interface Team { /** The name, as it is to be written: "Harbour City Gulls". */ name: string; /** A short form, three letters or so, for the score and the badge. Taken from the name when missing. */ abbr?: string; /** The score. A number, or text such as "–" before the game. */ score?: number | string; /** The logo: an `https:` or relative address, an image `data:` address, an SVG document as text, or, with a render…

One team of a score.

type TeamMark

type TeamMark = "plain" | "round";

The shape of a team's mark: its logo as it is (plain), or clipped to a circle with a ring (round).

type TextDatum

interface TextDatum extends MarkDatum { x: number | null; y: number | null; text: string | number; /** The height of the letters, in the layer's units. */ size?: number; /** Turn the text this many degrees, clockwise, as the eye sees it. The text stays upright when the surface is turned. */ rotate?: number; anchor?: "start" | "middle" | "end"; }

A word or a number to write.

type TextLayer

interface TextLayer extends LayerBase { kind: "text"; data: readonly TextDatum[]; style?: TextStyle; }

type TextStyle

interface TextStyle { /** The height of the letters in the layer's units. A default that follows the size of the surface. */ size?: number; color?: Paint; weight?: number; anchor?: "start" | "middle" | "end"; /** Outline the letters in the halo colour (`--ky-halo`) so that they read on any ground. On by default. */ halo?: boolean; opacity?: number; }

How text looks.

function textWidth

textWidth(text: string, bold?: boolean): number

An estimate of the width of a string, in ems. Characters of Japanese and other full-width scripts are one em.

type Theme

type Theme = "auto" | "light" | "dark";

auto follows the device's light or dark setting (and data-theme on the page's root), the others force one.

type Unit

type Unit = "ft" | "in" | "yd" | "m" | "cm" | "mm";

A unit of length: feet, inches, yards, metres, centimetres or millimetres.

const UNITS

UNITS: readonly Unit[]

The units, from the largest used for a field to the smallest.

function useEmblems

useEmblems(render: EmblemRenderer | undefined): void

Plug a renderer of ready-made emblems into kyogijo, for every drawing on the page: after this logo: "crest:shield" and emblem: "trophy:hockey-cup" are drawn. Call it once, at start-up. Without one, an id is an address and is refused. useEmblems(undefined) unplugs it.

Example

import { useEmblems } from "@johnmorrisdotca/kyogijo-stadiums";
import { renderEmblem } from "@johnmorrisdotca/dougu-sports/emblems";
useEmblems(renderEmblem);

const VARIANTS

VARIANTS: readonly League[]

Every league of every family, once each.

type VenueItem

interface VenueItem { pool: VenuePool; plan: PoolPlan; x: number; y: number; }

One pool of a hall: its plan and where its water's top left corner is, in metres from the corner of the water of the whole hall.

function venueLayout

venueLayout(style: PoolStyle): VenueLayout | undefined

The pools of a hall (pool.venue) laid out to scale. A pool with an x and a y stands there; the others are set in rows, left to right, each apart metres (10, the distance the rules prefer between a swimming pool and a diving well, Part Two 15.15.1; never less than the 8 they require) from the one before, and a new row when a row would be longer than 125 m.

type VenueLayout

interface VenueLayout { items: VenueItem[]; width: number; height: number; apart: number; labels: boolean; seating: boolean; /** How far the pools' fixtures reach beyond the box, in metres. */ reach: { left: number; right: number; top: number; bottom: number }; }

A hall laid out: its pools, and the size of the box round their water, in metres.

type VenueOptions

interface VenueOptions { pools: readonly VenuePool[]; /** The metres between two pools. 10 (the preferred distance of the rules) by default. */ apart?: number; /** Write each pool's name on the plan. On by default. */ labels?: boolean; /** Draw the seating for spectators along the top of the hall. On by default. */ seating?: boolean; }

A natatorium: several pools in one hall, to scale, kept the distance the rules ask of a swimming pool and a diving well.

type VenuePool

interface VenuePool { sport: PoolSport; /** A name for the plan's label. The pool's own by default. */ name?: string; /** For a swimming pool: its course and kind (`short` and `warm-up` for a warm-up pool). */ course?: "long" | "short"; kind?: PoolKind; lanes?: number; x?: number; y?: number; }

One pool of a natatorium: which pool it is, and where, in metres from the hall's top left corner. A pool with no place is put by the layout.

const VERSION

VERSION: "0.6.0"

The package's version: the same as package.json's, and a test holds them to each other.

type VoronoiLayer

interface VoronoiLayer extends PlayersBase { kind: "voronoi"; }

The part of the surface nearest each player, coloured by team: who controls the space (the Voronoi diagram of the players, clipped to the surface).

const WALL_SEATS

WALL_SEATS: Readonly<Record<string, string>>

The walls the package knows have seats on top of them, by park and the wall's label: Fenway Park's Green Monster (the Monster seats, 2003).

type WallOptions

interface WallOptions { /** A key under the drawing for the shades: under 10 feet, 10 to 20, 20 to 30 and 30 and over (the same in metres for a drawing in metres). */ legend?: boolean; /** Write each wall's name or place and its height beside it. */ labels?: boolean; }

How a ballpark's walls are drawn (walls).

type WallPlace

type WallPlace = "outfield" | "left" | "leftCenter" | "center" | "deadCenter" | "rightCenter" | "right" | "leftToCenter" | "centerToRight" | "leftCorner" | "rightCorner";

Where a stretch of wall stands, in words: a key of the words of ballpark.sections (left, leftCorner, deadCenter, centerToRight...).

type WaterJump

type WaterJump = "inside" | "outside" | "none";

Where the steeplechase's water jump is: inside the second bend, outside it, or not drawn.

function winnerOf

winnerOf(o: Pick<ScoreOptions, "home" | "away" | "status" | "winner"> & { sport?: Sport; }): "home" | "away" | "none"

Who is ahead in a score drawing, as the drawing's emphasis. auto (the default) picks the leader once the status is a final one; leader picks the leader of any game; a level game has none unless both teams have a penalty shoot-out score (penalties) and the shoot-out was not level; a score that is not a number has none.

const ZONE_SET_IDS

ZONE_SET_IDS: readonly ZoneSetId[]

const ZONE_SETS

ZONE_SETS: Readonly<Record<ZoneSetId, { sports: readonly Sport[]; title: string; source: string; }>>

Every zone set, with the sports it is for, the source of its definition and its title.

type ZoneSetId

type ZoneSetId = "field-thirds" | "cricket-ground" | "soccer-positional" | "soccer-thirds" | "soccer-channels" | "soccer-18" | "nba-shot" | "nhl-slot" | "nhl-home-plate" | "baseball-spray" | "baseball-retrosheet" | "football-pass" | "athletics-track" | "skating-track" | "budo-areas" | "curling-house" | "net-court" | "wall-court" | "roundnet-rings" | "pool-lanes" | "water-polo-areas" | "pool-thirds";

The zone sets.

type ZonesLayer

interface ZonesLayer extends HeatBase { kind: "zones"; /** The set of zones: `soccer-positional`, `soccer-thirds`, `soccer-channels`, `soccer-18`, `nba-shot`, `nhl-slot`, `nhl-home-plate`, `baseball-spray`, `baseball-retrosheet` or `football-pass`. */ zones: ZoneSetId; /** The way the team attacks as the surface is drawn wide: names the sides and counts the soccer bands from the team's own end. `right` by default. *…

A statistic by named zone of the surface.

@johnmorrisdotca/kyogijo-stadiums/geometry

ARTISTIC BADMINTON BEACH_VOLLEYBALL boardsY convertLength convertPoint CURLING DEFAULT_FENCE dimensionsOf DIVING DIVING_LINES FEET_PER FEET_PER_METRE FEET_PER_YARD FENCE_ANGLES fenceAt fenceIn FencePoint fencePoints FOOTBALL footballLength FootballSpec fullPitch goalAreaWidth GOALPOST_WIDTH HASH_LENGTH inch isUnit lengthsIn METRES_PER_FOOT METRES_PER_YARD MLB NBA NETBALL NHL PADEL penaltyAreaWidth PICKLEBALL Point RACQUETBALL ROUNDNET Scaled secondBase SEPAK_TAKRAW SITTING_VOLLEYBALL SizedSport skinJoin SOCCER SOCCER_PITCH_IDS SOCCER_PITCHES SoccerArea SoccerFormat soccerPitchClass SoccerPitchClass SoccerPitchId SoccerSize soccerSpec SoccerSpec SQUASH STONE_DIAMETER SURFACE_UNITS surfaceSize SWIMMING SWIMMING_WIDTHS TABLE_TENNIS TENNIS TEQBALL threePointJoin Unit UNITS VOLLEYBALL WATER_POLO

const ARTISTIC

ARTISTIC: { readonly olympic: { readonly length: 30; readonly width: 20; readonly depth: 3; }; readonly minimum: { readonly length: 25; readonly width: 15; readonly deepLength: 12; readonly deepWidth: 12; readonly deepDepth: 3; readonly depth: 2; }; readonly warmUp: { readonly length: 30; readonly width: 20; readonly depth: 3; }; readonly line: 0.25; readonly start: { readonly heightOlympic: 0.7; readonly heightReco…

Artistic swimming, in metres (Part Seven, Article 12). The routine area for the Olympic Games and the World Championships is at least 30.0 by 20.0 m, 3.0 m deep (Part Seven 12.2.2); elsewhere at least 15 by 25 m, of which 12 by 12 m is at least 3.0 m deep and the rest at least 2.0 m (Part Seven 12.1.1). The starting platform at least 0.7 m high at the Olympic Games and the World Championships, 1.0 m recommended (Part Seven 12.2.4), 0.5 m least elsewhere, 1.0 m recommended (12.1.6); the Technical Officials' platform (the judges') at least 0.6 m high and not more than 2 m from the pool's edge (12.2.5). A practice warm-up pool at least 25 by 25 m or 30 by 20 m, 3 m deep (12.2.6). June 2025: Article 27, 27.2.2 and 27.2.3, 27.1.2, 27.2.7 and 27.1.7, 27.2.8, 27.2.9.

const BADMINTON

BADMINTON: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").BadmintonFigures

const BEACH_VOLLEYBALL

BEACH_VOLLEYBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").VolleyballFigures

function boardsY

boardsY(x: number, radius?: number): number

Where the boards' curve is at a distance x from the end: the y at which it meets a line across the rink. The corner's radius is the NHL's unless given.

function convertLength

convertLength(value: number, from: Unit, to: Unit): number

A length in another unit.

Example

import { convertLength } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
console.log(convertLength(100, "yd", "ft"), convertLength(200, "ft", "m").toFixed(2));
// 300 60.96

function convertPoint

convertPoint(point: Point, from: Unit, to: Unit): [number, number]

A point in another unit.

const CURLING

CURLING: { readonly length: 150; readonly width: number; readonly lengthMin: 146; readonly widthMin: { readonly wcf: 14.5; readonly curlingCanada: number; }; readonly teeFromMiddle: 57; readonly backLine: 6; readonly hogLine: 21; readonly centreBeyondTee: 12; readonly rings: { readonly twelve: 6; readonly eight: 4; readonly four: 2; readonly button: 0.5; }; readonly lineWidth: number; readonly hogLineWidth: number; …

The curling sheet, from R1 of World Curling's Rules of Curling (1 September 2026) and Rule 3 of Curling Canada's Rules of Curling for General Play (September 2022 to August 2026), in feet as the second prints them and the first prints beside its metres. The sheet is drawn at the largest the rules allow: the length is 150 feet (R1(a) of World Curling: "minimum 150 feet"; Curling Canada allows 146 to 150), the width 15 feet 7 inches (4.750 m, the greatest of both). Lengths run along the sheet from the backboard, widths across it.

const DEFAULT_FENCE

DEFAULT_FENCE: { readonly left: 330; readonly leftCenter: 375; readonly center: 400; readonly rightCenter: 375; readonly right: 330; }

The default fence of a ballpark: the usual 330, 375, 400, 375 and 330 feet.

function dimensionsOf

dimensionsOf(sport: "nhl", unit?: Unit): Scaled<typeof NHL> dimensionsOf(sport: "nba", unit?: Unit): Scaled<typeof NBA> dimensionsOf(sport: "mlb", unit?: Unit): Scaled<typeof MLB> dimensionsOf(sport: "nfl" | "cfl" | "ifaf", unit?: Unit): Scaled<FootballSpec> dimensionsOf(sport: "soccer", unit?: Unit): Scaled<typeof SOCCER> dimensionsOf(sport: "curling", unit?: Unit): Scaled<typeof CURLING> dimensionsOf(sport: "tennis", unit?: Unit): Scaled<typeof TENNIS> dimensionsOf(sport: "volleyball", unit?: Unit): Scaled<typeof VOLLEYBALL> dimensionsOf(sport: "badminton", unit?: Unit): Scaled<typeof BADMI…

Every dimension of a sport's surface in a unit (the rulebook's own when none is given): the football spec, NHL, NBA or MLB with each length converted and every name and flag kept.

const DIVING

DIVING: { readonly springboard: { readonly length: 4.88; readonly width: 0.5; }; readonly platforms: { readonly "1": { readonly length: 5; readonly width: readonly [1, 2.9]; readonly height: 1; }; readonly "3": { readonly length: 5; readonly width: readonly [1, 2]; readonly height: 3; }; readonly "5": { readonly length: 6; readonly width: readonly [2.9, 2.9]; readonly height: 5; }; readonly "7.5": { readonly length:…

Diving, in metres (Part Four of the Competition Regulations, Article 15 and Appendix 2, "Diving facility specifications", which is Article 17.2 of Part Four). A springboard is 4.88 m long and 0.50 m wide (Part Four 15.3.2), 1 m or 3 m above the water (Appendix 2); a platform of the length, width and height of the table (Part Four 15.4.3 and Appendix 2). Every distance is measured from the plummet, the vertical line through the middle of the front edge of the springboard or platform (Part Four 15.1.1). A is from the plummet back to the pool wall (for a platform on concrete: how far its front edge projects beyond the wall, Part Four 15.4.9), B to the pool wall at the side, C to the next plummet (the platforms' widths: Part Four 15.2.2), D to the wall ahead, H the depth of water at the plummet; each as [minimum, preferred]. A depth of 5 m under the boards is what a pool built after 1 January 2025 should have if possible (Part Four 15.1.2.1). The minima of H are the February 2026 table's (3.40, 3.70, 3.20, 3.50, 3.70, 4.10 and 4.50 m, with 5.00 m preferred everywhere); the June 2025 edition had 3.50, 3.80, 3.30, 3.60, 3.80, 4.30 and 5.00 m ("Table Diving Facilities"). The other figures of the table, A to D, are the same in both editions.

const DIVING_LINES

DIVING_LINES: 3

How many lines cross a diving well for the Olympic Games and the World Championships (Part Four 15.6.3.1; June 2025: CR 13.2.7). A count, so it is kept out of the table of lengths.

const FEET_PER

FEET_PER: Readonly<Record<Unit, number>>

How many feet are in one of each unit (the international foot is exactly 0.3048 metres).

const FEET_PER_METRE

FEET_PER_METRE: 3.280839895

const FEET_PER_YARD

FEET_PER_YARD: 3

const FENCE_ANGLES

FENCE_ANGLES: readonly [-45, -22.5, 0, 22.5, 45]

The angles the five usual fence distances are taken at.

function fenceAt

fenceAt(points: readonly FencePoint[], angle: number): number

The distance to the fence at an angle: a monotone cubic through the points (Fritsch and Carlson), so that the wall is smooth and never swings out past the figures it was made from. Outside the points' angles it holds the end distance.

Example

import { fenceAt, fencePoints, DEFAULT_FENCE } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
const points = fencePoints(DEFAULT_FENCE);
console.log(fenceAt(points, 0), fenceAt(points, -45));
// 400 330

function fenceIn

fenceIn(points: readonly FencePoint[], to: Unit, from?: Unit): FencePoint[]

A fence's points with each distance in another unit; the angles stay as they are. Feet are the default for from.

type FencePoint

type FencePoint = readonly [angle: number, feet: number];

A fence point: the angle from dead centre field (negative to the left field line, positive to the right) and the distance in feet.

function fencePoints

fencePoints(fence: { left: number; leftCenter: number; center: number; rightCenter: number; right: number; }): FencePoint[]

A fence made of the five usual distances.

const FOOTBALL

FOOTBALL: Record<"nfl" | "cfl" | "ifaf", FootballSpec>

NFL: Rule 1 of the NFL rulebook. CFL: the CFL rulebook, "The Field". IFAF: Appendix D of the IFAF 2021 rules, "Field Diagrams".

function footballLength

footballLength: (spec: FootballSpec) => number

The length of a football field with its end zones.

type FootballSpec

interface FootballSpec { id: "nfl" | "cfl" | "ifaf"; /** The name written in the title. */ name: string; /** Goal line to goal line. */ length: number; /** The depth of each end zone. */ endZone: number; /** Sideline to sideline. */ width: number; /** Distance of the hash-mark lines from each sideline. */ hash: number; /** The width of the band of the boundary lines, outside the field of play. */ boundary: number; /…

A football field. Lengths are along the field from end line to end line, widths across it.

function fullPitch

fullPitch(id: string, name: string, length: number, width: number): SoccerSpec

A full-size, eleven-a-side pitch of any size, with the Laws' markings.

function goalAreaWidth

goalAreaWidth: (goal?: number, reach?: number) => number

The goal area's width and the penalty area's, which follow from the goal and the reach.

const GOALPOST_WIDTH

GOALPOST_WIDTH: 18.5

The space between the two uprights, inside to inside.

const HASH_LENGTH

HASH_LENGTH: 2

Hash marks and sideline ticks are this long (across the field).

function inch

inch: (n: number) => number

Inches as feet.

function isUnit

isUnit: (unit: unknown) => unit is Unit

True when unit is one of the six.

function lengthsIn

lengthsIn<T>(table: T, to: Unit, from?: Unit): Scaled<T>

Every length in a table of dimensions (NHL, MLB, a FootballSpec, nested objects and lists of numbers), in another unit. Text and flags are kept as they are. The table is not changed.

Example

import { NHL, lengthsIn } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
const rink = lengthsIn(NHL, "m");
console.log(rink.length.toFixed(2), rink.width.toFixed(3));
// 60.96 25.908

const METRES_PER_FOOT

METRES_PER_FOOT: 0.3048

const METRES_PER_YARD

METRES_PER_YARD: 0.9144

const MLB

MLB: { readonly base: 90; readonly baseSize: number; readonly pitcher: 60.5; readonly moundRadius: 9; readonly skinRadius: 95; readonly homeCircleRadius: 13; readonly baseCircleRadius: 10; readonly warningTrack: 15; readonly backstop: 60; readonly runningLane: { readonly from: 45; readonly width: 3; }; readonly boxes: { readonly batterWidth: 4; readonly batterLength: 6; readonly gap: 0.5; }; readonly plate: { readon…

The baseball field, from rules 2.01 to 2.05 of the Official Baseball Rules.

const NBA

NBA: { readonly length: 94; readonly width: 50; readonly lane: 16; readonly freeThrowLine: 19; readonly freeThrowRadius: 6; readonly centreRadius: 6; readonly centreInnerRadius: 2; readonly threePoint: { readonly radius: 23.75; readonly corner: 22; readonly sideInset: 3; }; readonly basket: 5.25; readonly backboard: { readonly width: 6; readonly fromBaseline: 4; }; readonly restricted: 4; readonly rim: 0.75; readonl…

The NBA court as it is today, from Rule 1 of the NBA rulebook and the court diagram. rulesFor("nba", year) has it as it was in a season.

const NETBALL

NETBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").NetballFigures

const NHL

NHL: { readonly length: 200; readonly width: 85; readonly cornerRadius: 28; readonly goalLine: 11; readonly blueLine: 75; readonly goalLineWidth: number; readonly lineWidth: 1; readonly circleRadius: 15; readonly endSpotFromGoalLine: 20; readonly spotOffset: 22; readonly spotRadius: 1; readonly neutralSpotFromBlue: 5; readonly centreSpotRadius: 0.5; readonly hashLength: 2; readonly hashApart: number; readonly crease…

The NHL rink as it is today, from Rule 1 of the NHL rulebook. rulesFor("nhl", year) (eras.ts) has it as it was in a season.

const PADEL

PADEL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").PadelFigures

function penaltyAreaWidth

penaltyAreaWidth: (goal?: number, reach?: number) => number

const PICKLEBALL

PICKLEBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").PickleballFigures

type Point

type Point = readonly [x: number, y: number];

A point or a vector: [x, y].

const RACQUETBALL

RACQUETBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").RacquetballFigures

const ROUNDNET

ROUNDNET: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").RoundnetFigures

type Scaled

type Scaled<T> = T extends number ? number : T extends string | boolean | undefined | null ? T : T extends readonly (infer U)[] ? Scaled<U>[] : { -readonly [K in keyof T]: Scaled<T[K]> };

A table of dimensions with every number turned into another unit, keeping its shape.

function secondBase

secondBase: () => number

The distance along the diagonal from home plate to second base.

const SEPAK_TAKRAW

SEPAK_TAKRAW: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").SepakTakrawFigures

const SITTING_VOLLEYBALL

SITTING_VOLLEYBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").VolleyballFigures

type SizedSport

type SizedSport = Exclude<Sport, "mlb">;

The sports whose surface has one size. A ballpark's size is its fence, which fenceIn gives.

function skinJoin

skinJoin(pitcher?: number, skinRadius?: number): number

Where the infield arc meets a foul line, along the line, from the back of home plate. The baseball field's by default; a league's pitching distance and arc if given.

const SOCCER

SOCCER: { readonly goalWidth: 7.32; readonly goalHeight: 2.44; readonly goalAreaReach: 5.5; readonly penaltyReach: 16.5; readonly spot: 11; readonly centreRadius: 9.15; readonly arcRadius: 9.15; readonly cornerRadius: 1; readonly flagHeight: 1.5; readonly limits: { readonly length: readonly [90, 120]; readonly width: readonly [45, 90]; readonly internationalLength: readonly [100, 110]; readonly internationalWidth: r…

Law 1 of the IFAB Laws of the Game (2025/26), the markings of a full pitch: the goal area reaches 5.5 m from the posts and into the field, the penalty area 16.5 m, the penalty mark is 11 m from the goal line, the arcs and the centre circle have a radius of 9.15 m, the corner arc 1 m; the goal is 7.32 m between the posts and 2.44 m high; the lines are at most 12 cm wide. The Laws give yards beside each (6, 18, 12, 10 and 8 yards and 8 feet).

const SOCCER_PITCH_IDS

SOCCER_PITCH_IDS: readonly SoccerPitchId[]

The ids of the presets, in the order a menu lists them.

const SOCCER_PITCHES

SOCCER_PITCHES: Readonly<Record<SoccerPitchId, SoccerSpec>>

The presets: the international pitch of 105 by 68 m (the Laws' usual, the Premier League's and UEFA's categories 3 and 4), the least size the MLS requires (110 by 70 yards) and the NCAA's best (120 by 75 yards), the least and most the Laws allow, the FA's 9-, 7- and 5-a-side pitches and FIFA's futsal court. Each is a full SoccerSpec.

Example

import { SOCCER_PITCHES } from "@johnmorrisdotca/kyogijo-stadiums";
const { length, width, name } = SOCCER_PITCHES.fifa;
console.log(name, length, width);
// International (FIFA) 105 68

type SoccerArea

type SoccerArea = "box" | "arcs" | "semicircle";

How a pitch's penalty area is shaped: a box (two lines and a line across), futsal's two quarter circles and a line, or the FA small-sided laws' semicircle round the middle of the goal line.

type SoccerFormat

type SoccerFormat = "11" | "9" | "7" | "5" | "futsal";

The formats: eleven a side under the IFAB Laws, the small-sided ones of the FA, and futsal under FIFA's Futsal Laws.

function soccerPitchClass

soccerPitchClass(length: number, width: number): SoccerPitchClass

Where a pitch of this size stands under Law 1: international (100 to 110 m by 64 to 75 m, for international matches), within-laws (90 to 120 m by 45 to 90 m, and the touchline longer than the goal line) or outside-laws.

Example

import { soccerPitchClass } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(soccerPitchClass(105, 68), soccerPitchClass(95, 60), soccerPitchClass(60, 40));
// international within-laws outside-laws

type SoccerPitchClass

type SoccerPitchClass = "international" | "within-laws" | "outside-laws";

Which of the Laws' ranges a length and a width fall in.

type SoccerPitchId

type SoccerPitchId = "fifa" | "uefa" | "mls" | "ncaa" | "law-min" | "law-max" | "9-a-side" | "7-a-side" | "5-a-side" | "futsal";

The ids of the presets.

type SoccerSize

interface SoccerSize { length: number; width: number; /** `m` (the default), `yd`, `ft`, `in`, `cm` or `mm`. */ unit?: "m" | "yd" | "ft" | "in" | "cm" | "mm"; }

A pitch given by size: any positive length and width, in metres, yards or feet.

function soccerSpec

soccerSpec(pitch?: SoccerPitchId | SoccerSize | string, defaultUnit?: NonNullable<SoccerSize["unit"]>): SoccerSpec

The pitch an option asks for: a preset's id, or a size. An unknown id, or a size that is not a positive number (or is more than 400 m), gives the international pitch. A size has the Laws' markings of a full pitch, sized to fit: the areas are never wider than the pitch.

Example

import { soccerSpec } from "@johnmorrisdotca/kyogijo-stadiums";
const pitch = soccerSpec({ length: 110, width: 70, unit: "yd" });
console.log(pitch.length.toFixed(2), pitch.width.toFixed(2));
// 100.58 64.01

type SoccerSpec

interface SoccerSpec { id: string; /** The name written in the title. */ name: string; format: SoccerFormat; /** Goal line to goal line (the touchline). */ length: number; /** Touchline to touchline (the goal line). */ width: number; /** The penalty area: how far it reaches into the field from the goal line, and its width along the goal line. */ penaltyDepth: number; penaltyWidth: number; /** The goal area (the six-…

A pitch: its size and every marking. All lengths in metres.

const SQUASH

SQUASH: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").SquashFigures

const STONE_DIAMETER

STONE_DIAMETER: number

The diameter of a curling stone drawn at the greatest circumference the rules allow (36 inches), in feet.

const SURFACE_UNITS

SURFACE_UNITS: Readonly<Record<Sport, Unit>>

The unit each surface's rulebook states its dimensions in: the NFL's and the CFL's fields in yards (and the IFAF's, which follows them), the NHL's rink, the NBA's court and the baseball field in feet, a soccer pitch in metres (the Laws'), a tennis court, a pickleball court and a racquetball court in feet (the ITF's, USA Pickleball's and the IRF's), a volleyball court, a badminton court, a table, a padel court, a squash court, a sepak takraw court, a teqball court, a roundnet court and a netball court in metres (FIVB's, BWF's, ITTF's, FIP's, World Squash's, ISTAF's, FITEQ's, the International Roundnet Federation's and World Netball's), a curling sheet in metres (World Curling's rules give metres with feet beside them; Curling Canada's give feet first, and its league curling-canada has feet), a rugby field, an Australian football ground, a Gaelic field, a hockey pitch and a handball court in metres (the rulebooks'), a swimming pool, a water polo field, a diving well and an artistic swimming area in metres (World Aquatics') and a cricket ground in yards (the Laws' and the ICC's; the NLL's box floor and the PLL's and the NCAA men's fields are in the leagues' own feet and yards). A Japanese park's fence is measured in metres and converted once: see parkUnit.

function surfaceSize

surfaceSize(sport: SizedSport, unit?: Unit): { length: number; width: number; unit: Unit; }

The length and the width of a surface, in a unit: the rulebook's own (SURFACE_UNITS) when none is given. A football field is measured with its end zones, end line to end line.

Example

import { surfaceSize } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
console.log(surfaceSize("nfl"), surfaceSize("nhl", "m").length.toFixed(2));
// { length: 120, width: 53.333333333333336, unit: 'yd' } 60.96

const SWIMMING

SWIMMING: { readonly longCourse: 50; readonly shortCourse: 25; readonly lane: 2.5; readonly edgeLane: 2.4; readonly spaceOlympic: 2.5; readonly spacePermanentWorld: 0.1; readonly spaceTemporaryWorld: 0.5; readonly spaceMinimum: 0.5; readonly floatDiameter: 0.15; readonly redEnds: 5; readonly markAt15: 15; readonly markAt25: 25; readonly markRun: 0.6; readonly laneLine: { readonly width: 0.25; readonly longLength: 46…

The swimming pool (Part Two, Article 15). Lengths are metres; widths run across the course and depths down. lane is the width of a lane and the distance between the centres of two (Part Two 15.13.2, 15.6.1); a 10-lane pool for the World Championships has lanes 0 and 9 2.4 m wide with spaces of 0.1 m outside them in a permanent pool (Part Two 15.6.2.1), or all lanes 2.5 m with spaces of 0.5 m (a 26 m wide temporary pool, Part Two 15.6.2.2).

const SWIMMING_WIDTHS

SWIMMING_WIDTHS: { readonly permanent: 25; readonly temporary: 26; readonly shortEight: 21; }

The widths of the pools the rules draw, as Appendices 1 to 9 of Part Two (Article 17) name them (the pool's width by its length and lanes): 50 by 25 (8 or 10 lanes), 50 by 26 (10 lanes, temporary), 25 by 21 (8), 25 by 25 and 25 by 26 (10).

const TABLE_TENNIS

TABLE_TENNIS: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").TableTennisFigures

const TENNIS

TENNIS: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").TennisFigures

The net sports' courts, in feet, from the rulebooks' own figures (leagues/courts.ts, docs/sources.md): the ITF's tennis court (Rule 1), the FIVB's indoor and beach volleyball courts (Rules 1 and 2), the BWF's badminton court (Law 1), USA Pickleball's court (Section 3) and the ITTF's table (Laws 2.1 and 2.2). The lengths run along the court from the end line, the widths across it, each to the outside of the lines.

const TEQBALL

TEQBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").TeqballFigures

function threePointJoin

threePointJoin(): number

Where the three-point arc meets the straight lines, measured from the baseline.

type Unit

type Unit = "ft" | "in" | "yd" | "m" | "cm" | "mm";

A unit of length: feet, inches, yards, metres, centimetres or millimetres.

const UNITS

UNITS: readonly Unit[]

The units, from the largest used for a field to the smallest.

const VOLLEYBALL

VOLLEYBALL: import("/home/runner/work/kyogijo-stadiums/kyogijo-stadiums/src/leagues").VolleyballFigures

const WATER_POLO

WATER_POLO: { readonly length: 25; readonly width: 20; readonly depth: 2; readonly twoMetre: 2; readonly fiveMetre: 5; readonly sixMetre: 6; readonly half: 12.5; readonly goalAreaLine: 2; readonly goal: { readonly width: 3; readonly post: 0.08; readonly crossbarHeight: 0.9; readonly netClear: 0.3; }; readonly reEntry: { readonly length: 2; readonly depth: 1.08; readonly fromCorner: 2; }; readonly substitution: 0.5; …

The water polo field of play, in metres (Part Six, Articles 20 and 21). The field is 25.00 by 20.00 m, the distance between the two goal lines and between the two side lines (Part Six 20.2.2.1 and 20.2.2.2); the water at least 2.00 m deep everywhere (Part Six 20.2.4 and 20.8.2.1). The June 2025 edition asked 1.80 m, and 2.00 m at the Olympic Games and the World Championships (CR 18.1.3 and 18.2.2).

@johnmorrisdotca/kyogijo-stadiums/parks

CoverQuirk DugoutSide fenceFor HillQuirk officialName OverhangQuirk Park PARK_IDS parkById parkByName parkByWikidata ParkData ParkDugouts parkNameIn parkOfficialName ParkPeriod ParkPeriodData parkPointsIn ParkQuirk parkQuirksIn PARKS parksByName ParkSource parkSourceUrl parkUnit ParkWall ParkWallData parkWallsIn PlaceAlias PlaceNameKind plainName PointQuirk QuirkKind WallPlace

type CoverQuirk

interface CoverQuirk extends QuirkBase { kind: "ivy" | "basket"; from: number; to: number; }

Ivy on the wall, or a basket on top of it, along a stretch of the fence.

type DugoutSide

type DugoutSide = "first" | "third";

The sides of the field a dugout is on: along the first base line or the third.

function fenceFor

fenceFor(park: Park | undefined, fence: Partial<Fence> | undefined, year?: number, generic?: readonly FencePoint[]): FencePoint[]

The fence points a drawing uses: a park's (as it stood in year, when the park has dated fences), with any of the five usual distances in fence laid over them, or the usual 330, 375, 400, 375 and 330 feet of a generic field (or the generic points a league's rules give).

type HillQuirk

interface HillQuirk extends QuirkBase { kind: "hill"; angle: number; feet: number; width_ft: number; slope_deg?: number; }

An incline in the field: against the fence at an angle and a distance, as wide as the article says (width_ft), and as steep as it says.

function officialName

officialName(place: PlaceNames, language: Language, shown?: string, year?: number): string | undefined

The name a place officially has, in a language, when it is not the everyday name: the official alias in that language that stands in year (without a year, the one that stands today). In English, where there is none in English, a name in the place's own language that is written in Latin letters stands for it (Estadio Banorte). undefined when there is none or when it is the name shown.

Example

import { PARKS, officialName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
const koshien = PARKS["hanshin-koshien-stadium"];
console.log(officialName(koshien, "en"), "/", officialName(koshien, "ja"));
// Hanshin Koshien Stadium / 阪神甲子園球場

type OverhangQuirk

interface OverhangQuirk extends QuirkBase { kind: "overhang"; from: number; to: number; depth_ft: number; }

A stand that hangs over the fence into the field by depth_ft, along a stretch of it.

type Park

interface Park extends Omit<ParkData, "points" | "unit" | "periods" | "walls"> { /** [angle from dead centre, distance in feet]. */ points: FencePoint[]; periods?: ParkPeriod[]; /** How high the fence stands along stretches of it, in feet. */ walls?: ParkWall[]; }

A ballpark, with its fence in feet.

const PARK_IDS

PARK_IDS: readonly string[]

The keys of PARKS: the current MLB parks, then the past ones by their first season, then the NPB's, each in the order of the sources.

function parkById

parkById: (id: string | undefined) => Park | undefined

The park with a key, or undefined.

function parkByName

parkByName: (name: string | undefined) => Park | undefined

The park a name belongs to (see parksByName; the best match when several answer to it), or undefined.

function parkByWikidata

parkByWikidata: (wikidata: string | undefined) => Park | undefined

The park whose Wikidata item is Q49136, or undefined.

type ParkData

interface ParkData { /** The key: `fenway-park`. */ id: string; /** The Wikidata item of the ground (`Q49136`), the key to join a park to other data. */ wikidata: string; /** The everyday name in English, the one English sports media use (`Koshien Stadium`, `Oracle Park`); the title of the Wikipedia article is one of the `aliases`. */ name: string; /** The everyday name in Japanese (`甲子園球場`, `マツダスタジアム`), as Japanese…

A ballpark's fence as the figures give it.

type ParkDugouts

interface ParkDugouts { home?: DugoutSide; visitors?: DugoutSide; }

Which dugout is on which line, where the article says.

function parkNameIn

parkNameIn: (park: Park, language?: Language) => string

A park's name in a language: the everyday name in English, or in Japanese.

function parkOfficialName

parkOfficialName: (park: Park, language?: Language, year?: number) => string | undefined

The name a park officially has in a language, when it is not the everyday name (Hanshin Koshien Stadium for Koshien Stadium), in the year given or today; else undefined.

type ParkPeriod

interface ParkPeriod { from: number; to: number; points: FencePoint[]; }

A fence of some seasons, in feet.

type ParkPeriodData

interface ParkPeriodData { from: number; to: number; /** [angle from dead centre, distance in the park's unit], from the left field line to the right. */ points: [number, number][]; }

A fence that stood for some seasons of a past park: the figures an article dates, from the first season to the last.

function parkPointsIn

parkPointsIn(park: Park, year: number | undefined): FencePoint[]

A park's fence in a season: the period that names the season, else the park's last fence.

type ParkQuirk

type ParkQuirk = HillQuirk | OverhangQuirk | PointQuirk | CoverQuirk;

A quirk of a ballpark that its article describes with figures.

function parkQuirksIn

parkQuirksIn(park: Park, year?: number): ParkQuirk[]

The quirks a park has in a season: those the article dates for that season, else those of today.

const PARKS

PARKS: Readonly<Record<string, Park>>

Every park by its key: the thirty current MLB ballparks, forty-three past ones, the twelve NPB home parks, and eight secondary or past NPB grounds.

function parksByName

parksByName: (name: string | undefined) => Park[]

The parks that answer to a name, best match first: the everyday names, then the official and cited names and the sponsors', then the former names and the nicknames. A name is compared without case, accents, spaces or punctuation, in full-width or half-width, and in Japanese with hiragana or katakana and with or without the long vowel mark.

Example

import { parksByName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
console.log(parksByName("Camden Yards")[0]?.id, parksByName("甲子園")[0]?.id, parksByName("ハマスタ")[0]?.id);
// oriole-park-at-camden-yards hanshin-koshien-stadium yokohama-stadium

type ParkSource

interface ParkSource { title: string; revision: number; }

Where a park's figures were read: an article of Wikipedia at a revision.

function parkSourceUrl

parkSourceUrl(source: ParkSource, language?: "en" | "ja"): string

The address of the revision of an article that a park's figures were read from.

function parkUnit

parkUnit: (id: string | undefined) => Unit | undefined

The unit a park's figures were published in: metres for the parks of Japan (converted to feet once, in PARKS), feet for the rest. undefined for a key that is not a park.

type ParkWall

interface ParkWall extends Omit<ParkWallData, "height"> { height_ft: number; }

A stretch of the fence and how high it stands, in feet.

type ParkWallData

interface ParkWallData { /** The stretch's angles from dead centre: the left field line is -45 and the right field line 45 (our reading of the article's words, see docs/references.md). */ from: number; to: number; /** The height of the wall in the park's unit. */ height: number; /** A name the wall has in the article (`Green Monster`); a wall without one is named by its place in the field. */ label?: string; /** Whe…

A stretch of the fence and how high it stands, as the figures give it (the height in the park's unit).

function parkWallsIn

parkWallsIn(park: Park, year?: number): ParkWall[]

The stretches of fence a park has in a season, with their heights in feet: the heights the article dates for that season, else those of today.

type PlaceAlias

interface PlaceAlias { name: string; /** The language of the name: `en`, `ja`, `es`, `de`... */ lang: string; kind: PlaceNameKind; /** The first and the last year the name was in use, where a source dates it. */ from?: number; to?: number; /** Where the name was read: an article and its revision, or a Wikidata item and property (`Wikidata Q49136 P1448`). */ source?: string; /** For a `wikipedia` alias, the revision …

Another name of a place: a title, an earlier or a sponsor's name, a nickname.

type PlaceNameKind

type PlaceNameKind = "official" | "wikipedia" | "sponsor" | "former" | "nickname";

What an alias is. official is the name the owner or operator gives it (when it is not the everyday one), wikipedia the title of an article that is cited, sponsor a naming-rights name, former an earlier name and nickname a short form or a name people use.

function plainName

plainName(text: string): string

A name made fit to compare: width, case, accents, kana (hiragana is katakana), the old and new forms of the same sound (ヂ, ヅ, ヴ), the long vowel mark and every space, dot and mark of punctuation put aside, so that Estádio Maracanã and estadio maracana are one name, as are ヤンキー・スタジアム and ヤンキースタジアム.

type PointQuirk

interface PointQuirk extends QuirkBase { kind: "pole" | "flagpole" | "bullpen"; angle: number; feet: number; /** The height of a pole, where the article gives it. */ height_ft?: number; /** Whose bullpen it is, where the article says. */ side?: "home" | "visitors"; }

A foul pole, a flagpole or a bullpen at an angle and a distance from home plate.

type QuirkKind

type QuirkKind = "hill" | "overhang" | "pole" | "flagpole" | "bullpen" | "ivy" | "basket";

What a quirk of a ballpark is.

type WallPlace

type WallPlace = "outfield" | "left" | "leftCenter" | "center" | "deadCenter" | "rightCenter" | "right" | "leftToCenter" | "centerToRight" | "leftCorner" | "rightCorner";

Where a stretch of wall stands, in words: a key of the words of ballpark.sections (left, leftCorner, deadCenter, centerToRight...).

@johnmorrisdotca/kyogijo-stadiums/mount

containerAspect kyogijoElement KyogijoMount KyogijoReplay mountKyogijo MountSettings

function containerAspect

containerAspect(host: HTMLElement, breakpoint?: number): number | undefined

The container's width over its height, for orientation: "auto". A container with a height of its own (a card of a fixed size, a flex or grid cell) gives its real shape; one whose height follows what is drawn in it gives a tall shape when it is narrower than the breakpoint and a wide one otherwise.

function kyogijoElement

kyogijoElement(options: SurfaceOptions | ScoreOptions): SVGSVGElement

The drawing as an svg element, made by the browser's own parser, for code that appends nodes rather than sets text.

Example

``ts no-run import { kyogijoElement } from "@johnmorrisdotca/kyogijo-stadiums/mount"; document.body.append(kyogijoElement({ sport: "nba" })); ``

type KyogijoMount

interface KyogijoMount { /** Draw again with new options: a score that moved, a theme that changed. */ update(options: SurfaceOptions | ScoreOptions): void; /** The drawing, or `null` after `destroy`. */ svg(): SVGSVGElement | null; /** The orientation the drawing lies in now. */ orientation(): "landscape" | "portrait"; /** * Play tracking data on the surface: the players, the ball and the events of a timeline, with…

What mountKyogijo gives back.

type KyogijoReplay

type KyogijoReplay = ReplayController & { destroy(): void };

A replay on a mount: the controller of its clock, and destroy to take the replay away and leave the surface.

function mountKyogijo

mountKyogijo(host: HTMLElement, options: SurfaceOptions | ScoreOptions, settings?: MountSettings): KyogijoMount

Draw into a container, and keep it drawn: with orientation: "auto" the container is watched, and the drawing is laid out again, tall or wide, when its shape changes.

Example

``ts no-run import { mountKyogijo } from "@johnmorrisdotca/kyogijo-stadiums/mount"; const card = mountKyogijo(document.querySelector("#game")!, { sport: "nhl", orientation: "auto", away: { name: "Harbour City Gulls", score: 1 }, home: { name: "Prairie Ridge Bison", score: 0 }, status: "live", }); card.update({ sport: "nhl", orientation: "auto", away: { name: "Harbour City Gulls", score: 2 }, home: { name: "Prairie Ridge Bison", score: 0 } }); ``

type MountSettings

interface MountSettings { /** For `orientation: "auto"` on a container whose height follows its content: it is portrait when narrower than this many pixels. 560 by default. */ breakpoint?: number; /** Highlight a score that changes in `update`: a short pulse of the number, or a fade for a reader who prefers reduced motion. On by default. */ highlight?: boolean; }

What mountKyogijo can be told.

@johnmorrisdotca/kyogijo-stadiums/react

createKyogijo kyogijoHtml kyogijoProps KyogijoProps ReactLike

function createKyogijo

createKyogijo(React: ReactLike): (props: KyogijoProps) => unknown

A React component made from the React you give it. It draws the options, and with orientation: "auto" measures its own box and lays the drawing out tall or wide as the box changes.

Example

import { createKyogijo } from "@johnmorrisdotca/kyogijo-stadiums/react";
// With React: `const Kyogijo = createKyogijo(React);` and then `<Kyogijo sport="nhl" orientation="auto" />`.
// Here a stand-in for React, to show what comes out:
const Kyogijo = createKyogijo({ createElement: (type, props) => ({ type, props }), useRef: (initial) => ({ current: initial }), useState: (initial) => [initial, () => {}], useEffect: () => {} });
const element = Kyogijo({ sport: "nhl" }) as { type: string };
console.log(element.type);
// div

function kyogijoHtml

kyogijoHtml(options: SurfaceOptions | ScoreOptions): { __html: string; }

The drawing as the object React's dangerouslySetInnerHTML takes.

function kyogijoProps

kyogijoProps(options: SurfaceOptions | ScoreOptions): { dangerouslySetInnerHTML: { __html: string; }; "data-kyogijo": string; "data-orientation": "landscape" | "portrait"; }

Props for a div (or any element) that holds a drawing, to spread onto it: no component, no dependency on React. With orientation: "auto" and no aspect, the drawing is wide; use createKyogijo for a container that is measured.

Example

import { kyogijoProps } from "@johnmorrisdotca/kyogijo-stadiums/react";
const props = kyogijoProps({ sport: "nba", away: { name: "Harbour City Gulls", score: 108 }, home: { name: "Prairie Ridge Bison", score: 112 }, status: "final" });
console.log(props["data-kyogijo"], props.dangerouslySetInnerHTML.__html.startsWith("<svg"));
// nba true

type KyogijoProps

type KyogijoProps = (SurfaceOptions | ScoreOptions) & { containerClassName?: string; containerStyle?: Record<string, unknown>; breakpoint?: number };

The props of the component createKyogijo makes: the options, and the usual className and style of its div.

type ReactLike

interface ReactLike { createElement(type: string, props: Record<string, unknown> | null, ...children: unknown[]): unknown; useRef<T>(initial: T): { current: T }; useState<S>(initial: S): [S, (next: S) => void]; useEffect(effect: () => void | (() => void), deps?: unknown[]): void; }

The part of React the component uses; pass the React you have, and nothing here imports it.

@johnmorrisdotca/kyogijo-stadiums/element

defineKyogijoField EmblemRenderer KYOGIJO_ATTRIBUTES KYOGIJO_TAG KyogijoField optionsFromAttributes replaySettingsFromAttributes turnSettingsFromAttributes turnWanted useEmblems

function defineKyogijoField

defineKyogijoField(tag?: string): typeof KyogijoField

Register <kyogijo-field> (or the tag you give), once. Returns the class.

type EmblemRenderer

type EmblemRenderer = (id: string, options?: EmblemDrawOptions) => string;

A function that draws an emblem from its id as a document of SVG, and throws for an id it does not know: renderEmblem of @johnmorrisdotca/dougu-sports/emblems is one. The id may end in /metal, /line or /color, and the renderer reads that suffix.

const KYOGIJO_ATTRIBUTES

KYOGIJO_ATTRIBUTES: readonly ["sport", "league", "size", "theme", "language", "orientation", "park", "ground", "arena", "year", "detail", "margin", "stripes", "exact-lines", "uppercase", "pitch", "pitch-type", "half", "range", "range-end", "goals", "stripe-pattern", "corner-flags", "look", "line-width", "line-dash", "line-opacity", "landmarks", "walls", "quirks", "home", "away", "home-abbr", "away-abbr", "home-score…

The attributes the element reads.

const KYOGIJO_TAG

KYOGIJO_TAG: "kyogijo-field"

The tag of the element.

const KyogijoField

KyogijoField: typeof KyogijoField

function optionsFromAttributes

optionsFromAttributes(read: (name: string): string | null, base?: Partial<ScoreOptions>) => SurfaceOptions | ScoreOptions

The options an element's attributes (and its options attribute and property) make.

function replaySettingsFromAttributes

replaySettingsFromAttributes(read: (name: string): string | null, base?: ReplaySettings) => ReplaySettings

The settings of a replay an element's attributes make: loop, autoplay and controls="false" are switches, speed and trail numbers.

function turnSettingsFromAttributes

turnSettingsFromAttributes(read: (name: string): string | null, base?: TurnSettings) => TurnSettings

The settings of a 3D view an element's attributes make: turn-view names the viewpoint it opens on, turn-controls="false" and turn-uprights="false" leave the buttons and what stands on the surface out, turn-score="floor" leaves the score panel on the floor, turn-touch="turn" keeps every touch for the view.

function turnWanted

turnWanted: (read: (name: string) => string | null) => boolean

Whether an element's turn attribute asks for the 3D view: present, and not false.

function useEmblems

useEmblems(render: EmblemRenderer | undefined): void

Plug a renderer of ready-made emblems into kyogijo, for every drawing on the page: after this logo: "crest:shield" and emblem: "trophy:hockey-cup" are drawn. Call it once, at start-up. Without one, an id is an address and is refused. useEmblems(undefined) unplugs it.

Example

import { useEmblems } from "@johnmorrisdotca/kyogijo-stadiums";
import { renderEmblem } from "@johnmorrisdotca/dougu-sports/emblems";
useEmblems(renderEmblem);

@johnmorrisdotca/kyogijo-stadiums/layers

ArrowsLayer Bandwidth Bin binGrid BinInfo buildZoneSet clipNearer Comet ContourLayer contourRings convexHull delaunay DelaunayLayer Density densityAt densityGrid drawLayers FlowLayer Formation FORMATION_IDS formationById FormationLayer FormationPlayer FormationPosition FORMATIONS formationsOf FormationSport Grid HEAT_RAMP_NAMES HEAT_RAMPS HeatBase HeatBins HeatDatum HeatLabels HeatLegend HeatmapLayer HeatRamp HeatRampName HeatStatistic hexAt HexBin HexbinLayer hexBins hexCentre hexCorners HexOrientation HullLayer KYOGIJO_HEAT_STYLE KYOGIJO_TEAM_STYLE Layer LayerBase LayerKind layerTable LayerTeam LegendLayer LineDatum LinesLayer LineStyle LINEUP_CODES LineupLayer LineupPlayer MarkDatum MarkShape massThresholds median MoveDatum Named neighbours NetworkLayer NetworkNode niceTicks OUTCOMES OutcomeStyle Outline outlineOf Paint PassDatum PlayerDatum PointDatum PointsLayer PointStyle polygonArea polygonCentroid POSITION_NAMES positionName Pt rampColour reduceBin Reducer RenderedLayers renderLayers resolveRamp Sample Scale scottBandwidth SonarLayer StatisticName TableOptions TextDatum TextLayer TextStyle Triangulation triangulationEdges uniformEdges voronoiCells VoronoiLayer ZONE_SET_IDS ZONE_SETS ZoneContext ZoneSet ZoneSetId ZoneShape ZonesLayer

type ArrowsLayer

interface ArrowsLayer extends LayerBase { kind: "arrows"; data: readonly LineDatum[]; style?: LineStyle; }

type Bandwidth

type Bandwidth = number | readonly [number, number] | "scott";

The shape of a density's bandwidth.

type Bin

interface Bin { n: number; hits: number; sum: number; nv: number; low: number; high: number; values: number[] | undefined; }

One bin's accumulator.

function binGrid

binGrid(samples: readonly Sample[], xEdges: readonly number[], yEdges: readonly number[], keepValues?: boolean): Grid

Count samples into the rectangles between two lists of edges (binned_statistic_2d's job). A sample outside the edges is counted as outside.

type BinInfo

interface BinInfo { /** How many samples fell in the bin. */ count: number; /** How many of them were hits. */ hits: number; /** How many samples were binned in all. */ total: number; }

What a bin holds, and what a custom reducer is told.

function buildZoneSet

buildZoneSet(id: ZoneSetId, ctx: ZoneContext): ZoneSet

The zones of a set on the surface the context describes. Throws for a set that is not for the surface's sport.

function clipNearer

clipNearer(polygon: readonly Pt[], a: Pt, b: Pt): Pt[]

A convex polygon cut to the side of the line that is nearer a than b (Sutherland and Hodgman's clip against one half plane).

type Comet

interface Comet { /** The opacity at the start, 0 to 1. 0.05 by default. */ from?: number; /** Draw it as a shape that thickens towards the end, not as a stroke of one width. */ taper?: boolean; }

The look of a comet: a line that fades in along its length, and may also thicken.

type ContourLayer

interface ContourLayer extends HeatBase { kind: "contour"; /** The kernel's width, in the layer's units (one number, or one for each axis), or `scott` (the default: Scott's rule, each axis's spread times n^(-1/6)). */ bandwidth?: Bandwidth; /** A multiple of the bandwidth: under 1 follows the data more closely, over 1 smooths it. 1 by default. */ smooth?: number; /** The grid the density is computed on, nodes across…

A kernel density estimate of the marks, drawn as filled bands or lines (hockey_rink's contour and contourf, mplsoccer's kdeplot).

function contourRings

contourRings(d: Density, level: number): Pt[][]

The closed rings that bound the part of a density at or above level, by marching squares. The grid is padded with a border of nodes below every level, so every ring closes (a region that reaches the box's edge is closed just beyond it). The rings are in the density's own coordinates; a ring that bounds a hole runs the other way, so that an even-odd fill leaves the hole empty.

function convexHull

convexHull(points: readonly Pt[]): Pt[]

The convex hull of some points: its corners in order, counter-clockwise with y up (clockwise on a page, where y is down), starting at the least x. A point on an edge of the hull is not a corner. The same point twice is one. With no points the hull is empty, with one it is that point, and with two or with all on a line it is the two ends.

Example

import { convexHull } from "@johnmorrisdotca/kyogijo-stadiums/layers";
console.log(convexHull([[0, 0], [4, 0], [2, 0], [4, 3], [1, 1], [0, 3]]));
// [ [ 0, 0 ], [ 4, 0 ], [ 4, 3 ], [ 0, 3 ] ]

function delaunay

delaunay(input: readonly Pt[]): Triangulation

The Delaunay triangulation of some points: triangles whose circumcircle holds no other point, which cover the points' convex hull. Bowyer and Watson's method: each point is added by removing the triangles whose circumcircle holds it and joining it to the edge of the hole. The hull is kept by "ghost" triangles (an edge and a point at infinity) in place of a big triangle round everything, so that a point on or beyond the hull is handled exactly and no sliver of the hull is lost. Points are added from the least x, so the answer does not depend on the order given (up to which of four points on one circle are joined). Points that are not finite numbers are not allowed; a repeated point is one point.

Example

import { delaunay } from "@johnmorrisdotca/kyogijo-stadiums/layers";
const t = delaunay([[0, 0], [10, 0], [5, 8], [5, 3]]);
console.log(t.points.length, t.triangles.length / 3);
// 4 3

type DelaunayLayer

interface DelaunayLayer extends PlayersBase { kind: "delaunay"; /** One triangulation for each team (`team`, the default) or one for all the players. */ by?: "team" | "all"; /** Fill the triangles, not only draw their edges. */ triangles?: boolean; }

The Delaunay triangulation of the players: triangles whose circle holds no other player, a mesh of near neighbours.

type Density

interface Density { nx: number; ny: number; x: readonly [number, number]; y: readonly [number, number]; /** Row by row from the top: `z[j * nx + i]` at node `i`, `j`. The density integrates to 1 over the plane (per square of the samples' unit). */ z: Float64Array; /** The bandwidths used, in the samples' unit. */ bandwidth: [number, number]; max: number; }

A density on a grid of nodes, nx by ny, spaced evenly from the first to the last of x and of y.

function densityAt

densityAt(d: Density, x: number, y: number): number

The density's value at (x, y), by bilinear interpolation between the four nodes round it (0 outside the grid).

function densityGrid

densityGrid(samples: readonly Sample[], box: { x: readonly [number, number]; y: readonly [number, number]; }, nx: number, ny: number, bandwidth: readonly [number, number], weights?: readonly number[]): Density

A Gaussian kernel density estimate of weighted samples on a grid of nodes. A sample adds a bell of the given bandwidth to every node within four bandwidths of it (the bell's tail beyond is under a thousandth of its height), so the cost is the number of samples times a window of nodes and not the whole grid. The density is not folded back at the edges: what lies beyond the box is lost.

function drawLayers

drawLayers(options: SurfaceOptions | ScoreOptions, layers: readonly Layer[]): string

The layers of a drawing as one SVG group, in the surface's own frame, for a caller who composes the drawing's parts. Put it inside the surface's group (the one with the quarter turn, when the drawing is tall) and the marks lie on the surface, cut to its outline, with the text still upright. The same options and layers always give the same string; drawKyogijo draws exactly this when it is given layers.

Example

import { drawLayers } from "@johnmorrisdotca/kyogijo-stadiums";
const group = drawLayers({ sport: "nhl", id: "rink" }, [
  { kind: "points", name: "shots", data: [{ x: 169, y: 20.5, outcome: "goal", team: "home" }, { x: 150, y: 40, outcome: "shot", team: "home" }] },
]);
console.log(group.startsWith('<g class="ky-layers"'), group.includes("2 shots"));
// true true

type FlowLayer

interface FlowLayer extends LayerBase { kind: "flow"; data: readonly MoveDatum[]; /** The grid: a number of cells across the surface's longer side (square cells, 12 by default), `[across, down]`, or `{ x, y }` as for a heat map. */ bins?: number | readonly [number, number]; /** Leave out a cell with fewer movements than this. 2 by default. */ minCount?: number; /** The length of an arrow: `mean` (the default: in pro…

The average direction of the movements in each cell of a grid, as an arrow (a quiver plot).

type Formation

interface Formation { id: string; sport: FormationSport; /** The frame the positions are in: `opta` for soccer, `nhl-api`, `nba-stats`, `nfl-tracking`, `retrosheet`. */ frame: string; name: { en: string; ja: string }; /** What the formation is, in a sentence: the side of the ball, the strategy. */ about: { en: string; ja: string }; positions: readonly FormationPosition[]; /** Places beside the surface's play that a …

A formation: where each position stands. Points in the numbers of a registered frame, for a team that attacks the right end of the surface as it is drawn wide (a football formation: the offence going right, with the ball at midfield).

const FORMATION_IDS

FORMATION_IDS: readonly string[]

The ids of the formations, in the order of the table.

function formationById

formationById(id: string): Formation

A formation by id, or an error that lists the ids.

type FormationLayer

interface FormationLayer extends LayerBase { kind: "formation"; /** The id of a formation in `FORMATIONS` (`4-4-2`), or a formation of your own. */ formation: string | Formation; /** The team that colours the marks (`home`, `away`). */ team?: LayerTeam; /** Players for some or all of the positions. */ data?: readonly FormationPlayer[]; /** What is written in a mark: the `position`'s id (the default without players),…

A formation drawn on the surface: a labelled mark for each position, with the players on them when data has some.

type FormationPlayer

interface FormationPlayer extends MarkDatum { /** The id of the position the player stands on. */ position: string; number?: string | number; name?: string; }

A player to stand on a position.

type FormationPosition

interface FormationPosition { /** The position's id: `GK`, `LB`, `QB`. Unique within a formation. */ id: string; x: number; y: number; /** How big the mark of a lineup is here, against the others: 1 by default. A second line's marks are smaller. */ size?: number; }

A named place of a formation, in the numbers of the formation's frame (see Formation).

const FORMATIONS

FORMATIONS: Readonly<Record<string, Formation>>

The formations, by id.

function formationsOf

formationsOf: (sport: FormationSport) => Formation[]

The formations of one sport.

type FormationSport

type FormationSport = "soccer" | "nhl" | "nba" | "nfl" | "mlb" | "rugby" | "afl" | "cricket" | "volleyball" | "handball" | "waterpolo" | "fieldhockey" | "netball";

The sports that have formations.

type Grid

interface Grid { xEdges: number[]; yEdges: number[]; /** Row by row from the top: `bins[row * nx + column]`. */ bins: Bin[]; nx: number; ny: number; /** Samples that fell inside the grid, and the number that fell outside it. */ total: number; outside: number; }

A grid of bins.

const HEAT_RAMP_NAMES

HEAT_RAMP_NAMES: readonly HeatRampName[]

Names of the built-in ramps.

const HEAT_RAMPS

HEAT_RAMPS: Readonly<Record<HeatRampName, HeatRampDef>>

The built-in ramps. On a light page a sequential ramp runs from a pale colour to a dark one, because the more there is the more ink it takes; on a dark page it runs from near the ground to a bright one, because the more there is the more light it gives. A diverging ramp has its neutral middle at the ground's colour and its two ends at full strength.

type HeatBase

interface HeatBase extends LayerBase { data: readonly HeatDatum[]; /** What each cell or zone shows: `count` (the default), `sum`, `mean`, `median`, `max` or `min` of the marks' `value`, `percent` (a share of all the marks, 0 to 100), `rate` (the percent of the marks whose `outcome` is a success; see `success`) or a function `(values, { count, hits, total }) => number | null`. */ statistic?: HeatStatistic; /** The s…

What the heat layers take beyond a layer's places.

type HeatBins

interface HeatBins { /** A number of bins across, or a list of edges in the layer's coordinates. */ x?: number | readonly number[]; y?: number | readonly number[]; }

Bins on a grid: a number of bins, or the edges between them.

type HeatDatum

interface HeatDatum extends MarkDatum { x: number | null; y: number | null; }

A point to count: where it is, and optionally a number it carries (value: a distance, an expected-goals figure, a speed) and an outcome (made, goal) for the rate statistic.

type HeatLabels

interface HeatLabels { /** What to write, a line each: the `value`, the `name` of a zone, the `count` of marks in it, its `share` of all of them (a percent) or the `ratio` of hits to marks (`13/31`). The value by default. */ show?: readonly ("value" | "name" | "count" | "share" | "ratio")[]; /** Decimal places of the value. A whole number for a count, and one place for a percent below 10, by default. */ decimals?: n…

Where to write the number of a cell or zone, and which.

type HeatLegend

interface HeatLegend { /** Where it sits on the surface: `bottom` (the middle of the lower edge) by default, or a corner. A ballpark's default is its lower right, outside the foul lines. */ position?: "bottom" | "top" | "bottom-left" | "bottom-right" | "top-left" | "top-right"; /** The legend's heading. The name of the statistic by default. */ title?: string; /** About how many ticks. 5 by default. */ ticks?: number…

A colour bar with ticks, drawn on the surface.

type HeatmapLayer

interface HeatmapLayer extends HeatBase { kind: "heatmap"; /** The bins. A number is how many across the surface's longer side, with square cells; `[across, down]` for both; or `{ x, y }` each a count or edges. About 24 across by default. */ bins?: number | readonly [number, number] | HeatBins; /** Grid only part of the surface, in the layer's coordinates: the attacking half, say. The whole surface by default. */ ex…

A binned statistic on a grid laid over the surface (hockey_rink's heatmap, mplsoccer's bin_statistic with heatmap).

type HeatRamp

type HeatRamp = HeatRampName | readonly string[] | { light: readonly string[]; dark?: readonly string[] };

A ramp for a layer: the name of a built-in one, a list of colour stops (the same on a light and a dark page), or a light and a dark list. Stops are anything safeColour accepts.

type HeatRampName

type HeatRampName = "ember" | "ocean" | "mono" | "balance" | "tide";

The built-in ramps. The sequential ones go from the least to the most; the diverging ones from the lowest through a neutral middle to the highest.

type HeatStatistic

type HeatStatistic = StatisticName | Reducer;

A statistic: a name, or a function of a bin's values (the median is the median of them; a function gets them all).

function hexAt

hexAt(x: number, y: number, size: number, orientation: HexOrientation, ox?: number, oy?: number): [number, number]

The hexagon (axial q, r) that holds (x, y) on a grid of hexagons of circumradius size whose (0, 0) hexagon is centred at (ox, oy).

type HexBin

interface HexBin { q: number; r: number; cx: number; cy: number; bin: Bin; }

A hexagon with the bin it holds.

type HexbinLayer

interface HexbinLayer extends HeatBase { kind: "hexbin"; /** The distance from a hexagon's centre to its corner, in the layer's units. A twenty-eighth of the surface's length by default. */ size?: number; /** A corner up (`pointy`, the default) or an edge up (`flat`). */ orientation?: HexOrientation; }

A binned statistic on hexagons (hockey_rink's and mplsoccer's hexbin).

function hexBins

hexBins(samples: readonly Sample[], size: number, orientation: HexOrientation, origin: readonly [number, number], keepValues?: boolean): { cells: HexBin[]; total: number; }

Count samples into hexagons. The hexagons that hold at least one sample are returned, in the order of their rows (top to bottom, left to right).

function hexCentre

hexCentre(q: number, r: number, size: number, orientation: HexOrientation, ox?: number, oy?: number): [number, number]

The centre of hexagon (q, r).

function hexCorners

hexCorners(size: number, orientation: HexOrientation): [number, number][]

The six corners of a hexagon of circumradius size, as offsets from its centre, clockwise from the right (flat) or the lower right (pointy), with y down.

type HexOrientation

type HexOrientation = "pointy" | "flat";

How a hexagon sits: a corner up (pointy) or an edge up (flat).

type HullLayer

interface HullLayer extends PlayersBase { kind: "hull"; /** One hull for each team (`team`, the default) or one for all the players. */ by?: "team" | "all"; }

The convex hull of the players: the smallest convex shape that holds them, for each team.

const KYOGIJO_HEAT_STYLE

KYOGIJO_HEAT_STYLE: string

The rules of the heat layers (the ground under a heat map, its zones' and bands' outlines and the legend), kept apart so that a drawing without one does not carry them: a drawing that has a heat layer writes them in beside the layer, and KYOGIJO_STYLE has them for a page.

const KYOGIJO_TEAM_STYLE

KYOGIJO_TEAM_STYLE: string

The rules of the team layers (the cells, triangles and hulls, the roses of a sonar, the arrows of a flow field and the marks of a formation), kept apart like the heat layers' so that a drawing without one does not carry them: a drawing that has one writes them in beside the layer, and KYOGIJO_STYLE has them for a page.

type Layer

type Layer = PointsLayer | LinesLayer | ArrowsLayer | TextLayer | HeatmapLayer | HexbinLayer | ZonesLayer | ContourLayer | LegendLayer | NetworkLayer | FormationLayer | LineupLayer | VoronoiLayer | DelaunayLayer | HullLayer | FlowLayer | SonarLayer;

A layer of data on a surface.

type LayerBase

interface LayerBase { /** A name for the layer in ids and in the page; letters, digits and hyphens. */ id?: string; /** What the marks are, for the summary: `shots`, `passes`. */ name?: string; /** The accessible title of the layer. Made from the count and `name` when missing, such as "24 shots". */ title?: string; /** The accessible description. Made from the counts by outcome and team when missing. */ description?…

What every layer takes.

type LayerKind

type LayerKind = Layer["kind"];

The kinds of layer.

function layerTable

layerTable(layers: readonly Layer[], options?: TableOptions): string

An HTML table for each layer: the data the drawing shows, as text, for a screen reader or for a page that wants its numbers. The coordinates are the ones given (in the layer's frame and unit), and a caption says which. Hidden layers are in it too. Every cell is escaped.

Example

import { layerTable } from "@johnmorrisdotca/kyogijo-stadiums";
const html = layerTable([{ kind: "points", name: "shots", from: "nhl-api", data: [{ x: 69, y: 22, outcome: "goal", title: "Goal, 2nd period" }] }]);
console.log(html.includes("<th scope=\"col\">Outcome</th>"), html.includes("Goal, 2nd period"));
// true true

type LayerTeam

type LayerTeam = "home" | "away" | (string & {});

Which team a mark belongs to: home or away take the layer's team colours (--ky-layer-home, --ky-layer-away); any other text is a mark of no team.

type LegendLayer

interface LegendLayer extends Omit<LayerBase, "from" | "attacking" | "surfaceSize" | "anchors" | "limits"> { kind: "legend"; for?: string; ramp?: HeatRamp; domain?: readonly [number, number]; center?: number; steps?: number; position?: HeatLegend["position"]; ticks?: number; /** The opacity of the legend, 0 to 1. */ opacity?: number; /** Size and place the legend against this part of the surface, as `HeatLegend.regi…

A colour bar on its own: for another layer (for, the id of an earlier or later heat layer) or with a ramp and a domain of its own.

type LineDatum

interface LineDatum extends MarkDatum { x?: number | null; y?: number | null; x2?: number | null; y2?: number | null; /** A path through several points, `[x, y]` each, in place of `x`, `y`, `x2` and `y2`. */ points?: readonly (readonly [number, number])[]; /** How far the line bows, as a fraction of its length. Positive bows to the right of the way it goes as the surface is drawn wide. */ curve?: number; /** Make th…

A line or an arrow: from (x, y) to (x2, y2), or through points.

type LinesLayer

interface LinesLayer extends LayerBase { kind: "lines"; data: readonly LineDatum[]; style?: LineStyle; }

type LineStyle

interface LineStyle { /** The width of the stroke in the layer's units, or a scale from `value`. */ width?: number | Scale; scale?: number; color?: Paint; opacity?: number; /** `auto` (the default) dashes the lines of a hollow outcome. */ dash?: "auto" | "solid" | "dashed" | "dotted"; /** How far lines bow, as a fraction of their length. */ curve?: number; /** Draw lines as waves, for a carry: `true` for the usual w…

How lines and arrows look.

const LINEUP_CODES

LINEUP_CODES: Readonly<Record<FormationSport, Readonly<Record<string, readonly string[]>>>>

The positions a short code may stand for, by sport, in the order a player takes them: a code that is also an id of the formation is that place first.

type LineupLayer

interface LineupLayer extends LayerBase { kind: "lineup"; /** `home` (the default) or `away`: the team's colour, and the end it attacks (the right for `home`, the left for `away`) unless `attacking` says. A football lineup's `side` is only the colour: the offence goes right unless `attacking` says, and a defence faces it. Baseball's fielders do not turn. */ side?: "home" | "away"; /** The id of a formation in `FORMA…

A team's lineup drawn on the surface: its players placed by position code in a named formation, as discs or jerseys, each with its number in the mark and its name under it. Two teams are two layers, one for each side. A lineup is not cut to the surface's outline (a fielder at the foul line keeps his whole jersey) unless clip is "surface".

type LineupPlayer

interface LineupPlayer extends MarkDatum { /** The position's code: `GK`, `SS`, `C`, `QB`, `9`, `P4` (a volleyball court's zone). The formation's own ids, and the usual short ones (`CB` for a centre-back of either side, `D` for a defenceman, `OF` for an outfielder), as `LINEUP_CODES` lists. */ pos: string | number; name?: string; number?: string | number; /** A flag or crest for a small badge on the mark: an SVG as …

A player of a lineup, placed by a position's code: nothing else about where he stands is needed.

type MarkDatum

interface MarkDatum { /** Your id for the mark: written on it as `data-mark-id`, so that a listener can link it to a row of your table. The mark's index is written when there is none. */ id?: string | number; /** The tooltip, and what a screen reader reads for the mark. */ title?: string; /** `home`, `away` or another team. */ team?: LayerTeam; /** What happened, as a word of your own (`goal`, `miss`, `complete`). T…

What every mark can carry.

type MarkShape

type MarkShape = "circle" | "square" | "triangle" | "diamond" | "cross" | "x" | "star";

The shapes a point can be drawn as. A team's logo is logo.

function massThresholds

massThresholds(d: Density, masses: readonly number[]): number[]

The densities that bound the smallest regions holding the given shares of the mass (the highest-density regions), for a density on a grid: masses of 0.5 gives the density above which half of the mass lies.

function median

median(values: readonly number[]): number

The median of a list that is not empty.

type MoveDatum

interface MoveDatum extends MarkDatum { x: number | null; y: number | null; x2?: number | null; y2?: number | null; dx?: number | null; dy?: number | null; /** The mover, for a sonar of each player. */ id?: string | number; name?: string; number?: string | number; }

A movement: from (x, y) to (x2, y2), or the vector (dx, dy) from (x, y).

type Named

interface Named { en: string; ja: string; }

A name in both languages.

function neighbours

neighbours(t: Triangulation): number[][]

For each distinct point of a triangulation, the points it shares a triangle edge with, in increasing order.

type NetworkLayer

interface NetworkLayer extends LayerBase { kind: "network"; data: readonly PassDatum[]; /** Places of your own for some players, over the averages. */ nodes?: readonly NetworkNode[]; /** Leave out links of fewer passes than this. 1 by default. */ minPasses?: number; /** Draw a link for each direction, bowed apart and with a head, and not one for the pair. */ directed?: boolean; /** The width of a node (a share of th…

A pass network: a node for each player at the average place of the passes he made and received, sized by touches, and a link for each pair sized by passes.

type NetworkNode

interface NetworkNode { id: string | number; x: number; y: number; team?: LayerTeam; name?: string; number?: string | number; }

A player of a pass network whose place is given and not worked out from the passes.

function niceTicks

niceTicks(low: number, high: number, count?: number): number[]

"Nice" tick values: about count of them, at 1, 2, 2.5 or 5 times a power of ten, covering [low, high] within it.

const OUTCOMES

OUTCOMES: Readonly<Record<string, OutcomeStyle>>

How the standard outcomes look. A word that is not here draws as a filled mark; style.outcomes of a layer adds words and changes these. A hollow point is drawn open and a hollow line is dashed, so that an outcome is never told by colour alone.

type OutcomeStyle

interface OutcomeStyle { /** Draw a point hollow and a line dashed. */ hollow?: boolean; /** A shape for this outcome, over the layer's. */ shape?: MarkShape; /** A multiple of the mark's size: 1.4 makes a goal stand out. */ scale?: number; }

How an outcome looks: a hollow mark (or a dashed line) is a miss, a failed pass or a shot that did not score.

type Outline

interface Outline { /** The path of the playing surface's edge: the boards for a rink, the lines of a court, the fence and the foul lines for a ballpark. */ d: string; box: { x: number; y: number; w: number; h: number }; /** True when a point (in the drawing's frame, feet) is on the surface, or on its edge. */ contains(x: number, y: number): boolean; }

A surface's outline as a path, with the box round it.

function outlineOf

outlineOf(o: Pick<SurfaceOptions, "sport" | "park" | "fence" | "units" | "pitch" | "league" | "year" | "sheet" | "net"> & Partial<Pick<SurfaceOptions, "field" | "detail" | "pool" | "athletics" | "skating" | "budo" | "dojo" | "arena">>): Outline

The outline of the surface the options draw.

type Paint

type Paint = | string | { by: "team"; home?: string; away?: string; other?: string } | { by: "value"; stops: readonly string[]; domain?: readonly [number, number]; missing?: string } | { by: "outcome"; colors: Readonly<Record<string, string>>; other?: string };

A colour, or a rule for choosing one. A text is any colour safeColour accepts. by: "team" uses the home and away colours of the layer, by: "value" runs through stops as a datum's value goes from the least to the greatest of the domain, and by: "outcome" looks the datum's outcome up in colors. A colour a datum carries itself (color) always wins.

type PassDatum

interface PassDatum extends MarkDatum { /** The id of the passer and of the receiver (a number or a name; the same id is the same player). */ from: string | number; to: string | number; /** Where the pass was made from, and where it was received. */ x?: number | null; y?: number | null; x2?: number | null; y2?: number | null; /** The names for the labels and the table, by id. The last one given for an id is used. */…

A pass: who passed to whom, from where to where. The places are the passer's and, if given, the receiver's.

type PlayerDatum

interface PlayerDatum extends MarkDatum { x: number | null; y: number | null; /** The player's id, name and shirt number, for a label and for the table. */ id?: string | number; name?: string; number?: string | number; }

A player at a place. team (home, away or another) is what colours the player's cell, triangles and hull.

type PointDatum

interface PointDatum extends MarkDatum { x: number | null; y: number | null; /** A shape for this mark alone, or `logo` for the logo of its team (or its own `logo`). */ shape?: MarkShape | "logo"; /** The width of the mark, in the layer's units. */ size?: number; /** The address of a logo, for the shape `logo`. */ logo?: string; }

A point to mark.

type PointsLayer

interface PointsLayer extends LayerBase { kind: "points"; data: readonly PointDatum[]; style?: PointStyle; }

type PointStyle

interface PointStyle { shape?: MarkShape | "logo"; /** The width of a mark in the layer's units, or a scale from `value`. A default that follows the size of the surface. */ size?: number | Scale; /** A multiple of the default size. */ scale?: number; /** The colour of the fill. The home and away colours for marks that carry a team, the layer colour for the rest. */ fill?: Paint; /** The colour of the edge. A pale or…

How points look.

function polygonArea

polygonArea(polygon: readonly Pt[]): number

The area of a polygon given by its corners (any orientation); 0 for fewer than three.

function polygonCentroid

polygonCentroid(polygon: readonly Pt[]): Pt | undefined

The centre of mass of a polygon's area, or the mean of its corners when it has none (a point, a line). undefined for no corners.

const POSITION_NAMES

POSITION_NAMES: Readonly<Record<FormationSport, Readonly<Record<string, Named>>>>

The names of the positions of each sport, by the id a formation uses for them.

function positionName

positionName(sport: FormationSport, id: string, language: "en" | "ja"): string

The name of a position in a language: the sport's name for its id, or the id itself when the sport has none.

type Pt

type Pt = readonly [number, number];

A place, [x, y].

function rampColour

rampColour(stops: readonly string[], t: number): string | undefined

A colour a fraction t (0 to 1) of the way through stops, as a hex colour, or the nearest stop when one cannot be read without a browser (var(--x), a name that is not a common one). Stops that are not colours are ignored; with none left, undefined.

function reduceBin

reduceBin(statistic: StatisticName | Reducer, bin: Bin, total: number): number | null

The value of a bin under a statistic, or null when it has none (an empty bin; a mean of samples that carry no value).

type Reducer

type Reducer = (values: readonly number[], info: BinInfo) => number | null;

A custom statistic: the finite values of a bin's samples (an empty list when none had one), and the bin's counts. Return null for a bin that has no value.

type RenderedLayers

interface RenderedLayers { svg: string; summaries: string[]; }

The layers, drawn, and a sentence for each.

function renderLayers

renderLayers(o: SurfaceOptions | ScoreOptions, layers: readonly Layer[] | undefined, turned?: boolean, range?: Box, upright?: number, zone?: Env["zone"]): RenderedLayers

The layers of a drawing as markup, and a sentence for each. turned says whether the surface group is turned a quarter.

function resolveRamp

resolveRamp(ramp: HeatRamp | undefined, fallback?: HeatRampName): ResolvedRamp

The ramp a layer asks for; ember when it asks for none (fallback for a diverging statistic).

type Sample

interface Sample { x: number; y: number; value?: number | null; hit?: boolean; /** Which input it was, for a caller who wants its tooltip. */ index?: number; }

A point to count: where it is, a number it carries (optional) and whether it counts as a success (a goal, a made shot).

type Scale

interface Scale { domain?: readonly [number, number]; range: readonly [number, number]; /** For a size: scale the area and not the width, so that twice the value covers twice the ground. On by default. */ area?: boolean; }

A number mapped to another range: domain is the values (the data's least and greatest when missing) and range what they become.

function scottBandwidth

scottBandwidth(xs: readonly number[], ys: readonly number[], weights?: readonly number[]): [number, number]

Scott's rule for a density in two dimensions: each axis's bandwidth is its standard deviation times n^(-1/6), with n the number of samples (the effective number, when they have weights). In the same units as the samples. Silverman's rule is the same number in two dimensions.

type SonarLayer

interface SonarLayer extends LayerBase { kind: "sonar"; data: readonly MoveDatum[]; /** What a rose is of: `all` (one rose, at the middle of the movements' starts; the default), `player` (one for each `id`, at the average start of his), `grid` (one for each cell of a grid) or `zone` (one for each zone of `zones`). */ by?: "all" | "player" | "grid" | "zone"; /** How many directions a rose is cut into. 12 by default. …

A rose for each group of movements: how many go in each direction, or how long they are on average.

type StatisticName

type StatisticName = "count" | "sum" | "mean" | "median" | "percent" | "max" | "min" | "rate";

The statistics of a bin. percent is each bin's share of all the samples (0 to 100); rate is the share of a bin's samples that are hits (0 to 100).

type TableOptions

interface TableOptions { language?: "en" | "ja"; /** The most rows to write for one layer. 500 by default; the caption says when rows are left out. */ maxRows?: number; /** A class for each table. `ky-table` by default. */ className?: string; /** The drawing's options (at least `sport`, and `park`, `pitch`, `units` and `language` where they apply): needed for the tables of heat maps, hexagon maps, zone charts and co…

What layerTable takes.

type TextDatum

interface TextDatum extends MarkDatum { x: number | null; y: number | null; text: string | number; /** The height of the letters, in the layer's units. */ size?: number; /** Turn the text this many degrees, clockwise, as the eye sees it. The text stays upright when the surface is turned. */ rotate?: number; anchor?: "start" | "middle" | "end"; }

A word or a number to write.

type TextLayer

interface TextLayer extends LayerBase { kind: "text"; data: readonly TextDatum[]; style?: TextStyle; }

type TextStyle

interface TextStyle { /** The height of the letters in the layer's units. A default that follows the size of the surface. */ size?: number; color?: Paint; weight?: number; anchor?: "start" | "middle" | "end"; /** Outline the letters in the halo colour (`--ky-halo`) so that they read on any ground. On by default. */ halo?: boolean; opacity?: number; }

How text looks.

type Triangulation

interface Triangulation { /** The distinct points, in the order they were first given. */ points: Pt[]; /** Three indices into `points` for each triangle, flat: `triangles[3 * k]`, `[3 * k + 1]`, `[3 * k + 2]`. Empty for fewer than three points and for points all on a line. */ triangles: number[]; /** For each input point, the index in `points` of the point it is (a repeated point is the first one). */ of: number[];…

A triangulation: the distinct points, the triangles as triples of indices into them (counter-clockwise with y up), and where each input point went.

function triangulationEdges

triangulationEdges(t: Triangulation): [number, number][]

The edges of a triangulation, each once, as pairs of indices into its points with the smaller first, in order.

function uniformEdges

uniformEdges(a: number, b: number, n: number): number[]

n equal steps from a to b, as the n + 1 edges that bound them.

function voronoiCells

voronoiCells(points: readonly Pt[], box: { x: number; y: number; w: number; h: number; }): Pt[][]

The Voronoi cells of some points inside a box: for each input point, the corners of the part of the box nearer to it than to any other point, in order. The cells tile the box. A point given twice has the cell on its first appearance and an empty one on its second. With one point the cell is the box; with points all on a line the cells are strips. The neighbours that cut a cell are the triangulation's, so a cell is made from a handful of cuts and not from every other point.

Example

import { voronoiCells } from "@johnmorrisdotca/kyogijo-stadiums/layers";
const cells = voronoiCells([[2, 5], [8, 5]], { x: 0, y: 0, w: 10, h: 10 });
console.log(cells[0].length, cells[1].map((p) => p.join(",")).join(" "));
// 4 5,0 10,0 10,10 5,10

type VoronoiLayer

interface VoronoiLayer extends PlayersBase { kind: "voronoi"; }

The part of the surface nearest each player, coloured by team: who controls the space (the Voronoi diagram of the players, clipped to the surface).

const ZONE_SET_IDS

ZONE_SET_IDS: readonly ZoneSetId[]

const ZONE_SETS

ZONE_SETS: Readonly<Record<ZoneSetId, { sports: readonly Sport[]; title: string; source: string; }>>

Every zone set, with the sports it is for, the source of its definition and its title.

type ZoneContext

interface ZoneContext { o: Pick<SurfaceOptions, "sport" | "pitch" | "units"> & Partial<Pick<SurfaceOptions, "league" | "net" | "field" | "pool" | "athletics" | "skating" | "budo" | "dojo">>; outline: Outline; /** The way the team attacks as the surface is drawn wide: names the sides and counts the bands from the team's own end. `right` by default. */ direction: "left" | "right"; /** `left` or `right` for one end of …

What a zone set needs to know about the drawing and the layer.

type ZoneSet

interface ZoneSet { id: ZoneSetId; zones: ZoneShape[]; /** The index of the zone a point belongs to, or -1 when it is in none. */ which(x: number, y: number): number; }

A set of zones. Where zones overlap, the later one is on top, and a point belongs to the last zone that has it.

type ZoneSetId

type ZoneSetId = "field-thirds" | "cricket-ground" | "soccer-positional" | "soccer-thirds" | "soccer-channels" | "soccer-18" | "nba-shot" | "nhl-slot" | "nhl-home-plate" | "baseball-spray" | "baseball-retrosheet" | "football-pass" | "athletics-track" | "skating-track" | "budo-areas" | "curling-house" | "net-court" | "wall-court" | "roundnet-rings" | "pool-lanes" | "water-polo-areas" | "pool-thirds";

The zone sets.

type ZoneShape

interface ZoneShape { /** A name for the zone in the data (`restricted-area`, `wing-left-3`), the same in every language. */ id: string; /** The name to show, in the drawing's language. */ name: string; /** The zone's outline as a path, in the drawing's frame (feet). */ d: string; /** Where its label goes. */ label: Pt; /** About how wide and how high a label can be without leaving the zone, in feet. */ room?: Pt; /…

One zone: its shape on the surface and whether a point lies in it.

type ZonesLayer

interface ZonesLayer extends HeatBase { kind: "zones"; /** The set of zones: `soccer-positional`, `soccer-thirds`, `soccer-channels`, `soccer-18`, `nba-shot`, `nhl-slot`, `nhl-home-plate`, `baseball-spray`, `baseball-retrosheet` or `football-pass`. */ zones: ZoneSetId; /** The way the team attacks as the surface is drawn wide: names the sides and counts the soccer bands from the team's own end. `right` by default. *…

A statistic by named zone of the surface.

@johnmorrisdotca/kyogijo-stadiums/frames

Attacking attackingIn convert convertLength ConvertOptions convertPoint convertPoints Ends flipEnds Frame frameByName FrameMap FRAMES framesOf FrameUnit fromSurface Landmark Mapper piecewise PlaceOptions Point registerFrame rescale Rescaler RETROSHEET_ZONES SOCCER_PROVIDER_IDS SOCCER_PROVIDERS soccerFrame SoccerFrame soccerLandmarks SoccerProvider SoccerProviderId span SurfaceSize toSurface Unit zoneAt zoneToPoint

type Attacking

type Attacking = "left" | "right";

The end a team is attacking: the end to the right of the surface as it is drawn wide, or the left.

function attackingIn

attackingIn(sport: string, period: number, first: Attacking, changedAfter?: readonly number[]): Attacking

The end a team attacks in a period, given the end it attacked in the first: hockey changes ends every period, football after the first and the third quarters, basketball at the half (overtime keeps the half's ends). For a period a rule does not settle, or a sport with no rule here, pass changedAfter: the periods after which the teams change ends.

Example

import { attackingIn } from "@johnmorrisdotca/kyogijo-stadiums/frames";
console.log([1, 2, 3].map((period) => attackingIn("nhl", period, "right")));
// [ 'right', 'left', 'right' ]

function convert

convert(from: Frame | string, to: Frame | string, options?: ConvertOptions): (points: readonly Point[]) => [number, number][]

A function that turns points from one provider's frame into another's through the surface. Both frames are of one sport.

Example

import { convert } from "@johnmorrisdotca/kyogijo-stadiums/frames";
// HockeyTech's canvas to the NHL API's feet: its middle, (300, 150), is centre ice.
const toNhl = convert("hockeytech", "nhl-api");
console.log(toNhl([[300, 150], [600, 0]]));
// [ [ 0, 0 ], [ 100, 42.5 ] ]

function convertLength

convertLength(value: number, from: Unit, to: Unit): number

A length in another unit.

Example

import { convertLength } from "@johnmorrisdotca/kyogijo-stadiums/geometry";
console.log(convertLength(100, "yd", "ft"), convertLength(200, "ft", "m").toFixed(2));
// 300 60.96

type ConvertOptions

interface ConvertOptions { /** The surface's size when a frame is variable. */ size?: SurfaceSize; /** The end the points' team attacks in the first frame's numbers, and the end it is to attack in the second's. Right for both when missing. */ attacking?: Attacking; toAttacking?: Attacking; }

What convert takes besides the two frames.

function convertPoint

convertPoint(point: Point, from: Unit, to: Unit): [number, number]

A point in another unit.

function convertPoints

convertPoints: (points: readonly Point[], from: Frame | string, to: Frame | string, options?: ConvertOptions) => [number, number][]

Points converted from one frame to another at once: convert(from, to, options)(points).

type Ends

type Ends = | "fixed" // the numbers are where a point is on the surface: which end a team attacks varies (the NHL's x, nflverse's x) | "shooter" // the numbers are measured from the basket or goal being attacked, so a half-court point lies at the attacked end | "none";

The ends of a surface and what a frame says about them.

function flipEnds

flipEnds(points: readonly Point[], size: { length: number; width: number; }): [number, number][]

Turn points a half turn about the middle of a surface: where they are when the same play is drawn attacking the other end.

Example

import { flipEnds } from "@johnmorrisdotca/kyogijo-stadiums/frames";
console.log(flipEnds([[169, 20.5]], { length: 200, width: 85 }));
// [ [ 31, 64.5 ] ]

type Frame

interface Frame { /** The key it is registered under: `nhl-api`. */ name: string; /** What it is called in words: "NHL API play-by-play". */ title: string; /** The surface it is for: one of `SPORTS`, or another sport's name for a frame of a surface made elsewhere. */ sport: Sport | (string & {}); /** The unit of its numbers. */ unit: FrameUnit; /** The range of x and of y the provider uses, `[least, greatest]`. */ x…

A provider's coordinate frame.

function frameByName

frameByName(name: string): Frame

The frame with a name, or an error that lists the names.

type FrameMap

interface FrameMap { x: readonly Landmark[]; y: readonly Landmark[]; /** The frame's x runs across the surface and its y along it (the NBA's `LOC_X` is across the court). The landmarks of x then give the surface's y. */ swap?: boolean; }

How a frame lies on a surface: the landmarks of its x axis and of its y axis. Two landmarks make a straight, scaled mapping; more make one that bends at each (a pitch whose penalty box is drawn at the provider's own figures, whatever the pitch's size). Values beyond the first and last landmark continue on the nearest line.

const FRAMES

FRAMES: Record<string, Frame>

Every frame by its name. registerFrame adds to it; the providers in frames.providers.ts are in it from the start.

function framesOf

framesOf: (sport: string) => Frame[]

The frames of a sport, in the order they were registered.

type FrameUnit

type FrameUnit = Unit | "px" | "tenth-ft" | "percent" | "fraction";

The unit of a frame's numbers: a length, or what a provider's own scale is.

function fromSurface

fromSurface(points: readonly Point[], options: PlaceOptions & { to: Frame | string; }): [number, number][]

Places on the surface as points in a provider's frame: the undoing of toSurface, with the same options. The places are in units (feet by default).

Example

import { fromSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames";
// The right end-zone face-off dot of an NHL rink, as the NHL API writes it.
console.log(fromSurface([[169, 20.5]], { to: "nhl-api" }));
// [ [ 69, 22 ] ]

type Landmark

type Landmark = readonly [frame: number, surface: number];

A value on one axis of a provider's frame and the place it falls on the surface, in feet.

type Mapper

interface Mapper { /** A value on the provider's axis as a place on the surface. */ forward(value: number): number; /** A place on the surface as a value on the provider's axis. */ inverse(value: number): number; }

A straight mapping between two values and back.

function piecewise

piecewise(landmarks: readonly Landmark[]): Mapper

A piecewise-linear mapper through landmarks: between two landmarks a straight line, beyond the ends the line of the nearest pair. The frame values must differ and the surface values must all rise or all fall, or the mapping could not be undone. Reuse it for a frame whose axis bends at its landmarks.

Example

import { piecewise } from "@johnmorrisdotca/kyogijo-stadiums/frames";
// A pitch drawn 0 to 100 whose halfway line is at 52.5 metres of a 105-metre pitch.
const along = piecewise([[0, 0], [50, 172.2], [100, 344.5]]);
console.log(along.forward(25), along.inverse(172.2));
// 86.1 50

type PlaceOptions

interface PlaceOptions { /** The surface's size when the frame is variable, or to use another size than the sport's. */ size?: SurfaceSize; /** The end the points' team is attacking, as the surface is drawn wide: `left` turns the points a half turn about the middle so that the team attacks right. */ attacking?: Attacking; /** The unit of the surface's numbers: feet when missing. Anchors and limits are in it too. */ …

Where points are put, and in what: what every function of this file takes besides the points.

type Point

type Point = readonly [x: number, y: number];

A point or a vector: [x, y].

function registerFrame

registerFrame(frame: Frame, replace?: boolean): Frame

Add a frame to FRAMES so that toSurface, fromSurface and convert take it by name. A name already taken is an error unless replace says it is meant. Returns the frame.

Example

import { FRAMES, registerFrame, toSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames";
registerFrame({
  name: "my-rink-grid", title: "A rink in tenths of a foot", sport: "nhl", unit: "tenth-ft", x: [-1000, 1000], y: [-425, 425],
  origin: "centre ice", yDirection: "up", size: "fixed", ends: "fixed", source: "my own data",
  landmarks: { x: [[-1000, 0], [1000, 200]], y: [[-425, 85], [425, 0]] },
});
console.log(toSurface([[0, 0]], { from: "my-rink-grid" })[0], Object.keys(FRAMES).includes("my-rink-grid"));
// [ 100, 42.5 ] true

function rescale

rescale(from: SoccerProviderId | SoccerFrame, to: SoccerProviderId | SoccerFrame, pitch?: PitchMetres): Rescaler

The map from one coordinate system to another, piecewise linear between the landmarks (it is built on piecewise of @johnmorrisdotca/kyogijo-stadiums/frames), so that a point on a line of the pitch (the goal line, the goal area, the penalty mark, the penalty area, the halfway line, the touchline, the posts) lands on the same line in the other system. Between two landmarks the map is linear; beyond the goal lines and the touchlines (a ball out of play) it runs on along the last piece. Give a pitch size in metres for a provider whose numbers follow the pitch (Tracab, SkillCorner, Second Spectrum, Metrica, international). The same map, through the surface and in feet, is convert of the frames module.

Example

import { rescale } from "@johnmorrisdotca/kyogijo-stadiums";
const toStatsBomb = rescale("opta", "statsbomb");
// The middle of Opta's penalty-box line, 17 units from the goal line, is on StatsBomb's, 18 yards from it.
const [x, y] = toStatsBomb(100 - 17, 50);
console.log(x, y);
// 102 40

type Rescaler

interface Rescaler { (x: number, y: number): [number, number]; /** Only the x of a point. */ x(value: number): number; /** Only the y of a point. */ y(value: number): number; /** The map the other way: from the second provider's coordinates to the first's. */ inverse: Rescaler; from: SoccerFrame; to: SoccerFrame; }

A map from one provider's coordinates to another's.

const RETROSHEET_ZONES

RETROSHEET_ZONES: Readonly<Record<string, Point>>

The place, in feet from home plate (x towards right field, y out), that each of Retrosheet's hit-location codes stands for: the centre of its sector.

const SOCCER_PROVIDER_IDS

SOCCER_PROVIDER_IDS: readonly SoccerProviderId[]

The ids of the providers, in the order a menu lists them.

const SOCCER_PROVIDERS

SOCCER_PROVIDERS: Readonly<Record<SoccerProviderId, SoccerProvider>>

The providers. Where a provider's own page gives the figures (StatsBomb's and Wyscout's diagrams, Impect's glossary, Metrica's and SkillCorner's READMEs) they are those; Opta's, Tracab's and Second Spectrum's specifications are not public, so theirs are the figures the open-source libraries mplsoccer, ggsoccer and kloppy agree on, which docs/sources.md says.

function soccerFrame

soccerFrame(frame: SoccerProviderId | SoccerFrame | string | undefined, pitch?: PitchMetres): SoccerFrame | undefined

The frame of a provider (by id), or a frame of your own as it is, for a pitch of this size in metres (which matters only to a provider whose numbers follow the pitch). An id that is not a provider, or a frame that cannot be drawn, gives undefined.

Example

import { soccerFrame } from "@johnmorrisdotca/kyogijo-stadiums";
const opta = soccerFrame("opta");
console.log(opta?.xRight, opta?.yTop);
// 100 100

type SoccerFrame

interface SoccerFrame { id: string; name: string; /** The unit of the numbers, for people. */ unit: string; xLeft: number; xRight: number; yTop: number; yBottom: number; /** How far the penalty area reaches from the goal line, and half its width. */ penaltyDepth: number; penaltyHalfWidth: number; /** How far the goal area reaches, and half its width. */ goalAreaDepth: number; goalAreaHalfWidth: number; /** The penal…

A pitch as a data provider writes it down. Everything is in the provider's own unit.

xLeft and xRight are x at the two goal lines, with the team attacking left to right; yTop and yBottom are y at the touchlines as they lie in a picture with the first at the top. So the frame says both where the origin is and which way y runs: Opta (y up) has yTop: 100, yBottom: 0; StatsBomb (y down) has yTop: 0, yBottom: 80. The depths are measured from the goal line along x, the half widths from the middle of the pitch along y.

function soccerLandmarks

soccerLandmarks(f: SoccerFrame): { x: number[]; y: number[]; }

Where the landmarks of a frame stand on each axis, in its units, from the left goal line to the right and from the top touchline to the bottom.

type SoccerProvider

interface SoccerProvider { id: SoccerProviderId; name: string; /** The unit of its numbers, for people. */ unit: string; /** The unit as the frames registry names it. */ frameUnit: FrameUnit; /** Where (0, 0) lies, in words. */ origin: string; /** True when its extents follow the size of the pitch (Tracab's centimetres from the centre): the frame is made for a size. */ sized: boolean; /** The page of the provider's …

A provider: its name, the page its figures were read from, and its frame (which for some depends on the size of the pitch).

type SoccerProviderId

type SoccerProviderId = "opta" | "statsbomb" | "wyscout" | "tracab" | "metrica" | "skillcorner" | "secondspectrum" | "impect" | "uefa" | "international";

The providers, by the id the options use.

function span

span: (frameA: number, frameB: number, surfaceA: number, surfaceB: number) => Landmark[]

Two landmarks as a straight mapping: span(0, 600, 0, 200) puts 0 at 0 feet and 600 at 200 feet.

type SurfaceSize

interface SurfaceSize { length: number; width: number; unit?: Unit; }

The size of a surface that varies, in a unit (feet by default). length runs from end to end, width across.

function toSurface

toSurface(points: readonly Point[], options: PlaceOptions & { from: Frame | string; }): [number, number][]

Points in a provider's frame as places on the surface: feet in the drawing's own landscape frame (x along the surface from the left, y across it from the top; a ballpark's x is across it, y the distance out from home plate, minus). Pass units for another unit, attacking: "left" to turn points that attack the left a half turn so that they attack the right, anchors for landmarks of your own and limits to keep them on the surface.

Example

import { toSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames";
// The NHL API's centre ice is the middle of the rink; its (69, 22) is the right end-zone face-off dot.
console.log(toSurface([[0, 0], [69, 22]], { from: "nhl-api" }));
// [ [ 100, 42.5 ], [ 169, 20.5 ] ]

type Unit

type Unit = "ft" | "in" | "yd" | "m" | "cm" | "mm";

A unit of length: feet, inches, yards, metres, centimetres or millimetres.

function zoneAt

zoneAt(point: Point): string

The code of the Retrosheet zone whose centre is nearest a point, in feet from home plate (x to the right, y out): the zone the point most probably lay in. Zone centres are not boundaries, so a point near the edge of two may go to either.

Example

import { zoneAt, zoneToPoint } from "@johnmorrisdotca/kyogijo-stadiums/frames";
console.log(zoneAt([0, 335]), zoneToPoint("8D"));
// 8D [ 0, 330 ]

function zoneToPoint

zoneToPoint(code: string): Point | undefined

The place a Retrosheet hit-location code stands for, or undefined for a code not on the diagram. The code is the location alone (8D), not the batted-ball type before it (F, L, G, P) nor a leading B for a bunt.

@johnmorrisdotca/kyogijo-stadiums/leagues

AFL_GROUND AFL_GROUNDS AFL_LEAGUES AflFigures AflGround AflLeague aflOf ATHLETICS_LEAGUES AthleticsFigures AthleticsLeague BADMINTON_LEAGUES BadmintonFigures BadmintonLeague BaseballLeague BasketballLeague BWF_BADMINTON chooseLeague CourtFigures CourtMarks courtOf CRICKET_LEAGUES CricketFigures CricketLeague cricketOf CURLING_CANADA_SHEET CURLING_LEAGUES CurlingLeague DiamondFigures diamondOf Family FAMILY_OF FIELD_HOCKEY_LEAGUES FieldHockeyFigures FieldHockeyLeague fieldHockeyOf fieldOf FIH_PITCH FIP_PADEL FITEQ_TEQBALL FIVB_BEACH FIVB_INDOOR fixed FootballLeague FreeZone FreeZones GAA_FOOTBALL_FIELD GAA_HURLING_FIELD GAELIC_LEAGUES GaelicFigures GaelicLeague gaelicOf HANDBALL_LEAGUES HandballFigures HandballLeague handballOf hasHistory HockeyLeague ICC_MEN_GROUND ICC_WOMEN_GROUND IHF_COURT IRF_RACQUETBALL IRF_ROUNDNET IRL_FIELD ISTAF_SEPAK_TAKRAW ITF_TENNIS ITTF_TABLE LACROSSE_LEAGUES LacrosseFigures LacrosseLeague lacrosseOf League LEAGUE_SOURCES leagueAt LeagueAt leagueAtOf leagueById LeagueChange leagueChanges leagueFigures leagueIds LEAGUES leagueSeason leagueSize LeagueSource LeagueSpan MCC_GROUND MLB_DIAMOND Named NBA_COURT NCAA_MEN_FIELD NCAA_WOMEN_FIELD NETBALL_LEAGUES NetballFigures NetballLeague NetFigures NetLeague netOf NetSport NHL_RINK NLL_FLOOR NRL_FIELD PADEL_LEAGUES PadelFigures PadelLeague PICKLEBALL_LEAGUES PickleballFigures PickleballLeague PLL_FIELD RACQUETBALL_LEAGUES RacquetballFigures RacquetballLeague RinkFigures rinkOf ROUNDNET_LEAGUES RoundnetFigures RoundnetLeague RUGBY_LEAGUES RugbyFigures RugbyLeague rugbyOf SEPAK_TAKRAW_LEAGUES SepakTakrawFigures SepakTakrawLeague SheetFigures sheetOf span Span SQUASH_LEAGUES SquashFigures SquashLeague TABLE_TENNIS_LEAGUES TableTennisFigures TableTennisLeague TENNIS_LEAGUES TennisFigures TennisLeague TEQBALL_LEAGUES TeqballFigures TeqballLeague USAP_PICKLEBALL VARIANTS VOLLEYBALL_LEAGUES VolleyballFigures VolleyballLeague WA_SHORT_STAGGER_TABLE WA_SHORT_TRACK WA_STAGGER_TABLE WA_STANDARD_TRACK WCF_SHEET WN_NETBALL WORLD_BOX_FLOOR WORLD_MEN_FIELD WORLD_RUGBY_FIELD WORLD_WOMEN_FIELD WPV_SITTING WSF_SQUASH WSF_SQUASH_DOUBLES WSF_SQUASH_WIDE

const AFL_GROUND

AFL_GROUND: AflFigures

Law 3 of the Laws of Australian Football 2026: oval, 135 to 185 m long and 110 to 155 m wide; goal posts 6.4 m apart and behind posts 6.4 m on each side of them, in a line (3.3.1); the goal square 9 m deep from each goal post (3.4); the centre square 50 by 50 m, the Centre Circle 3 m across and the Ten Metre Circle 10 m (3.5); a Fifty Metre Arc at each end, 50 m from the middle of the goal line (the definition, and Diagram 1). The length is measured as the Playing Surface is defined, inside the Boundary Line, Goal Line and Behind Line, so from goal line to goal line. The size drawn is the MCG's (160 by 141 m, its venue page). The Laws print no line width: 0.1 m is drawn.

const AFL_GROUNDS

AFL_GROUNDS: readonly AflGround[]

The grounds whose length and width the AFL's venue pages print (read 2026-10-13), each within the Laws' range. The AFL's page for Adelaide Oval prints 190 by 125 m, a length beyond the 185 m the Laws allow, so that ground is not here.

const AFL_LEAGUES

AFL_LEAGUES: readonly AflLeague[]

The AFL league: the Laws' ground.

type AflFigures

interface AflFigures { /** The Playing Surface's length (goal line to goal line) and width, as the Laws allow; the size drawn is the MCG's. */ length: Span; width: Span; /** The goal posts' distance apart, and the behind posts' distance from each goal post on either side. */ goalPosts: number; behindPosts: number; /** The goal square: the depth of the two lines drawn at right angles to the goal line from each goal p…

The figures of the ground, in the unit of the table they are in.

type AflGround

interface AflGround { id: string; name: { en: string; ja: string }; /** Metres, as the venue's page prints them: length by width. */ length: number; width: number; /** The page it was read on (a key of `LEAGUE_SOURCES`). */ source: string; /** What else is published, when it disagrees. */ note?: string; }

A ground the AFL publishes the size of.

type AflLeague

type AflLeague = LeagueBase<"afl", AflFigures>;

The league with the Laws' ground.

function aflOf

aflOf: (league: AflLeague) => AflSpec

The Australian football ground, in feet.

const ATHLETICS_LEAGUES

ATHLETICS_LEAGUES: readonly AthleticsLeague[]

The athletics leagues: the outdoor track (the sport's own) and the covered one.

type AthleticsFigures

interface AthleticsFigures { /** The length of each straight (the distance between the centres of the semicircles, for the outdoor track's). */ straight: number; /** The radius of each bend at the inside of the track, where the kerb is. */ radius: number; /** The width of a lane, the line on its right included. */ lane: number; /** The least, the most and the drawn number of lanes of the oval. */ lanes: { min: numbe…

The figures of an oval track, in metres.

type AthleticsLeague

type AthleticsLeague = LeagueBase<"athletics", AthleticsFigures>;

A league with an athletics track of its own.

const BADMINTON_LEAGUES

BADMINTON_LEAGUES: readonly BadmintonLeague[]

The badminton leagues: the BWF's.

type BadmintonFigures

interface BadmintonFigures { /** Back boundary line to back boundary line, to the outside of the lines. */ length: number; /** The doubles court's width. The singles sidelines are inside it. */ width: number; /** The lines are 40 mm wide. */ lineWidth: number; /** A singles side line's outside edge is this far inside the doubles side line's. */ singlesInset: number; /** The doubles long service line: its outside edg…

A badminton court, from Law 1 of the BWF's Laws of Badminton and its Diagram A. Metres.

type BadmintonLeague

type BadmintonLeague = LeagueBase<"badminton", BadmintonFigures>;

type BaseballLeague

type BaseballLeague = LeagueBase<"baseball", DiamondFigures> & { game: "baseball" | "softball" };

A league with a diamond of its own.

type BasketballLeague

type BasketballLeague = LeagueBase<"basketball", CourtFigures>;

A league with a basketball court of its own.

const BWF_BADMINTON

BWF_BADMINTON: BadmintonFigures

The BWF's court, from Law 1 of the Laws of Badminton (as amended 26 April 2025) and its Diagram A, in metres: 13.40 by 6.10 m, lines 40 mm wide; the singles side lines 460 mm inside the doubles side lines (so 5.18 m apart, outside to outside); the short service lines 1.98 m from the net; the doubles long service lines 760 mm inside the back boundary lines; the net 1.524 m high at the centre and 1.55 m over the doubles side lines, 760 mm deep and at least 6.1 m wide; the posts 1.55 m high, on the doubles side lines.

function chooseLeague

chooseLeague(o: Pick<SurfaceOptions, "sport" | "league">): { sport: Sport; league: League | undefined; }

Which league the options ask for: the league, and the sport it is drawn as. No league is the sport's own. A league the sport's own (nba for nba) is the sport as it is drawn without one; one of the football leagues that is a sport of its own (cfl) is that sport. A league of another family, or one that does not exist, is an error that says what to use.

Example

import { chooseLeague } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(chooseLeague({ sport: "nba", league: "fiba" }).league?.name.en, chooseLeague({ sport: "nfl", league: "cfl" }).sport);
// FIBA basketball court cfl

type CourtFigures

interface CourtFigures { /** Baseline to baseline, inside the lines. */ length: number; /** Sideline to sideline, inside the lines. */ width: number; /** The free-throw lane (restricted area), side to side. */ laneWidth: number; /** A lane that is wider at the baseline than at the free-throw line (a trapezoid): its width at the baseline. */ laneEndWidth?: number; /** The free-throw line, from the baseline. */ freeTh…

The figures of a basketball court. Lengths, in the unit of the table they are in.

type CourtMarks

interface CourtMarks { /** The marks that divide the free-throw lane, from the baseline. The first is the block. */ lane: number[]; /** The lower defensive box's marks: from the lane, from the baseline, and how long; or none. */ box: { fromLane: number; fromBaseline: number; length: number } | null; /** The marks 28 feet from the baseline on the sidelines: where, and how long. */ sideline: { fromBaseline: number; le…

The small marks the NBA's court diagram has along the lane and the sidelines, drawn in the full detail. Lengths.

function courtOf

courtOf: (league: BasketballLeague, year?: number) => CourtSpec

A basketball court of a league in a season, in feet.

const CRICKET_LEAGUES

CRICKET_LEAGUES: readonly CricketLeague[]

The cricket leagues: the ICC men's conditions first (the ground with the 30-yard circle), then the women's and the Laws.

type CricketFigures

interface CricketFigures { /** The pitch: its length (the distance between the bowling creases, which are its ends) and its width (5 feet either side of the line of the middle stumps). */ pitchLength: number; pitchWidth: number; /** The bowling crease's length; the popping crease's distance in front of it; how far the popping crease is marked either side of the line of the middle stumps (the creases are unlimited in…

The figures of the ground, in the unit of the table they are in (yards).

type CricketLeague

type CricketLeague = LeagueBase<"cricket", CricketFigures>;

A league with a cricket ground of its own.

function cricketOf

cricketOf: (league: CricketLeague) => CricketSpec

A cricket ground of a league, in feet.

const CURLING_CANADA_SHEET

CURLING_CANADA_SHEET: SheetFigures

Curling Canada's sheet, from Rule 3 of the Rules of Curling for General Play (September 2022 to August 2026), in feet as it prints them: 146 to 150 feet from backboard to backboard and 14 ft 2 in to 15 ft 7 in wide; 114 feet from tee to tee; the back line 6 feet from the tee; the hog line's inside edge 21 feet from it; the circles of 6, 4 and 2 feet and a button of at least 6 inches; the centre line 12 feet beyond each tee and the hacks' back edges there, each at most 8 inches long and its inside edge at most 3 inches from the centre line. The book prints no hack line and no courtesy line. Its hack width is not printed; World Curling's 6 inches is drawn.

const CURLING_LEAGUES

CURLING_LEAGUES: readonly CurlingLeague[]

The curling leagues, World Curling's first.

type CurlingLeague

type CurlingLeague = LeagueBase<"curling", SheetFigures>;

A league with a curling sheet of its own.

type DiamondFigures

interface DiamondFigures { /** The distance between bases (the side of the square). */ base: number; /** The side of a base. */ baseSize: number; /** The front of the pitcher's plate, from the back point of home plate. */ pitcher: number; /** The pitcher's plate: how long (across) and how deep. */ pitchersPlate: { width: number; depth: number }; /** The mound's radius, or a softball pitcher's circle's. */ moundRadiu…

The figures of a diamond. Lengths, in the unit of the table they are in.

function diamondOf

diamondOf: (league: BaseballLeague, year?: number) => DiamondSpec

A diamond of a league in a season, in feet.

type Family

type Family = "basketball" | "hockey" | "football" | "baseball" | "curling" | "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball" | "lacrosse" | "rugby" | "afl" | "gaelic" | "cricket" | "fieldhockey" | "handball" | "athletics" | "skating" | "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";

The families of surface a league is a variant of.

const FAMILY_OF

FAMILY_OF: Readonly<Record<Sport, Family | undefined>>

The family of surface each sport draws; soccer's variants are its pitches (pitch), not leagues.

const FIELD_HOCKEY_LEAGUES

FIELD_HOCKEY_LEAGUES: readonly FieldHockeyLeague[]

The field hockey leagues.

type FieldHockeyFigures

interface FieldHockeyFigures { length: Span; width: Span; /** The width of the lines. */ lineWidth: number; /** The distance from the back-line to the 22.90 m lines (the 23-metre lines), edge to edge. */ twentyThree: number; /** The goal-posts' inside distance, the height under the crossbar, and the net's depth at the ground (the least the rules allow). */ goalWidth: number; goalHeight: number; goalDepth: number; /*…

The figures of the pitch, in the unit of the table they are in.

type FieldHockeyLeague

type FieldHockeyLeague = LeagueBase<"fieldhockey", FieldHockeyFigures>;

A league with a hockey pitch of its own.

function fieldHockeyOf

fieldHockeyOf: (league: FieldHockeyLeague) => FieldHockeySpec

A field hockey pitch, in feet.

function fieldOf

fieldOf: (league: FootballLeague, year?: number) => FootballSpec

A football field of a league in a season: the spec, in feet.

const FIH_PITCH

FIH_PITCH: FieldHockeyFigures

The FIH's pitch, from the Rules of Hockey (effective 1 March 2026), Field and Equipment Specifications 1.1 to 1.8: 91.40 by 55.00 m; lines 75 mm; the 22.90 m lines (1.3e); the circle (1.4): a 3.66 m line whose outside edge is 14.63 m from the back-line, continued by quarter-circles centred on the inside front corners of the posts; the broken line 5 m outside it, solid sections of 300 mm and gaps of 3 m (1.4d); the penalty spot 150 mm across, its centre 6.40 m from the inner edge of the goal-line (Rule 1; 6.475 m from the outer edge, 1.3i); the goal 3.66 m between the posts and 2.14 m under the crossbar (1.5d), its net at least 1.20 m deep at the ground (1.5e); marks of 300 mm outside the pitch at 5 and 10 m from the goal-posts (1.3g); flag-posts of 1.20 to 1.50 m (1.8).

const FIP_PADEL

FIP_PADEL: PadelFigures

The FIP's padel court, from "The court" (dimensions, net, enclosures) in the Rules of Padel (review of application 1 January 2026), in metres: 10 by 20 m inside the walls; the service lines 6.95 m from the net on either side, the central service line between the net and each, extending at least 20 cm beyond it; lines 5 cm wide; the net 10 m long, 0.88 m high at the centre and 0.92 m at the ends, on posts at most 1.05 m high whose outer side is the side limit of the court; the court enclosed, the end walls 4 m high (3 m of wall and 1 m of metal fence) and each side wall, at both ends, 4 m long and 3 m high with metal fence between.

const FITEQ_TEQBALL

FITEQ_TEQBALL: TeqballFigures

The FITEQ's teqball court and table, from sections 1 and 2 of the Official Rules and Regulations of Teqball (2026), in metres: a table 3,000 mm long and 1,500 mm wide (1,700 mm with the net), 900 mm high with the net; a net 1,700 mm wide, 20 mm thick and 140 mm high; the table in the exact middle of a court of at least 12 m (the sides parallel to the net) by 16 m, the net parallel to the shorter sides; the halfway line across the middle, and a service line on each side 3.5 m from the middle of the table, each 20 to 50 mm wide; court surrounds 500 to 1,500 mm high.

const FIVB_BEACH

FIVB_BEACH: VolleyballFigures

The FIVB's beach court, from Rules 1 and 2 of the Official Beach Volleyball Rules 2025-2028, in metres: 16 by 8 m inside a free zone of at least 3 m (at least 5 m in the FIVB's competitions); lines 5 cm wide inside the dimensions and no centre line; the service zone 8 m wide; the net 8.5 m long and 1 m wide, at the same heights; the posts 0.70 to 1.00 m outside the side lines, 1 m in the FIVB's competitions.

const FIVB_INDOOR

FIVB_INDOOR: VolleyballFigures

The FIVB's indoor court, from Rules 1 and 2 of the Official Volleyball Rules 2025-2028, in metres as it prints them: 18 by 9 m inside a free zone of at least 3 m (5 m beside the side lines and 6.5 m behind the end lines in the FIVB's competitions); lines 5 cm wide inside the dimensions; the attack line's rear edge 3 m from the axis of the centre line and, in the FIVB's competitions, extended by five 15 cm lines 20 cm apart to 1.75 m; the service zone 9 m wide, limited by two 15 cm lines 20 cm behind the end line; the net 1 m wide and 9.50 to 10 m long, 2.43 m (men) or 2.24 m (women) high; each antenna 1.80 m long and 10 mm across, at the outer edge of a side band; the posts 0.50 to 1.00 m outside the side lines, 1 m in the FIVB's competitions.

function fixed

fixed: (value: number) => Span

A size the rules fix: a range of one.

type FootballLeague

type FootballLeague = LeagueBase<"football", FootballFigures>;

A league with a football field of its own.

type FreeZone

type FreeZone = { side: number; end: number } | null;

The ground round a court that a rulebook gives a figure for: beside the sidelines and behind the end lines. null when it gives none.

type FreeZones

interface FreeZones { official: FreeZone; least: FreeZone; }

The two figures a rulebook can give for the ground round its court: what it asks of its own competitions, and the least it allows or recommends.

const GAA_FOOTBALL_FIELD

GAA_FOOTBALL_FIELD: GaelicFigures

The field for Gaelic football, from Rule 1 of the Official Guide Part 2 (effective 27 March 2026): 130 to 145 m by 80 to 90 m; lines across at 13, 20 and 45 m from each end line; the posts 6.5 m apart on the end line, the crossbar 2.5 m up; the small rectangle 14 by 4.5 m (its sides 3.75 m outside the posts' inside edges) and the large one 19 by 13 m (6.25 m); a 13 m arc on each 20 m line; for football, a 40 m arc centred on the middle of the goal line and the penalty point 11 m from it; the 10 m midline with dashes of 5 m at intervals of 5 m; lines 90 mm wide.

const GAA_HURLING_FIELD

GAA_HURLING_FIELD: GaelicFigures

The field for hurling, the same field: the 65 m lines (marked "Hurling") and no football arc or penalty point.

const GAELIC_LEAGUES

GAELIC_LEAGUES: readonly GaelicLeague[]

The Gaelic leagues, football first.

type GaelicFigures

interface GaelicFigures { /** `football` or `hurling`. */ code: "football" | "hurling"; /** End line to end line, and across; the size drawn is the greatest. */ length: Span; width: Span; /** The lines across the field, in metres from each end line: 13, 20, 45 and, for hurling, 65. */ lines: number[]; /** The posts' distance apart and the crossbar's height above the ground. */ postsApart: number; crossbar: number; /…

The figures of the field, in the unit of the table they are in. null is what a code does not have.

type GaelicLeague

type GaelicLeague = LeagueBase<"gaelic", GaelicFigures>;

A league with a Gaelic field of its own.

function gaelicOf

gaelicOf: (league: GaelicLeague) => GaelicSpec

A Gaelic games field of a league (football or hurling), in feet.

const HANDBALL_LEAGUES

HANDBALL_LEAGUES: readonly HandballLeague[]

The handball leagues.

type HandballFigures

interface HandballFigures { length: Span; width: Span; /** The goal: inside width and height, and the depth of its net behind the goal line (at the ground). */ goalWidth: number; goalHeight: number; goalDepth: number; /** The goal-area line (6 m): its straight part's length, and its distance from the rear edge of the goal line to its front edge; the arcs' radius is that distance. */ areaLine: number; areaDistance: n…

The figures of the court, in the unit of the table they are in.

type HandballLeague

type HandballLeague = LeagueBase<"handball", HandballFigures>;

A league with a handball court of its own.

function handballOf

handballOf: (league: HandballLeague) => HandballSpec

A handball court, in feet.

function hasHistory

hasHistory: (league: League | undefined) => boolean

True when a league has markings a source dates, so that year changes its drawing.

type HockeyLeague

type HockeyLeague = LeagueBase<"hockey", RinkFigures>;

A league with an ice rink of its own.

const ICC_MEN_GROUND

ICC_MEN_GROUND: CricketFigures

The ICC men's playing conditions (effective from October 2026): the Laws' pitch and creases (6.1, 7.2 to 7.4, 8.1), the popping crease marked at least 25 ft either side (7.3), a boundary no longer than 90 yards and not shorter than 65 yards from the centre of the pitch (19.1.3), and the two semicircles of 30 yards about the middle stumps, linked by two parallel lines (28.7.2.1). 75 yards is drawn.

const ICC_WOMEN_GROUND

ICC_WOMEN_GROUND: CricketFigures

The ICC women's playing conditions (ODI, effective from October 2026): a boundary of 60 to 70 yards (19.1.3) and semicircles of 25.15 yards (23 m, 28.7.2.1). 65 yards is drawn.

const IHF_COURT

IHF_COURT: HandballFigures

The IHF's court, from Rule 1 of the Rules of the Game for Indoor Handball (1 July 2025): 40 by 20 m (1:1); the goal 3 m wide and 2 m high (1:2); lines 5 cm, the goal line between the posts 8 cm (1:3); the goal-area line a 3 m line 6 m from the goal line with quarter circles of 6 m about the posts' rear inner corners (1:4); the free-throw line broken, 3 m outside it, segments and spaces of 15 cm (1:5); the 7-metre line 1 m long (1:6); the 4-metre line 15 cm long (1:7); the throw-off area 4 m across (1:9); the substitution line 4.5 m from the centre line, ended by a mark 15 cm either side of the side line (1:10). The net's 1.1 m depth at the ground is from the Guidelines for Playing Courts and Goals (V h), which are not part of the rules.

const IRF_RACQUETBALL

IRF_RACQUETBALL: RacquetballFigures

The IRF's racquetball court, from Rule 2.1 of the Official Rules 2026-2028 and Section Three of the Court Specifications (2025), in feet as they are printed: 20 feet wide, 40 feet long and 20 feet high; lines 1 to 1 1/2 inches wide (1 1/2 inches, bright red, recommended); the back edge of the short line 20 feet from the back wall; the front edge of the service line 5 feet in front of the short line's back edge; a service box's line 18 inches from the side wall and a drive serve line 3 feet from it (to the edge nearest the centre); the receiving line's back edge 5 feet behind the short line's back edge, a broken line of two 21 inch ends and sixteen 6 inch lines between 6 inch spaces; the back wall at least 12 feet high, with the out-of-court line's upper edge at 12 feet.

const IRF_ROUNDNET

IRF_ROUNDNET: RoundnetFigures

The International Roundnet Federation's court, from Rules 1.1.2 and 1.2.1 to 1.2.4 (adopted 21 October 2022), in metres: a set 91.4 cm across and 20.3 cm high; a court of at least 10 by 10 m (33 by 33 feet) recommended, with no out of bounds; a serving line drawn as a circle 2.6 m (8.5 feet) from the centre of the set, 2.13 m (7 feet) from its edge; a no hit zone line drawn as a circle 90 cm (3 feet) from the centre; a mark at each of the four service positions, 90 degrees apart.

const IRL_FIELD

IRL_FIELD: RugbyFigures

The IRL's field, from Section 1 of the International Laws of the Game (2024): 100 m between the goal lines, 68 m wide, in-goal 6 to 11 m (8 m, the NRL's, is drawn), goal posts 5.5 m apart with the crossbar 3 m up, a line across the field at every 10 m from each goal line to the half-way line. The book prints no line width and no red lines: the NRL's 15 cm is drawn, in white.

const ISTAF_SEPAK_TAKRAW

ISTAF_SEPAK_TAKRAW: SepakTakrawFigures

The ISTAF's court, from Laws 1 to 3 of the Law of the Game (2013, dated 9 December 2013), in metres: 13.40 by 6.10 m; the boundary lines at most 0.04 m wide, drawn inwards; a centre line 0.02 m wide; a quarter circle of 0.9 m radius at each end of the centre line; a service circle of 0.3 m radius on each side, its centre 2.45 m from the back line and 3.05 m from the sidelines, its line 0.04 m wide; a free zone of 3.0 m; the posts 0.3 m outside the sidelines, in line with the centre line, 1.55 m (men) or 1.45 m (women) high; the net 0.7 m wide and at least 6.1 m long, 1.52 m (men) or 1.42 m (women) high at the centre.

const ITF_TENNIS

ITF_TENNIS: TennisFigures

The ITF's court, from Rule 1 of the Rules of Tennis (2026) and its Appendix X, in feet as the ITF prints them: 78 feet long, 27 feet wide for singles and 36 feet for doubles; the net 3 feet 6 inches at the posts and 3 feet at the centre; the centres of the net posts 3 feet outside the doubles court and the centres of singles sticks 3 feet outside the singles court; the service lines 21 feet from the net; the centre mark 4 inches long and the centre service line 2 inches wide; the other lines 1 to 2 inches wide and the baselines up to 4 inches; posts at most 6 inches square, singles sticks 3. Appendix X recommends 21 feet behind the baselines and 12 feet beside the sidelines for international competitions, 18 and 10 for clubs.

const ITTF_TABLE

ITTF_TABLE: TableTennisFigures

The ITTF's table, from Laws 2.1 and 2.2 of the Laws of Table Tennis (the 2026 statutes), in metres: 2.74 by 1.525 m and 76 cm above the floor; a white side line and end line 2 cm wide, a white centre line 3 mm wide; the net 15.25 cm above the playing surface, hung from posts 15.25 cm high whose outside limits are 15.25 cm outside the side lines.

const LACROSSE_LEAGUES

LACROSSE_LEAGUES: readonly LacrosseLeague[]

The lacrosse leagues: World Lacrosse's men's field first, which is what a drawing has without a league.

type LacrosseFigures

interface LacrosseFigures { /** `box` (a floor with boards), or the field of the `men`'s game, the `women`'s or the `pll`'s. */ kind: "box" | "men" | "women" | "pll"; length: Span; width: Span; lineWidth: number; /** The goal line's distance from the end line (the end boards on a floor). */ goalLine: number; /** The goal's opening, and the depth of the net behind the goal line. */ goalWidth: number; goalHeight: numb…

The figures of a lacrosse surface, in the unit of the table they are in. null is what a game does not mark.

type LacrosseLeague

type LacrosseLeague = LeagueBase<"lacrosse", LacrosseFigures>;

A league with a lacrosse surface of its own.

function lacrosseOf

lacrosseOf: (league: LacrosseLeague) => LacrosseSpec

A lacrosse surface of a league, in feet (the box floor, or a field). These have no history; a year is ignored.

type League

type League = BasketballLeague | HockeyLeague | FootballLeague | BaseballLeague | CurlingLeague | NetLeague | LacrosseLeague | RugbyLeague | AflLeague | GaelicLeague | CricketLeague | FieldHockeyLeague | HandballLeague | AthleticsLeague | SkatingLeague | BudoLeague;

A league: a variant of a family of surface.

const LEAGUE_SOURCES

LEAGUE_SOURCES: Readonly<Record<string, Readonly<LeagueSource>>>

Every document a league's figures were read from, by id.

function leagueAt

leagueAt(sport: Sport, id: string, year?: number): LeagueAt

A league's figures as they were in a season. Without a year, or for a league with no dated history, they are the rulebook's as it is now.

Example

import { leagueAt } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueAt("nba", "ncaa-men", 2010).differences.map((change) => change.id));
// [ 'ncaa-men.restricted.start' ]

type LeagueAt

interface LeagueAt<L extends League = League> { league: L; /** The season asked for, or `undefined` for today's. */ year: number | undefined; /** The figures in the unit the league keeps them in. */ figures: L["dimensions"]; /** The markings that differ from today's. */ differences: LeagueChange[]; /** Markings whose history starts later than the season asked for (the first cited state is used). */ gaps: string[]; }

The league's figures in a season: the rulebook's as it is now, with each marking that has a dated history as it stood then.

function leagueAtOf

leagueAtOf<L extends League>(league: L, year?: number): LeagueAt<L>

leagueAt for a league already in hand.

function leagueById

leagueById(sport: Sport, id: string): League | undefined

A league by its sport and id, or undefined.

type LeagueChange

interface LeagueChange { /** `ncaa-men.threePoint.2019`. */ id: string; league: string; /** The marking's name: a key of the league's dimensions. */ feature: string; /** The first season, or `null` for the state before the first change that is cited. */ from: number | null; value: unknown; what: string; ja: string; sources: string[]; }

One change of a league's markings: a state of one marking from a season on.

function leagueChanges

leagueChanges(sport: Sport, id: string): LeagueChange[]

Every change a source dates in a league's markings, in season order, each with a sentence in English and in Japanese and its sources. A league with no cited history has none (a drawing with a year is then the league's rulebook as it is now).

Example

import { leagueChanges } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueChanges("nba", "ncaa-men").map((change) => change.id));

function leagueFigures

leagueFigures(league: League): [string, string][]

The key figures of a league in words, each in the unit its rulebook uses: for a table of the leagues (docs/variants.md) and for a test that holds the drawing to the rulebook.

Example

import { LEAGUES, leagueFigures } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
const fiba = LEAGUES.nba.find((league) => league.id === "fiba")!;
console.log(leagueFigures(fiba).slice(0, 2).map(([name, value]) => `${name}: ${value}`).join("; "));
// court: 28 m by 15 m; lane: 4.9 m wide

function leagueIds

leagueIds: (sport: Sport) => string[]

The ids of a sport's leagues.

const LEAGUES

LEAGUES: Readonly<Record<Sport, readonly League[]>>

The leagues each sport can draw, in the order a menu lists them, the sport's own first (the three football sports share their list, the football leagues). Soccer and the pools have none.

function leagueSeason

leagueSeason(family: Family, year: number): string

The name of a season as the family writes it: 1979–80 for basketball and hockey, which play across the new year, else 1979.

function leagueSize

leagueSize(sport: Sport, id: string, unit?: Unit): { length: number; width: number; unit: Unit; }

The length and the width of a league's surface in a unit (the league's own when none is given): a football field with its end zones, end line to end line. A diamond has no fixed size, its fence is its size.

Example

import { leagueSize } from "@johnmorrisdotca/kyogijo-stadiums/leagues";
console.log(leagueSize("nba", "nba"));
// { length: 94, width: 50, unit: 'ft' }

type LeagueSource

interface LeagueSource { id: string; /** The document: "NCAA Men's and Women's Basketball Rules and Interpretations". */ title: string; /** Who publishes it. */ by: string; /** The edition: a season or a year. */ edition: string; /** The address it was read at, on the date in `read`. */ url: string; /** The date it was read (yyyy-mm-dd). */ read: string; /** What was taken from it. */ note?: string; }

A rulebook (or a page that publishes its figures).

type LeagueSpan

interface LeagueSpan<T> { /** The first season it was in force, or `null` for the state before the first change that is cited. */ from: number | null; value: T; /** What it was, in a sentence. */ what: string; /** The same in Japanese. */ ja: string; /** Keys of `LEAGUE_SOURCES`; at least one. */ sources: string[]; }

The state of one marking from a season on, in a league's own history: the season it began, what it was, and its sources.

const MCC_GROUND

MCC_GROUND: CricketFigures

The Laws of Cricket (MCC, 2017 Code, 4th Edition 2026): the pitch 22 yards by 10 feet (6.1); the bowling crease 8 ft 8 in (7.2); the popping crease 4 ft in front of it and marked at least 6 ft either side of the line of the middle stumps (7.3); the return creases 4 ft 4 in either side of that line (inside edges), marked 8 ft behind the popping crease (7.4); a set of wickets 9 in wide (8.1). No boundary size and no circle: the Laws have none (19), so the boundary is any size, 75 yards by default. The Laws print no width for the creases: 2 inches is drawn.

const MLB_DIAMOND

MLB_DIAMOND: DiamondFigures

The baseball field of the Official Baseball Rules, from MLB (geometry.ts).

type Named

interface Named { en: string; ja: string; }

The words of a league in both languages.

const NBA_COURT

NBA_COURT: CourtFigures

The NBA court today, from NBA (geometry.ts). Rule 1 of the NBA rulebook.

const NCAA_MEN_FIELD

NCAA_MEN_FIELD: LacrosseFigures

The NCAA's men's field (NCAA Men's Lacrosse Rules and Interpretations, 2027 and 2028, Rule 1): 110 by 60 yards (1-2); the goal 6 ft by 6 ft, 15 yards from each end line (1-3); the net fastened 7 ft behind the centre of the goal (1-5); a crease of 9 ft radius and a goal mouth of 6 ft radius (1-4); the attack-area line 40 yards long, 20 yards from the goal line, joined to the end lines (1-6); the defensive-area line 20 yards from the goal line, sideline to sideline (1-7); wing lines 20 yards from the centre, 10 yards each side of the centre line (1-8); the goal line and goal mouth 2 in wide, the centre line 4 in (1-2).

const NCAA_WOMEN_FIELD

NCAA_WOMEN_FIELD: LacrosseFigures

The NCAA's women's field (NCAA Women's Lacrosse Rules, 2026 and 2027, Rule 1): 101 to 110 m by 55 to 64 m, the optimal 110 by 60 m with the goals 92 m apart, 9 m behind each goal line (1-1); the goal 1.83 m by 1.83 m with the net 2.1 m behind the goal line (1-18); a goal circle of 2.6 m radius (1-21); a restraining line 27 m from each goal line (1-4); a centre circle of 9 m radius and a 3 m line through the centre (1-5); an arc of 8 m and a fan of 12 m from the goal circle, the arc ending at lines at 45 degrees to the goal line extended and the fan at the goal line extended (1-6); lines 5 to 10.1 cm, the goal line 5 cm (1-3).

const NETBALL_LEAGUES

NETBALL_LEAGUES: readonly NetballLeague[]

The netball leagues: World Netball's.

type NetballFigures

interface NetballFigures { /** The side lines' length, and the goal lines'. */ length: number; width: number; /** All lines are 50 mm wide. */ lineWidth: number; /** The centre circle's diameter, and the goal circle's radius (a semicircle centred on the middle of the outside of the goal line). */ centreCircle: number; goalCircle: number; /** A goalpost's height, the greatest diameter of its pole, and its ring: the i…

A netball court, from Rule 1 of World Netball's Rules of Netball (2024 edition). Metres.

type NetballLeague

type NetballLeague = LeagueBase<"netball", NetballFigures>;

type NetFigures

type NetFigures = TennisFigures | VolleyballFigures | BadmintonFigures | PickleballFigures | TableTennisFigures | PadelFigures | SquashFigures | RacquetballFigures | SepakTakrawFigures | TeqballFigures | RoundnetFigures | NetballFigures;

The figures of any net sport's court.

type NetLeague

type NetLeague = TennisLeague | VolleyballLeague | BadmintonLeague | PickleballLeague | TableTennisLeague | PadelLeague | SquashLeague | RacquetballLeague | SepakTakrawLeague | TeqballLeague | RoundnetLeague | NetballLeague;

A league with a net sport's court.

function netOf

netOf: <L extends NetLeague>(league: L) => L["dimensions"]

A net sport's court of a league (tennis, volleyball, beach and sitting volleyball, badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball), in feet. These courts have no history; a year is ignored.

type NetSport

type NetSport = "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball";

The sports of these courts.

const NHL_RINK

NHL_RINK: RinkFigures

The NHL rink today, from NHL (geometry.ts). Rule 1 of the NHL rulebook.

const NLL_FLOOR

NLL_FLOOR: LacrosseFigures

The NLL's floor (Official Rules 2025-26, Section 1 and Diagram A): 180 to 200 ft long and 85 wide, corners of 28 ft radius (the diagram's labels); the goal 4 ft 9 in apart and 4 ft high, the goal line 12 ft from the end boards (2.1), poles of 4 ft 6 in behind (2.2); the crease a circle of 9 ft 3 in radius cut by a line 1 ft behind the goal line (3.1); restraining lines 42 ft 6 in from the centre line (1.5); the centre face-off circle of 11 ft radius with a dotted circle of 17 in (6.1); corner face-off spots 5 yards from the boards and 5 yards inside the restraining lines (6.2). The NLL's book prints no line width: World Lacrosse's 2 in is drawn.

const NRL_FIELD

NRL_FIELD: RugbyFigures

The NRL's field, from Section 1 and Section 18 of the Rugby League Laws of the Game, International Level (2023 edition): 100 m between the goal lines, 68 m wide, in-goal areas 68 by 8 m, goal posts 5.5 m apart and 16 m high, lines 15 cm wide, white, at every 10 m from each goal line to the half-way line, those at 20 m and 40 m red; scrum lines 10 m and 20 m in from each touchline (10 cm). The section also prints a media line 1.5 m outside each touchline, which is not drawn: it gives no length. The crossbar's height, 3 m, is the plan's label in Section 1.

const PADEL_LEAGUES

PADEL_LEAGUES: readonly PadelLeague[]

The padel leagues: the FIP's.

type PadelFigures

interface PadelFigures { /** Interior length and width (a 0.5% tolerance). */ length: number; width: number; /** All lines are 5 cm wide. */ lineWidth: number; /** The service lines are this far from the net, on either side. */ serviceLine: number; /** The central service line runs from the net and extends at least this far beyond the service line. */ centreOverrun: number; net: { /** The net's length, its height at…

A padel court, from "The court" in the FIP's Rules of Padel (review of application 1 January 2026). Metres. The court is measured inside its walls, so it has no lines round it: the enclosure is its boundary.

type PadelLeague

type PadelLeague = LeagueBase<"padel", PadelFigures>;

const PICKLEBALL_LEAGUES

PICKLEBALL_LEAGUES: readonly PickleballLeague[]

The pickleball leagues: USA Pickleball's.

type PickleballFigures

interface PickleballFigures { length: number; width: number; /** The non-volley zone: each of its lines is this far from the net, to its outside edge. */ nonVolley: number; /** All lines are 2 inches wide. */ lineWidth: number; net: { /** The net's height at the sidelines and at the centre. */ heightSides: number; heightCentre: number; /** From the inside of one post to the inside of the other (so each stands 1 foot…

A pickleball court, from Section 3 of the USA Pickleball rulebook. Feet.

type PickleballLeague

type PickleballLeague = LeagueBase<"pickleball", PickleballFigures>;

const PLL_FIELD

PLL_FIELD: LacrosseFigures

The PLL's field (2024 PLL Rulebook, Section II, the newest book found): 100 by 60 yards (A); the goal 6 ft by 6 ft, 15 yards from each end line (C, D); a crease of 9 ft radius (E); the two-point arc 13 yards from the middle of the goal line at the goal line extended, parallel to the sideline until it is 15 yards from it, then an arc of 15 yards, and two straight lines from the goal line extended to the end line (G); wing lines 19 yards from the centre, 10 yards each side (H); the centre line and the arc 4 in wide, the goal lines 2 in (A). The net is "approximately seven feet" back (F).

const RACQUETBALL_LEAGUES

RACQUETBALL_LEAGUES: readonly RacquetballLeague[]

The racquetball leagues: the IRF's.

type RacquetballFigures

interface RacquetballFigures { length: number; width: number; /** The lines are 1 to 1 1/2 inches wide: 1 1/2 inches is the recommended width. */ lineWidth: number; /** The back edge of the short line is this far from the back wall (midway between the walls). */ shortLine: number; /** The front edge of the service line is this far in front of the short line's back edge. */ serviceLine: number; /** The edge of a serv…

A racquetball court, from Rule 2.1 of the International Racquetball Federation's Official Rules 2026-2028 and its Court Specifications (2025). Feet.

type RacquetballLeague

type RacquetballLeague = LeagueBase<"racquetball", RacquetballFigures>;

type RinkFigures

interface RinkFigures { /** End boards to end boards. */ length: number; width: number; /** The radius of the boards' corners. */ cornerRadius: number; /** The goal line, from the end boards. */ goalLine: number; /** The blue lines, from the end boards. */ blueLine: number; /** The face-off circles' radius. */ circleRadius: number; /** An end-zone face-off spot, from the goal line, and from the rink's long axis. */ …

The figures of an ice rink. Lengths, in the unit of the table they are in.

function rinkOf

rinkOf: (league: HockeyLeague, year?: number) => RinkSpec

An ice rink of a league in a season, in feet.

const ROUNDNET_LEAGUES

ROUNDNET_LEAGUES: readonly RoundnetLeague[]

The roundnet leagues: the International Roundnet Federation's.

type RoundnetFigures

interface RoundnetFigures { /** The recommended least court; there are no out of bounds. */ length: number; width: number; /** The set: its diameter and its height. */ set: { diameter: number; height: number }; /** The serving line is a circle this far from the centre of the set; the no hit zone line, another. */ servingRadius: number; noHitRadius: number; /** The distance from the edge of the set to the service lin…

A roundnet court, from Chapter 1 of the International Roundnet Federation's Rules (adopted 21 October 2022). Metres.

type RoundnetLeague

type RoundnetLeague = LeagueBase<"roundnet", RoundnetFigures>;

const RUGBY_LEAGUES

RUGBY_LEAGUES: readonly RugbyLeague[]

The rugby leagues, World Rugby's first.

type RugbyFigures

interface RugbyFigures { /** `union` or `league`: the lines each code marks differ. */ code: "union" | "league"; /** Try line to try line (goal line to goal line), and across. */ length: Span; width: Span; /** The depth of each in-goal area, try line to dead-ball line. */ inGoal: Span; /** The width of the lines, as drawn at true width. */ lineWidth: number; /** The goal posts: apart, and the crossbar's top edge abo…

The figures of a rugby field. Lengths, in the unit of the table they are in. null and [] are what a code does not have.

type RugbyLeague

type RugbyLeague = LeagueBase<"rugby", RugbyFigures>;

A league with a rugby field of its own.

function rugbyOf

rugbyOf: (league: RugbyLeague) => RugbySpec

A rugby field of a league, in feet.

const SEPAK_TAKRAW_LEAGUES

SEPAK_TAKRAW_LEAGUES: readonly SepakTakrawLeague[]

The sepak takraw leagues: the ISTAF's.

type SepakTakrawFigures

interface SepakTakrawFigures { length: number; width: number; /** The boundary lines are at most this wide, drawn inwards; the service circle's is the same. */ lineWidth: number; /** The centre line, under the net. */ centreLine: number; /** The quarter circles at the ends of the centre line: their radius. */ quarterCircle: number; /** The service circles: their radius, and their centre's distance from the back line…

A sepak takraw court, from Laws 1 to 3 of the ISTAF's Law of the Game (2013). Metres.

type SepakTakrawLeague

type SepakTakrawLeague = LeagueBase<"sepaktakraw", SepakTakrawFigures>;

type SheetFigures

interface SheetFigures { /** The sheet as drawn: backboard to backboard (inside edges), and sideline to sideline. The greatest the rules allow. */ length: number; width: number; /** The least length and the least width the rules allow. The greatest are `length` and `width`. */ lengthMin: number; widthMin: number; /** Each tee line's centre, from the middle of the sheet. */ teeFromMiddle: number; /** The back line's …

The figures of a curling sheet. Lengths, in the unit of the table they are in.

function sheetOf

sheetOf: (league?: CurlingLeague) => SheetSpec

A curling sheet of a league, in feet. A sheet has no history; a year is ignored.

function span

span: (min: number, max: number, drawn?: number) => Span

A range of sizes; the drawn size is the greatest the rules allow unless another is said.

type Span

interface Span { min: number; max: number; drawn: number; }

A length the rules allow between a least and a greatest, with the one that is drawn, all in the league's unit.

const SQUASH_LEAGUES

SQUASH_LEAGUES: readonly SquashLeague[]

The squash leagues: World Squash's singles court, its doubles court and its international competition width doubles court.

type SquashFigures

interface SquashFigures { length: number; width: number; /** All court markings are 50 mm wide. */ lineWidth: number; /** The nearest edge of the short line is this far from the back wall. */ shortLine: number; /** A service box is a square of this many metres inside its lines. */ serviceBox: number; /** Heights on the walls: to the top of the tin, to the lower edge of the service line and of the front wall line, an…

A squash court, from the World Squash Federation's Specifications for Squash Courts (28 November 2017), clauses 4 and 5 and Appendices 1 and 2. Metres. The court is measured between the playing surfaces of its walls, so it has no lines round it; the front wall's lines are heights.

type SquashLeague

type SquashLeague = LeagueBase<"squash", SquashFigures>;

const TABLE_TENNIS_LEAGUES

TABLE_TENNIS_LEAGUES: readonly TableTennisLeague[]

The table tennis leagues: the ITTF's.

type TableTennisFigures

interface TableTennisFigures { length: number; width: number; /** The height of the playing surface above the floor. */ height: number; /** The white side lines and end lines are 2 cm wide, the centre line 3 mm. */ edgeLine: number; centreLine: number; net: { /** The top of the net above the playing surface, and the height of the posts. */ height: number; postHeight: number; /** The outside limits of a post are this…

A table tennis table, from Laws 2.1 and 2.2 of the ITTF's Laws of Table Tennis. Metres.

type TableTennisLeague

type TableTennisLeague = LeagueBase<"tabletennis", TableTennisFigures>;

const TENNIS_LEAGUES

TENNIS_LEAGUES: readonly TennisLeague[]

The tennis leagues: the ITF's. The ATP and the WTA play by the ITF's rules.

type TennisFigures

interface TennisFigures { /** Baseline to baseline, to the outside of the lines. */ length: number; /** The singles court's width, and the doubles court's. */ widthSingles: number; widthDoubles: number; /** The service lines: each is this far from the net, to its outside edge. */ serviceLine: number; /** The width of the lines: 1 to 2 inches (the greatest is drawn); a baseline may be up to 4 inches. */ lineWidth: nu…

A tennis court, from Rule 1 of the ITF's Rules of Tennis and its Appendix X. Feet, as the ITF prints them first.

type TennisLeague

type TennisLeague = LeagueBase<"tennis", TennisFigures>;

const TEQBALL_LEAGUES

TEQBALL_LEAGUES: readonly TeqballLeague[]

The teqball leagues: the FITEQ's.

type TeqballFigures

interface TeqballFigures { /** The least court: the sides parallel to the net are `width` long, the other two `length`. */ length: number; width: number; table: { /** The table's length, its width without the net and with it, and its height with the net. */ length: number; width: number; widthWithNet: number; height: number; }; /** The net: its width, thickness and height above the table. */ net: { width: number; th…

A teqball court and table, from sections 1 and 2 of the FITEQ's Official Rules and Regulations of Teqball (2026). Metres.

type TeqballLeague

type TeqballLeague = LeagueBase<"teqball", TeqballFigures>;

const USAP_PICKLEBALL

USAP_PICKLEBALL: PickleballFigures

USA Pickleball's court, from Section 3 of the 2026 rulebook, in feet as it prints them: 20 by 44 feet to the outside edge of the lines (3.A.1, 3.A.2); the non-volley zone 7 feet from the net (3.A.4.c); all lines 2 inches wide (3.A.4.e); the posts 22 feet apart, inside to inside, and at most 3 inches across (3.B.2); the net at least 21 feet 9 inches long (3.B.3), 36 inches high at the sidelines (3.B.6) and 34 inches at the centre (3.B.7); a playing surface of at least 30 by 60 feet, 34 by 64 feet recommended for tournaments (3.A.3).

const VARIANTS

VARIANTS: readonly League[]

Every league of every family, once each.

const VOLLEYBALL_LEAGUES

VOLLEYBALL_LEAGUES: readonly VolleyballLeague[]

The volleyball leagues: the FIVB's indoor court, its beach court, and World ParaVolley's sitting volleyball court.

type VolleyballFigures

interface VolleyballFigures { length: number; width: number; /** All lines are 5 cm wide and drawn inside the dimensions of the court. */ lineWidth: number; /** Indoors: the axis of the centre line is the middle of the court; the attack line's rear edge is this far from it. Beach volleyball has neither. */ attackLine: number | null; /** The broken lines that extend each attack line outside the side lines, in the FIV…

A volleyball or beach volleyball court, from Rules 1 and 2 of the FIVB's rules. Metres.

type VolleyballLeague

type VolleyballLeague = LeagueBase<"volleyball", VolleyballFigures>;

const WA_SHORT_STAGGER_TABLE

WA_SHORT_STAGGER_TABLE: Readonly<Record<"two-bends" | "three-bends" | "one-bend", readonly number[]>>

The staggers of the lanes of the 200 m Standard Short Track (Marking Plan, 1 January 2026), in metres measured 0.20 m out from the lane line, for lanes 2 to 6. The first row serves the 200 m, 400 m, 800 m, 4 x 400 m and 4 x 800 m (two bends in lanes), the second the 4 x 200 m (three bends in lanes), the third the second leg of the 4 x 200 m (one bend).

const WA_SHORT_TRACK

WA_SHORT_TRACK: AthleticsFigures

The 200 m Standard Short Track (Marking Plan for the World Athletics Standard Short Track, 1 January 2026; Rules 41 to 44): the bends have a construction radius of 17.205 m at the kerb and the centres of the banked bends are 44.983 m apart; lanes are 1.000 m wide on the oval and 1.220 m on the infield straight, four to six of them (Rule 43: 0.90 to 1.22 m, a minimum of four lanes and a maximum of six); the bends are banked at 10.000 degrees. The plan's own transitions (the ascending and descending sections) are drawn here as one circular bend of the same radius, which is ours.

const WA_STAGGER_TABLE

WA_STAGGER_TABLE: Readonly<Record<"200m" | "400m" | "800m" | "4x400m", readonly number[]>>

The staggers of the lanes of the 400 m Standard Track as the Manual prints them (Table 2.2.1.6a), in metres on the running line, for lanes 2 to 8: 200 m, 400 m, 800 m and 4 x 400 m.

const WA_STANDARD_TRACK

WA_STANDARD_TRACK: AthleticsFigures

The 400 m Standard Track (Manual 2.2.1.1, Table 2.2.1.1): two semicircles of 36.50 m radius at the kerb joined by two straights of 84.39 m, lanes 1.22 m wide including the 50 mm line on the right (Rule 14.4), the running line 0.30 m outside the kerb in lane 1 and 0.20 m outside the line on the left in every other lane (Rule 14.2), eight lanes at least for the big competitions (Rule 14.5) and nine at the most (Manual 2.2.1.1). A straight of at least six lanes runs on beyond the oval's with a starting area of 3 m and a run-out of 17 m (Manual 2.2.2.1), and has no most (2.2.2.1). The safety zones are 1.00 m wide (2.2.1.5). The kerb is drawn 0.05 m wide, the narrowest the rules allow (Rule 14.1: 50 to 250 mm).

const WCF_SHEET

WCF_SHEET: SheetFigures

World Curling's sheet, from R1 of The Rules of Curling and Rules of Competition (1 September 2026), in metres as it prints them: the sheet is at least 45.720 m long (146 ft, 44.501 m, in an existing facility) and at most 4.750 m wide (at least 4.420 m); each tee line is 17.375 m from the middle; the back line's outside edge is 1.829 m from the tee, the hog line's inside edge 6.401 m; the circles have radii of 1.829, 1.219 and 0.610 m and the button at least 0.152 m; the hog line is 102 mm wide, the others at most 13 mm; the centre line runs 3.658 m beyond each tee; the hack line is 457 mm long; the courtesy lines are 152 mm long and 1.219 m outside the hog lines; each hack's inside edge is 76 mm from the middle of the centre line, at most 152 mm wide and 203 mm in front of its line.

const WN_NETBALL

WN_NETBALL: NetballFigures

World Netball's court, from Rule 1 (Technical specifications, Court and related areas, items 2 to 8, 13 and 14) of the Rules of Netball (2024 edition), in metres: 30.5 m (100 feet) by 15.25 m (50 feet); two transverse lines dividing it into three equal areas; a centre circle 0.9 m (3 feet) across; at each end a goal circle, a semicircle of radius 4.9 m (16 feet) centred on the middle of the outside of the goal line; lines 50 mm wide; a court surround 3.05 m (10 feet) on every side; a goalpost at the middle of each goal line, its pole 65 to 100 mm across and 3.05 m high, the back of the pole at the outer edge of the goal line, with a ring of 380 mm inside diameter made of 15 mm rod, on a bar 150 mm long projecting from the pole.

const WORLD_BOX_FLOOR

WORLD_BOX_FLOOR: LacrosseFigures

World Lacrosse's box floor (Box Lacrosse Official Playing Rules 2026-2028, v2.0): the diagram's 200 ft by 85 ft, the same crease (1.1.7), goals (1.2.1), restraining lines (1.1.5) and face-off circles (1.1.9) as the NLL's, all lines 2 in (1.1.3). Its Rule 1.2.2 says "25 m from the midline", which does not fit the feet.

const WORLD_MEN_FIELD

WORLD_MEN_FIELD: LacrosseFigures

World Lacrosse's men's field (Men's Field Lacrosse Official Playing Rules 2025-2027, v1.1): 91.4 to 100 m long and 50 to 60 m wide (1.1); goal posts 1.83 m apart and 1.83 m high, 12 m from each end line (2.1, 2.2); the net's anchor 2.1 m behind the goal line (4.1); the crease a circle of 3 m radius (3.1); the goal area lines 22 m from the goal line (5.1); wing lines 18 m from the centre, extending 10 m either side of the centre line (7.1); lines 5 cm, the centre line 10 cm (12.1). The centre is an X or a 10 cm square (6.1).

const WORLD_RUGBY_FIELD

WORLD_RUGBY_FIELD: RugbyFigures

World Rugby's field, from Law 1 of the Laws of the Game, Rugby Union, 2026: the field of play 94 to 100 m between the try lines and 68 to 70 m wide; each in-goal 6 to 22 m deep (10 m is drawn: the Laws name none); the 22-metre lines 22 m from the try lines; the 10-metre dash lines 10 m either side of the half-way line; dash lines 5 m and 15 m from the touchlines and 5 m from the try lines, each dash 5 m; the half-way mark 0.5 m long; the goal posts 5.6 m apart on the try line, the crossbar 3 m to its top edge; flags 2 m outside the touchlines. Law 1 gives no width for the lines: 0.1 m is drawn.

const WORLD_WOMEN_FIELD

WORLD_WOMEN_FIELD: LacrosseFigures

World Lacrosse's women's field (Women's Field Lacrosse Official Playing Rules 2025-2026, v1.1): 91.4 to 100 m by 50 to 60 m (1.A.2); the goal line 12 m from each end line and 1.83 m long (1.A.5); a goal circle of 3 m radius (1.A.8); the 15 m marking area, a semicircle from the middle of the goal line to each goal line extended (1.A.6); the 11 m marking area, with a line from where the goal line extended meets the goal circle at 45 degrees to it out to the 11 m radius (1.A.7); restraining lines 22 m from each goal line (1.A.9); the centre line 3 m long (1.A.10) and the centre circle 9 m in radius (1.A.11); lines 5 to 10 cm (1.A.2). The book prints no depth for the net: the men's 2.1 m is drawn.

const WPV_SITTING

WPV_SITTING: VolleyballFigures

The World ParaVolley's sitting volleyball court, from Rules 1 and 2 of the Official Sitting Volleyball Rules 2025-2028, in metres: 10 by 6 m inside a free zone of at least 3 m (4 m beside the side lines and 6 m behind the end lines in World ParaVolley, World and Official Competitions); lines 5 cm wide inside the dimensions; the attack line's rear edge 2 m from the axis of the centre line and, in those competitions, extended by five 15 cm lines 20 cm apart to 1.75 m; the service zone 6 m wide, limited by two 15 cm lines 20 cm behind the end line; the net 0.80 m wide and 6.5 to 7 m long, 1.15 m (men) or 1.05 m (women) high; each antenna 1.60 m long and 10 mm across; the posts 0.5 to 1.0 m outside the side lines, 1 m in those competitions.

const WSF_SQUASH

WSF_SQUASH: SquashFigures

World Squash's singles court, from clauses 4.01 and 5 and Appendix 1 of its Specifications for Squash Courts (28 November 2017), in metres: 9.75 by 6.40 m between the playing surfaces; markings 50 mm wide; the nearest edge of the short line 4.26 m from the back wall; each service box a square of 1.60 m inside its lines; on the front wall the top of the tin 480 mm up, the lower edge of the service line 1.78 m and of the front wall line 4.57 m, and on the back wall the back wall line 2.13 m.

const WSF_SQUASH_DOUBLES

WSF_SQUASH_DOUBLES: SquashFigures

World Squash's doubles court (Appendix 2, clause 4.02): 7.62 m wide, the rest as the singles court.

const WSF_SQUASH_WIDE

WSF_SQUASH_WIDE: SquashFigures

World Squash's international competition width doubles court (Appendix 2 note 8, clause 4.03): 8.42 m wide, with the top of the tin 330 mm up, for WSF recognised World and Regional events and the Commonwealth Games.

@johnmorrisdotca/kyogijo-stadiums/eras

ERA_CURRENT ERA_SOURCES ERA_SPORTS EraChange eraChanges eraRange EraRules EraRulesOf EraSource EraSpan EraSport eraStates footballIn FootballRules MlbRules NbaRules NhlRules rulesFor seasonLabel

const ERA_CURRENT

ERA_CURRENT: 2026

The season whose rules are "today's", and the one a drawing without a year shows: the data were last checked in 2026. A change scheduled for a later season is planned and is drawn only when that season is asked for.

const ERA_SOURCES

ERA_SOURCES: Readonly<Record<string, EraSource>>

Every page a change was read from, by id. Wikipedia is cited by revision, for facts only.

const ERA_SPORTS

ERA_SPORTS: readonly EraSport[]

The sports with a history, in the order a menu lists them.

type EraChange

interface EraChange { /** `nhl.trapezoid.2005`. */ id: string; sport: EraSport; /** The marking's name: a key of the sport's rules. */ feature: string; /** The first season, or `null` for the state the sport started with. */ from: number | null; value: unknown; what: string; ja: string; sources: string[]; planned: boolean; approx: boolean; }

A change in the markings: one span of one marking that started in some season.

function eraChanges

eraChanges(sport: EraSport): EraChange[]

Every change of a sport's markings, in season order: what moved, from which season, in a sentence in English and in Japanese, and the sources that say so. The state a sport started with is not a change and is left out (see eraStates).

Example

import { eraChanges } from "@johnmorrisdotca/kyogijo-stadiums/eras";
const nhl = eraChanges("nhl");
console.log(nhl.length, nhl[0].id, nhl[0].from);
// 12 nhl.centreLine.1943 1943

function eraRange

eraRange(sport: EraSport): { from: number; to: number; }

The seasons a sport's history is drawn for: from the league's first to today's, or to a planned season when one is announced.

type EraRules

interface EraRules<S extends EraSport = EraSport> { sport: S; /** The season asked for (the year it starts in); today's when none was. */ year: number; /** Its name: `1979–80`. */ season: string; /** The markings. Every one is in feet, except the mound's height in inches. */ rules: EraRulesOf[S]; /** The states in force that are not today's, each as the change that began it (a marking in `gaps` is not listed). */ di…

The markings in force in a season, with what differs from today's and what is not known.

type EraRulesOf

interface EraRulesOf { nhl: NhlRules; nba: NbaRules; nfl: FootballRules; cfl: FootballRules; mlb: MlbRules; }

The rules of a sport in a season.

type EraSource

interface EraSource { id: string; /** The page: the article's title for Wikipedia, else the page's own. */ title: string; /** Who publishes it. */ by: string; /** For Wikipedia (CC BY-SA 4.0): the revision of the article, so that the figure can be checked. */ revision?: number; url: string; /** What was taken from it. */ note?: string; }

Where a change was read.

type EraSpan

interface EraSpan<T> { /** The first season it was in force, or `null` for "from the start" (the state the earliest season of the sport had). */ from: number | null; value: T; /** What it was, in a sentence. */ what: string; /** The same in Japanese. */ ja: string; /** Keys of `ERA_SOURCES`; at least one. */ sources: string[]; /** A rule announced for a season that has not been played yet. It is drawn only when that…

One state of a marking, from a season on.

type EraSport

type EraSport = "nfl" | "cfl" | "nhl" | "nba" | "mlb";

The sports whose markings have a history here. The IFAF's surface is 2021's and has none.

function eraStates

eraStates(sport: EraSport): EraChange[]

The state each marking of a sport started with: the spans that begin "from the start".

function footballIn

footballIn(sport: "nfl" | "cfl" | "ifaf", year?: number): FootballSpec

A football field's spec in a season: the NFL's or the CFL's as FOOTBALL has it today, with the length, the end zones, the hash marks and the goal posts of that season. The IFAF's has no history and is returned as it is.

Example

import { footballIn } from "@johnmorrisdotca/kyogijo-stadiums/eras";
console.log(footballIn("cfl", 1985).endZone / 3, footballIn("cfl", 1986).endZone / 3, footballIn("nfl", 1960).hash / 3);
// 25 20 20

type FootballRules

interface FootballRules { /** Goal line to goal line. */ length: number; /** The depth of each end zone. */ endZone: number; /** The hash marks' distance from each sideline, or `null` before there were hash marks. */ hash: number | null; /** Where the goal posts stand. */ posts: "goal-line" | "end-line"; /** Two poles joined by a crossbar (`h`), or one curved support behind the crossbar (`slingshot`). */ postShape: …

A football field's markings in a season: the NFL's or the CFL's. Lengths are feet.

type MlbRules

interface MlbRules { /** The side of first, second and third base, in feet (home plate is not a base of this size). */ baseSize: number; /** * Where the pitcher throws from: a chalked box whose front edge is `distance` from the back point of home plate, with its * `width` and `depth`, or (from 1893) the rubber, whose front edge is `distance` from it. */ pitcher: { kind: "box" | "plate"; distance: number; width: numb…

The baseball field's markings in a season. Lengths are feet, the mound's height inches.

type NbaRules

interface NbaRules { /** The width of the free-throw lane. */ keyWidth: number; /** The three-point arc's radius from the basket (the corners stay at 22 feet), or none. */ threePoint: { radius: number } | null; /** The restricted area (the no-charge arc) under each basket. */ restrictedArea: boolean; }

The NBA court's markings in a season. Lengths are feet.

type NhlRules

interface NhlRules { /** The centre red line across the middle of the ice. */ centreLine: boolean; /** The goal line's distance from the end boards. */ goalLine: number; /** The blue line's distance from the end boards. */ blueLine: number; /** The goaltender's trapezoid behind the net, or none. */ trapezoid: { atGoalLine: number; atBoards: number } | null; /** The gap between the two hash marks beside an end-zone f…

The NHL rink's markings in a season. Lengths are feet.

function rulesFor

rulesFor<S extends EraSport>(sport: S, year?: number): EraRules<S>

The markings of a sport's surface in a season: what the rule books and the leagues had painted on it then. Without a year it is today's (ERA_CURRENT). A season is named by the year it starts in (1979 is the 1979–80 season). differences lists what is not as it is today, gaps the markings that no source covers for the season, and approx those whose measure is not cited.

Example

import { rulesFor } from "@johnmorrisdotca/kyogijo-stadiums/eras";
const nba = rulesFor("nba", 1995);
console.log(nba.season, nba.rules.threePoint, nba.rules.restrictedArea, nba.differences.length);
// 1995–96 { radius: 22 } false 2

function seasonLabel

seasonLabel(sport: EraSport, year: number): string

The name of a season as the sport writes it: 1979–80 for the NHL and the NBA, which play across the new year, and 1974 for the NFL, the CFL and baseball. The year is the one the season starts in.

Example

import { seasonLabel } from "@johnmorrisdotca/kyogijo-stadiums/eras";
console.log(seasonLabel("nba", 1979), seasonLabel("nhl", 1999), seasonLabel("nfl", 1974));
// 1979–80 1999–00 1974

@johnmorrisdotca/kyogijo-stadiums/arenas

Arena ARENA_IDS arenaById arenaByName arenaByWikidata ArenaCapacity arenaCourtIn ArenaData ArenaName arenaNameIn arenaOfficialName arenaRinkIn ARENAS arenasByName ArenaSize ArenaSource arenaSourceUrl officialName PlaceAlias PlaceNameKind plainName

type Arena

type Arena = ArenaData;

An arena as the package holds it: the data, with the sizes in the unit they were published in (see arenaRinkIn and arenaCourtIn for them in feet).

const ARENA_IDS

ARENA_IDS: readonly string[]

The keys of ARENAS, in the order of the data: the arenas in use first, then those of the past.

function arenaById

arenaById: (id: string | undefined) => Arena | undefined

The arena with a key, or undefined.

function arenaByName

arenaByName: (name: string | undefined) => Arena | undefined

The arena a name belongs to (the best match when several answer to it), or undefined.

function arenaByWikidata

arenaByWikidata: (wikidata: string | undefined) => Arena | undefined

The arena whose Wikidata item is Q894567, or undefined.

type ArenaCapacity

interface ArenaCapacity { hockey?: number; basketball?: number; }

The seats an arena holds for each sport, as the infobox of its article gives them (the first number it gives for the sport).

function arenaCourtIn

arenaCourtIn: (arena: Arena | undefined, year?: number) => { length: number; width: number; } | undefined

The size of an arena's court in feet, in a year or today, when the sources publish one that differs from the NBA's 94 by 50 feet; else undefined.

type ArenaData

interface ArenaData { /** The key: `boston-garden`. */ id: string; /** The Wikidata item of the arena (`Q894567`), the key to join an arena to other data. */ wikidata: string; /** The everyday name in English (`Boston Garden`, `TD Garden`); the title of the English article is one of the `aliases`. */ name: string; /** Names the arena had for some years, in order of time: where two share a year the later one is the n…

An arena as the data gives it: the rink and the court are the sizes published where they differ from the league's.

type ArenaName

interface ArenaName { name: string; from: number; to: number; }

A name an arena had for some years, first to last.

function arenaNameIn

arenaNameIn(arena: Arena, year?: number, language?: Language): string

The name an arena had in a year (the name of the time in English; in Japanese there is one name for every year).

function arenaOfficialName

arenaOfficialName(arena: Arena, language?: Language, year?: number): string | undefined

The name an arena officially has in a language, when it is not the everyday name; else undefined.

function arenaRinkIn

arenaRinkIn: (arena: Arena | undefined, year?: number) => { length: number; width: number; } | undefined

The size of an arena's rink in feet, in a year or today (the latest), when the sources publish one that differs from the NHL's; else undefined and the NHL's 200 by 85 feet is the rink.

Example

import { arenaById, arenaRinkIn } from "@johnmorrisdotca/kyogijo-stadiums/arenas";
console.log(arenaRinkIn(arenaById("boston-garden")!), arenaRinkIn(arenaById("matthews-arena")!, 1926), arenaRinkIn(arenaById("td-garden")!));
// { length: 190.9, width: 83 } { length: 220, width: 90 } undefined

const ARENAS

ARENAS: Readonly<Record<string, ArenaData>>

Every arena by its key: the homes of the NHL's, the NBA's, the WNBA's and the PWHL's clubs, today's and the old ones, each with where it stands, when it was in use, its names, its seats for each sport and, where the sources publish one that differs from the league's, the size of its rink or its court.

Example

import { ARENAS } from "@johnmorrisdotca/kyogijo-stadiums/arenas";
const garden = ARENAS["boston-garden"];
console.log(garden.name, garden.city, garden.from, garden.to, garden.capacity?.hockey);
// Boston Garden Boston 1928 1995 14448

function arenasByName

arenasByName: (name: string | undefined) => Arena[]

The arenas that answer to a name, best match first (see placesByName): the everyday names, then the official and cited names, then the former names and the nicknames.

type ArenaSize

interface ArenaSize { length: number; width: number; unit: "ft" | "m"; /** The first and the last year it had this size, where the source gives them. */ from?: number; to?: number; /** Where it was read: the article and its revision, and in a few words what it says (not a copied passage). */ source: string; }

A published size of an ice rink or a basketball court, in the unit it was published in, with where it was read and, if the source dates it, the years it had that size.

type ArenaSource

interface ArenaSource { title: string; revision: number; }

Where an arena's facts were read: an article of Wikipedia at a revision.

function arenaSourceUrl

arenaSourceUrl(source: ArenaSource, language?: "en" | "ja"): string

The address of the revision of an article that an arena's facts were read from.

function officialName

officialName(place: PlaceNames, language: Language, shown?: string, year?: number): string | undefined

The name a place officially has, in a language, when it is not the everyday name: the official alias in that language that stands in year (without a year, the one that stands today). In English, where there is none in English, a name in the place's own language that is written in Latin letters stands for it (Estadio Banorte). undefined when there is none or when it is the name shown.

Example

import { PARKS, officialName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
const koshien = PARKS["hanshin-koshien-stadium"];
console.log(officialName(koshien, "en"), "/", officialName(koshien, "ja"));
// Hanshin Koshien Stadium / 阪神甲子園球場

type PlaceAlias

interface PlaceAlias { name: string; /** The language of the name: `en`, `ja`, `es`, `de`... */ lang: string; kind: PlaceNameKind; /** The first and the last year the name was in use, where a source dates it. */ from?: number; to?: number; /** Where the name was read: an article and its revision, or a Wikidata item and property (`Wikidata Q49136 P1448`). */ source?: string; /** For a `wikipedia` alias, the revision …

Another name of a place: a title, an earlier or a sponsor's name, a nickname.

type PlaceNameKind

type PlaceNameKind = "official" | "wikipedia" | "sponsor" | "former" | "nickname";

What an alias is. official is the name the owner or operator gives it (when it is not the everyday one), wikipedia the title of an article that is cited, sponsor a naming-rights name, former an earlier name and nickname a short form or a name people use.

function plainName

plainName(text: string): string

A name made fit to compare: width, case, accents, kana (hiragana is katakana), the old and new forms of the same sound (ヂ, ヅ, ヴ), the long vowel mark and every space, dot and mark of punctuation put aside, so that Estádio Maracanã and estadio maracana are one name, as are ヤンキー・スタジアム and ヤンキースタジアム.

@johnmorrisdotca/kyogijo-stadiums/grounds

Ground GROUND_IDS groundById groundByName groundByWikidata GroundCompetition GroundData GroundName groundNameIn groundOfficialName GROUNDS groundsByName groundsHosting GroundSource groundSourceUrl GroundTournament officialName PlaceAlias PlaceNameKind plainName

type Ground

interface Ground extends Omit<GroundData, "pitch"> { /** The pitch, goal line to goal line and touchline to touchline, in metres to the centimetre. */ pitch: { length: number; width: number }; /** The unit the article published the size in; `m` for a ground at the default size. */ pitchUnit: "m" | "yd"; }

A ground, with its pitch in metres: the published size, or the international 105 by 68 m of a ground that has none.

const GROUND_IDS

GROUND_IDS: readonly string[]

The keys of GROUNDS: the men's World Cup final venues in order of the tournaments, the other venues of 2026, the Women's final venues not among them, then club grounds.

function groundById

groundById: (id: string | undefined) => Ground | undefined

The ground with a key, or undefined.

function groundByName

groundByName: (name: string | undefined) => Ground | undefined

The ground a name belongs to (see groundsByName; the best match when several answer to it), or undefined.

function groundByWikidata

groundByWikidata: (wikidata: string | undefined) => Ground | undefined

The ground whose Wikidata item is Q155174, or undefined.

type GroundCompetition

type GroundCompetition = "world-cup" | "womens-world-cup";

The competitions a ground can have staged: the men's World Cup and the Women's.

type GroundData

interface GroundData { /** The key: `maracana`. */ id: string; /** The Wikidata item of the ground (`Q155174`), the key to join a ground to other data. */ wikidata: string; /** The everyday name in English, the one English sports media use (`Reliant Stadium`, `San Siro`); the title of the English article is one of the `aliases`. */ name: string; /** * Names the ground had for some years, in order of time: where two …

A soccer ground as the data gives it: the pitch is the size the article publishes, in the unit it publishes it in.

type GroundName

interface GroundName { name: string; from: number; to: number; }

A name a ground had for some years, first to last.

function groundNameIn

groundNameIn(ground: Ground, year?: number, language?: Language): string

A ground's name in a year: the name it had then, or its everyday name, in English. In Japanese there is one name for every year.

Example

import { GROUNDS, groundNameIn } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
console.log(groundNameIn(GROUNDS["soccer-city"], 2010), "/", groundNameIn(GROUNDS["soccer-city"], 2012));
// Soccer City / FNB Stadium

function groundOfficialName

groundOfficialName(ground: Ground, language?: Language, year?: number): string | undefined

The name a ground officially has in a language, when it is not the everyday name (Parc Olympique Lyonnais for Groupama Stadium), in the year given or today; else undefined.

const GROUNDS

GROUNDS: Readonly<Record<string, Ground>>

Every ground by its key: the venues of the men's World Cup finals and of the 2026 tournament, of the Women's World Cup finals, and well-known club grounds, each with its pitch at its own size where the English article publishes one.

Example

import { GROUNDS, GROUND_IDS } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
const { name, city, pitch, pitchSize } = GROUNDS["old-trafford"];
console.log(name, city, pitch.length, pitch.width, pitchSize, GROUND_IDS.length);
// Old Trafford Manchester 105 68 published 64

function groundsByName

groundsByName: (name: string | undefined) => Ground[]

The grounds that answer to a name, best match first: the everyday names, then the official and cited names, the sponsors' and the names the ground had in some years, then the former names and the nicknames. A name is compared without case, accents, spaces or punctuation, in full-width or half-width, and in Japanese with hiragana or katakana and with or without the long vowel mark.

Example

import { groundByName, groundsByName } from "@johnmorrisdotca/kyogijo-stadiums/grounds";
console.log(groundByName("Giuseppe Meazza Stadium")?.id, groundByName("日産スタジアム")?.id, groundByName("Estadio Jornalista Mario Filho")?.id, groundsByName("stadium australia").length);
// san-siro nissan-stadium-yokohama maracana 1

function groundsHosting

groundsHosting: (competition: GroundCompetition, year: number) => Ground[]

The grounds of this list that staged matches of a World Cup, in the order of GROUND_IDS.

type GroundSource

interface GroundSource { title: string; revision: number; }

Where a ground's facts were read: an article of Wikipedia at a revision.

function groundSourceUrl

groundSourceUrl(source: GroundSource, language?: "en" | "ja"): string

The address of the revision of an article that a ground's facts were read from.

type GroundTournament

interface GroundTournament { competition: GroundCompetition; year: number; /** The final was played here. */ final?: true; }

A tournament a ground staged matches of.

function officialName

officialName(place: PlaceNames, language: Language, shown?: string, year?: number): string | undefined

The name a place officially has, in a language, when it is not the everyday name: the official alias in that language that stands in year (without a year, the one that stands today). In English, where there is none in English, a name in the place's own language that is written in Latin letters stands for it (Estadio Banorte). undefined when there is none or when it is the name shown.

Example

import { PARKS, officialName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
const koshien = PARKS["hanshin-koshien-stadium"];
console.log(officialName(koshien, "en"), "/", officialName(koshien, "ja"));
// Hanshin Koshien Stadium / 阪神甲子園球場

type PlaceAlias

interface PlaceAlias { name: string; /** The language of the name: `en`, `ja`, `es`, `de`... */ lang: string; kind: PlaceNameKind; /** The first and the last year the name was in use, where a source dates it. */ from?: number; to?: number; /** Where the name was read: an article and its revision, or a Wikidata item and property (`Wikidata Q49136 P1448`). */ source?: string; /** For a `wikipedia` alias, the revision …

Another name of a place: a title, an earlier or a sponsor's name, a nickname.

type PlaceNameKind

type PlaceNameKind = "official" | "wikipedia" | "sponsor" | "former" | "nickname";

What an alias is. official is the name the owner or operator gives it (when it is not the everyday one), wikipedia the title of an article that is cited, sponsor a naming-rights name, former an earlier name and nickname a short form or a name people use.

function plainName

plainName(text: string): string

A name made fit to compare: width, case, accents, kana (hiragana is katakana), the old and new forms of the same sound (ヂ, ヅ, ヴ), the long vowel mark and every space, dot and mark of punctuation put aside, so that Estádio Maracanã and estadio maracana are one name, as are ヤンキー・スタジアム and ヤンキースタジアム.

@johnmorrisdotca/kyogijo-stadiums/events

conventionPlacement EVENT_IDS EVENT_SOURCES eventById eventFit eventOptions EVENTS EventSource eventSources Fit fitOf KYOGIJO_SPECIAL_STYLE PlacementBasis PlacementRule SpecialEvent SpecialEventData Words

function conventionPlacement

conventionPlacement(rule: PlacementRule, host: HostOptions, o?: Pick<SurfaceOptions, "year" | "detail" | "stripes" | "stripePattern" | "exactLines">): Placement

The place a convention gives a surface in its host, in the host's frame: midfield the middle of the host's field, deck the middle of the deck, both with the surface as it lies; infield the middle of a ballpark's infield, with the surface's length across the field from first base to third; diamond a ballpark's infield on the middle of the host.

const EVENT_IDS

EVENT_IDS: readonly string[]

The keys of EVENTS, in the order the games were played.

const EVENT_SOURCES

EVENT_SOURCES: Readonly<Record<string, EventSource>>

Every source, by key. Read on 2026-10-13.

function eventById

eventById: (id: string | undefined) => SpecialEvent | undefined

The game with a key, or undefined.

function eventFit

eventFit: (id: string) => Fit

How a game's surface sits in its host: wholly inside its outline, or the share of it that lies outside. For a ballpark the outline is the fair ground inside the fence.

function eventOptions

eventOptions(id: string): SurfaceOptions

The options that draw a game: its sport and season, its host and where its surface stands in it. Spread them into drawKyogijo with a score or without.

Example

import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
import { eventOptions } from "@johnmorrisdotca/kyogijo-stadiums/events";
const svg = drawKyogijo({ ...eventOptions("winter-classic-2009") });
console.log(svg.includes("2009 NHL Winter Classic"), svg.includes("Wrigley Field"));
// true true

const EVENTS

EVENTS: Readonly<Record<string, SpecialEventData>>

The special games, by key, in the order they were played: the NHL's outdoor games in ballparks and football stadiums, a college basketball game on the deck of a carrier and Major League Baseball in London. Each has its dates, its venue, what its surface was set inside and where, and the sources of all of it.

Example

import { EVENTS, EVENT_IDS } from "@johnmorrisdotca/kyogijo-stadiums/events";
const { name, dates, venue } = EVENTS["winter-classic-2009"];
console.log(name.en, dates[0], venue.en, EVENT_IDS.length);
// 2009 NHL Winter Classic 2009-01-01 Wrigley Field 18

type EventSource

interface EventSource { id: string; title: string; /** The author and the publication, or the encyclopedia and its licence. */ by: string; /** The revision of an article of Wikipedia, or the date of a page. */ edition: string; url: string; /** What it gave. */ gave: string; }

A source a game was read from.

function eventSources

eventSources(event: SpecialEvent): EventSource[]

The sources of a game, in the order they are listed in its entry.

type Fit

interface Fit { /** True when every point of the guest's surface is inside the host's outline. */ inside: boolean; /** The share of the guest's area outside the host's outline, to a thousandth. */ outside: number; }

How a guest sits in its host: the share of the guest's surface that lies outside the host's outline, from 0 (wholly inside) to 1.

function fitOf

fitOf(o: SurfaceOptions): Fit

How the guest sits in its host, worked out on a grid over the guest's outline (a rink's rounded corners and a fence's curve are the guest's own). For a ballpark the host's outline is the fair ground inside the fence: a rink across the infield reaches into foul ground, which this reports as outside.

const KYOGIJO_SPECIAL_STYLE

KYOGIJO_SPECIAL_STYLE: string

The rules of every host's sport.

type PlacementBasis

type PlacementBasis = { kind: "published"; note: Words; sources: string[] } | { kind: "convention"; rule: PlacementRule };

How the place of the surface was decided.

type PlacementRule

type PlacementRule = "infield" | "midfield" | "diamond" | "deck";

The ways a surface is placed when no source gives where it stood.

type SpecialEvent

type SpecialEvent = SpecialEventData;

A special game: a game played on a surface set inside another.

type SpecialEventData

interface SpecialEventData { /** The key: `winter-classic-2009`. */ id: string; name: Words; /** The day or days it was played, `YYYY-MM-DD`. */ dates: string[]; /** The venue's name when the game was played. */ venue: Words; /** The surface the game was played on, as the options of a drawing: its sport, its season and the fence or the league that makes it what it was. */ guest: { sport: Sport; year: number; league?…

A game played on a surface set inside another.

type Words

interface Words { en: string; ja: string; }

The words of a thing in both languages.

@johnmorrisdotca/kyogijo-stadiums/atlas

atlasFacets AtlasFacets Hall HallBasis HALLS HallSurface hallSurfaces nameJaOf officialName Place PlaceAlias placeById placeByName PlaceFilter PlaceKind PlaceNameKind PLACES placesBy placesByClub placesByName PlaceSport PlaceStatus PlaceSurface plainName surfaceOf surfacesOf SurfaceSport Tenant TENANT_JAPANESE_NAMES TenantArticle TenantFilter TenantNameJa TenantNameKind TENANTS tenantsOf TenantSource TenantSport

function atlasFacets

atlasFacets(): AtlasFacets

The sports, leagues and countries of the places, and the span of years they give.

Example

import { atlasFacets } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
const { sports, leagues, countries, years } = atlasFacets();
console.log(sports.join(), leagues.length, leagues.includes("CFL"), countries.length, years.min);
// baseball,basketball,budo,hockey,soccer 90 true 23 1877

type AtlasFacets

interface AtlasFacets { sports: PlaceSport[]; leagues: string[]; countries: string[]; /** The first and the last year any place gives (`from` or `to`). */ years: { min: number; max: number }; }

What the places hold, for the controls of a page: each list sorted, the years as far as the data gives them.

type Hall

interface Hall { id: string; name: string; nameJa: string; readingJa?: string; aliases: readonly PlaceAlias[]; city: string; country: string; lat: number; lon: number; wikidata: string; /** The first year of the institution or the building (the Kodokan's is its founding, 1882). */ from: number; /** What the first year is, in a phrase. */ fromWhat: { en: string; ja: string }; /** The most the hall seats or holds, whe…

A hall as the data gives it.

type HallBasis

type HallBasis = "documented" | "count" | "generic";

What is known of a surface's layout. documented: the floor, its size and its contest areas are from a source (the dohyō of the Association's rules). count: a source gives the number of mats, and the shape is plausible. generic: no source gives the size or the layout, and a plausible one is drawn.

const HALLS

HALLS: readonly Hall[]

The halls, in the order of their surfaces' fame: the Kodokan, the Budokan, the Kokugikan, the Butokuden and the Budokwai.

type HallSurface

interface HallSurface { /** The key within the hall: `main-dojo`. */ id: string; name: { en: string; ja: string }; sport: BudoSport; /** The options (beyond `sport`) that draw it. */ options: { league?: string; budo?: BudoStyle; dojo?: DojoSpec }; basis: HallBasis; /** The number of mats (jō) a source gives for the floor, where it gives one. */ mats?: number; /** What is known and what is drawn, in a sentence each. …

One surface a hall offers: a floor of a sport, with the options that draw it.

function hallSurfaces

hallSurfaces: (place: Place) => readonly HallSurface[]

Every surface a hall of the budō offers, each with its name, how much of it is documented (basis) and the options that draw it, in the order of the hall's own list. A place that is not a hall has none.

Example

import { hallSurfaces, placeById } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
console.log(hallSurfaces(placeById("kodokan")!).map((surface) => `${surface.id} ${surface.mats}`).join());
// main-dojo 420,main-dojo-earlier 420,school-dojo 240,international-dojo 192,womens-dojo 240,boys-dojo 114

function nameJaOf

nameJaOf: (tenant: Pick<Tenant, "wikidata">) => TenantNameJa | undefined

The note on a tenant's name in Japanese, if its everyday form is a curated one.

function officialName

officialName(place: PlaceNames, language: Language, shown?: string, year?: number): string | undefined

The name a place officially has, in a language, when it is not the everyday name: the official alias in that language that stands in year (without a year, the one that stands today). In English, where there is none in English, a name in the place's own language that is written in Latin letters stands for it (Estadio Banorte). undefined when there is none or when it is the name shown.

Example

import { PARKS, officialName } from "@johnmorrisdotca/kyogijo-stadiums/parks";
const koshien = PARKS["hanshin-koshien-stadium"];
console.log(officialName(koshien, "en"), "/", officialName(koshien, "ja"));
// Hanshin Koshien Stadium / 阪神甲子園球場

type Place

interface Place { /** The key in `PARKS` or in `GROUNDS` (`fenway-park`, `maracana`): unique across both. */ id: string; kind: PlaceKind; /** The everyday name in English. */ name: string; /** The everyday name in Japanese. */ nameJa: string; /** The reading of `nameJa` in kana, where it has kanji. */ readingJa?: string; /** The everyday name in the language of the place, where it is not English. */ localName?: stri…

One place on the atlas: a ballpark or a soccer ground, with where it is and when it was in use.

type PlaceAlias

interface PlaceAlias { name: string; /** The language of the name: `en`, `ja`, `es`, `de`... */ lang: string; kind: PlaceNameKind; /** The first and the last year the name was in use, where a source dates it. */ from?: number; to?: number; /** Where the name was read: an article and its revision, or a Wikidata item and property (`Wikidata Q49136 P1448`). */ source?: string; /** For a `wikipedia` alias, the revision …

Another name of a place: a title, an earlier or a sponsor's name, a nickname.

function placeById

placeById: (id: string | undefined) => Place | undefined

The place with a key, or undefined.

function placeByName

placeByName: (name: string | undefined) => Place | undefined

The place a name belongs to (see placesByName; the best match when several answer to it), or undefined.

type PlaceFilter

interface PlaceFilter { /** A sport a place is drawn for: an arena is found by `hockey` and by `basketball` if clubs of both played there. */ sport?: PlaceSport | readonly PlaceSport[]; kind?: PlaceKind | readonly PlaceKind[]; /** A league or competition from `Place.leagues`: a tenant's league (`CFL`) or a tournament. */ league?: string | readonly string[]; /** ISO 3166-1 alpha-2. */ country?: string | readonly stri…

What placesBy keeps: every field given must match. A list matches any of its members.

type PlaceKind

type PlaceKind = "park" | "ground" | "hall" | "arena";

What a place is: a ballpark (of PARKS), a soccer ground (of GROUNDS), a hall of the budō (of HALLS: a dojo, an arena, a sumo hall) or a hockey and basketball arena (of ARENAS).

type PlaceNameKind

type PlaceNameKind = "official" | "wikipedia" | "sponsor" | "former" | "nickname";

What an alias is. official is the name the owner or operator gives it (when it is not the everyday one), wikipedia the title of an article that is cited, sponsor a naming-rights name, former an earlier name and nickname a short form or a name people use.

const PLACES

PLACES: readonly Place[]

function placesBy

placesBy(filter?: PlaceFilter): Place[]

The places that match every field of the filter, in the order of PLACES.

Example

import { placesBy } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
const japan = placesBy({ sport: "baseball", country: "JP", status: "current" });
const in1950 = placesBy({ year: 1950, country: "US" });
console.log(japan.length, in1950.some((place) => place.id === "ebbets-field"));
// 14 true

function placesByClub

placesByClub(name: string | undefined): Place[]

The places a club plays, or played, its home matches in, best match first: a club whose name is the one given (ignoring case, accents, spaces and punctuation, width, and in Japanese hiragana or katakana), then a club whose name has every word given in it (whitecaps, lions, ライオンズ); the names are the club's in English and in Japanese.

Example

import { placesByClub } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
console.log(placesByClub("BC Lions").map((place) => place.id).join(), placesByClub("Montreal Expos").map((place) => place.id).join());
// bc-place jarry-park-stadium,olympic-stadium-montreal

function placesByName

placesByName: (name: string | undefined) => Place[]

The places that answer to a name, best match first: the everyday names, then the official and cited names and the sponsors', then the former names and the nicknames. The comparison ignores case, accents, spaces and punctuation, width, and in Japanese hiragana or katakana and the long vowel mark.

Example

import { placeByName, placesByName } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
console.log(placeByName("Koshien Stadium")?.id, placeByName("サン・シーロ")?.id, placeByName("Estadio Jornalista Mario Filho")?.id, placesByName("wrigley field").map((place) => place.id).join());
// hanshin-koshien-stadium san-siro maracana wrigley-field,wrigley-field-los-angeles

type PlaceSport

type PlaceSport = "baseball" | "soccer" | "budo" | "hockey" | "basketball";

The sport a place is drawn for: baseball parks have a fence, soccer grounds a pitch, the halls of the budō (budo) the mats, the dohyō or the range of their sports, and arenas an ice rink (hockey) and a court (basketball).

type PlaceStatus

type PlaceStatus = "current" | "past";

current is a ground a club or a competition uses today, past one it has left, closed or pulled down.

type PlaceSurface

type PlaceSurface = | { sport: BudoSport; league?: string; budo?: BudoStyle; dojo?: DojoSpec } | { sport: "mlb"; park: string } | { sport: "soccer"; ground?: string } | { sport: "nhl" | "nba"; arena?: string } | { sport: "nfl" | "cfl" | "afl" | "cricket" } | { sport: "rugby"; league?: "nrl" };

The options drawKyogijo takes to draw a surface a place offers.

function plainName

plainName(text: string): string

A name made fit to compare: width, case, accents, kana (hiragana is katakana), the old and new forms of the same sound (ヂ, ヅ, ヴ), the long vowel mark and every space, dot and mark of punctuation put aside, so that Estádio Maracanã and estadio maracana are one name, as are ヤンキー・スタジアム and ヤンキースタジアム.

function surfaceOf

surfaceOf(place: Place): { sport: "mlb"; park: string; } | { sport: "soccer"; ground: string; } | { sport: "nhl" | "nba"; arena: string; } | PlaceSurface surfaceOf(place: Place, sport: SurfaceSport): PlaceSurface | undefined

The options drawKyogijo takes to draw a place's surface. With no sport, the place's own: a park's fence or a ground's pitch at its own size. With a sport (the sport of drawKyogijo: cfl, nfl, mlb, soccer, nba, nhl, rugby, afl, cricket), the surface the place offers for it (surfacesOf), or undefined when no club that plays that sport has played there: surfaceOf(bcPlace, "cfl") is { sport: "cfl" } and surfaceOf(fenwayPark, "cfl") is undefined.

Example

import { placeById, surfaceOf } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
const bcPlace = placeById("bc-place")!;
console.log(surfaceOf(bcPlace), surfaceOf(bcPlace, "cfl"), surfaceOf(bcPlace, "mlb"));
// { sport: 'soccer', ground: 'bc-place' } { sport: 'cfl' } undefined

function surfacesOf

surfacesOf(place: Place): PlaceSurface[]

Every surface a place offers, the place's own first: a park's fence (mlb with the park) or a ground's pitch at its size (soccer with the ground), then one for each sport a tenant plays that the package draws (a CFL field for BC Place, an NFL field for MetLife Stadium, the rugby league field for Stadium Australia), each once. A ground offers no ballpark, since its fence is not known; a park that a soccer club has played in offers the pitch at the international size, since its length and width are not known.

Example

import { placeById, surfacesOf } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
console.log(surfacesOf(placeById("bc-place")!).map((surface) => surface.sport).join());
// soccer,cfl

type SurfaceSport

type SurfaceSport = PlaceSurface["sport"];

The sports surfaceOf can be asked for.

type Tenant

interface Tenant { /** The club's name in English: the one the place's article gives it (`Montreal Expos`, `Boston Braves` for a club named differently then), else Wikidata's label. */ club: string; /** The club's name in Japanese as Japanese media say it (`BCライオンズ`), where there is one for this name of the club: the curated form in `TENANT_NAMES_JA` if the club has one, else Wikidata's label. */ clubJa?: string; /*…

A club that plays, or played, its home matches in a place, and when.

const TENANT_JAPANESE_NAMES

TENANT_JAPANESE_NAMES: Readonly<Record<string, TenantNameJa>>

The clubs whose name in Japanese is the everyday form and not Wikidata's label, by Wikidata item, with why and the source of each: nameJaOf(tenant) finds a tenant's.

type TenantArticle

interface TenantArticle { title: string; revision: number; }

The article of English Wikipedia, at a revision, that a place's tenants were read from.

type TenantFilter

interface TenantFilter { /** Keep the clubs that played there in this year. A bound the data does not give does not shut a club out. */ year?: number; league?: string | readonly string[]; sport?: TenantSport | readonly TenantSport[]; /** `true` keeps the clubs that play there still (no last year), `false` those that have left. */ current?: boolean; }

What tenantsOf keeps: every field given must match.

type TenantNameJa

interface TenantNameJa { /** The name Japanese media use, which the atlas shows as `clubJa`. */ ja: string; kind: TenantNameKind; /** What was checked, where and when. */ note: string; }

The everyday Japanese form of a club's name where it is not Wikidata's label, and the source of it.

type TenantNameKind

type TenantNameKind = "abbreviation" | "formal" | "title" | "era";

Why a club's name in Japanese is not Wikidata's label: the label is a transliteration of an abbreviation (abbreviation), of a formal name or a suffix such as FC that Japanese media do not use (formal), a page title with a disambiguator or "history of" (title), or the club's name in another period (era).

const TENANTS

TENANTS: Readonly<Record<string, readonly Tenant[]>>

The tenants of every place that has any, by the place's key: the current ones first, by the year they came, then those that have left, the latest to leave first.

function tenantsOf

tenantsOf(place: Place | string | undefined, filter?: TenantFilter): Tenant[]

The clubs that play, or played, their home matches in a place, the current ones first (by the year they came) and then those that have left (the latest to leave first). Give a place, or its key.

Example

import { placeById, tenantsOf } from "@johnmorrisdotca/kyogijo-stadiums/atlas";
const expos = tenantsOf("olympic-stadium-montreal").map((t) => `${t.club} (${t.league}) ${t.from}-${t.to ?? ""}`);
console.log(expos[0], tenantsOf(placeById("bc-place")!, { current: true }).map((t) => t.club).join(", "));
// Montreal Expos (MLB) 1977-2004 BC Lions, Vancouver Whitecaps FC

type TenantSource

interface TenantSource { /** The Wikidata statement that names the club as a tenant (CC0): `P466` (the place lists the club as an occupant), `P115` (the club lists the place as its home venue), or both. Absent where only the article names it. */ wikidata?: "P466" | "P115" | "P466 P115"; /** The article and revision of English Wikipedia (CC BY-SA 4.0) whose `tenants` line in the place's infobox lists the club. Absent…

Where a tenant was read.

type TenantSport

type TenantSport = "baseball" | "soccer" | "american-football" | "canadian-football" | "basketball" | "ice-hockey" | "rugby-union" | "rugby-league" | "australian-football" | "lacrosse" | "cricket";

The sport a tenant plays: what decides the surface a place offers for it (surfaceOf).

@johnmorrisdotca/kyogijo-stadiums/athletics

ARENA ATHLETICS_LEAGUES AthleticsEntry AthleticsFieldEvent AthleticsFigures AthleticsInfield athleticsLeague AthleticsLeague athleticsPlan AthleticsPlan AthleticsRace AthleticsRaceOptions AthleticsResult AthleticsRunner AthleticsStyle AthleticsTrackLook breaklineStagger distanceOn distanceRun eventsOf Facility FIELD_EVENT_IDS FIELD_EVENTS FieldAttempt fieldEventById FieldEventInfo fieldMarks FieldMarksOptions FieldTotals firstStart formatTime INDOOR_RACES JAVELIN_HALF KYOGIJO_ATHLETICS_STYLE KYOGIJO_TRACK_STYLE laneDraw LaneDrawTotals laneEdge laneRadius lap markRace MBox MMark MRange Oval pose Pose RACE_IDS raceById RaceInfo RaceMarks raceReplay RaceReplay RaceReplayOptions raceResults RACES racesOf ResultsTotals scPose SECTOR_ANGLE SECTOR_REACH stagger staggerOf StartPlacement STEEPLECHASE_HURDLE STEEPLECHASE_INSIDE STEEPLECHASE_OUTSIDE TRACK_COLOURS WA_SHORT_STAGGER_TABLE WA_SHORT_TRACK WA_STAGGER_TABLE WA_STANDARD_TRACK WaterJump

const ARENA

ARENA: { readonly turf: 12; readonly javelinApex: number; readonly shotWest: { readonly out: 13.5; readonly across: 18.15; }; readonly shotEast: { readonly out: 14.2; readonly across: 18.5; }; readonly discus: { readonly out: 20.4; readonly across: -24.1; readonly heading: number; }; readonly vaultA: { readonly out: 23.26; readonly box: 16.5; }; readonly vaultB: { readonly out: 15.82; readonly box: 20.7; }; readonly…

Where the standard stadium puts each facility, in metres from the centre of the semicircle of its segment (CP1 at the left, CP2 at the right), read from the Marking Plan.

const ATHLETICS_LEAGUES

ATHLETICS_LEAGUES: readonly AthleticsLeague[]

The athletics leagues: the outdoor track (the sport's own) and the covered one.

type AthleticsEntry

interface AthleticsEntry { /** The lane, from 1. */ lane: number; name: string; /** The country's code or name, for the table. */ country?: string; /** The flag: an SVG document as text, or an `https:` or relative address, as a team's `logo`. */ flag?: string; bib?: string | number; }

An athlete in a lane.

type AthleticsFieldEvent

type AthleticsFieldEvent = "high-jump" | "pole-vault" | "long-jump" | "triple-jump" | "shot-put" | "discus-throw" | "hammer-throw" | "javelin-throw";

The field events a drawing can mark in the infield, and alone at a large scale (layout).

type AthleticsFigures

interface AthleticsFigures { /** The length of each straight (the distance between the centres of the semicircles, for the outdoor track's). */ straight: number; /** The radius of each bend at the inside of the track, where the kerb is. */ radius: number; /** The width of a lane, the line on its right included. */ lane: number; /** The least, the most and the drawn number of lanes of the oval. */ lanes: { min: numbe…

The figures of an oval track, in metres.

type AthleticsInfield

type AthleticsInfield = "pitch" | "grass" | "none";

What lies inside the track: the soccer pitch the package draws (pitch), plain mown grass, or nothing but the track's own surface.

function athleticsLeague

athleticsLeague(o: Pick<SurfaceOptions, "sport" | "league">): AthleticsLeague

The league a set of options asks for: the outdoor standard track, or the covered one.

type AthleticsLeague

type AthleticsLeague = LeagueBase<"athletics", AthleticsFigures>;

A league with an athletics track of its own.

function athleticsPlan

athleticsPlan(o: Pick<SurfaceOptions, "sport" | "league" | "athletics" | "units" | "pitch" | "detail"> & Partial<Pick<SurfaceOptions, "range" | "ground">>): AthleticsPlan

The standard track and everything the options ask to be marked on it, as a plan.

type AthleticsPlan

interface AthleticsPlan { sport: "athletics"; league: string; /** True for the covered 200 m track. */ indoor: boolean; figures: AthleticsFigures; oval: Oval; lanes: number; straight: StraightPlan | undefined; track: AthleticsTrackLook; infield: AthleticsInfield; /** The playing surface: the outer edge of the oval. The data frame's own box. */ box: MBox; /** Everything drawn: the box and what lies beyond it (the jum…

What an athletics drawing is.

type AthleticsRace

type AthleticsRace = | "60m" | "60m-hurdles" | "100m" | "100m-hurdles" | "110m-hurdles" | "200m" | "400m" | "400m-hurdles" | "800m" | "1000m" | "1500m" | "mile" | "2000m" | "3000m" | "5000m" | "10000m" | "2000m-steeplechase" | "3000m-steeplechase" | "4x100m" | "4x400m";

The races a drawing can mark: where each starts, how it is staggered or curved, its hurdles or its exchange zones. A race is named by its distance, in kebab case.

type AthleticsRaceOptions

interface AthleticsRaceOptions extends ChartOptions { /** The race. The 400 m by default. */ race?: AthleticsRace; }

What laneDraw, raceResults and raceReplay take beyond a chart's options.

type AthleticsResult

interface AthleticsResult extends AthleticsEntry { /** The place, from 1. A runner who did not finish has none. */ place?: number; /** The time in seconds from the gun, or `null` for one who did not finish. */ time?: number | null; /** The reaction time in seconds (the time from the gun to the athlete's feet leaving the blocks), for a race with blocks. */ reaction?: number; /** The times, from the gun, at distances …

One athlete's result in a race. Times are in seconds from the gun.

type AthleticsRunner

interface AthleticsRunner extends AthleticsEntry { /** The time, in seconds from the gun, at which the runner finishes. */ time: number; /** The reaction time, in seconds; the runner stays on the blocks until then. 0.15 by default. */ reaction?: number; /** Times, from the gun, at distances along the way, which the runner's pace follows; without them the pace is even. */ splits?: readonly { distance: number; time: n…

One runner of a replay.

type AthleticsStyle

interface AthleticsStyle { /** The lanes of the oval: 4 to 9 outdoors (8 by default; the Manual says nine is the most there should be), 4 to 6 on the indoor track (6 by default). */ lanes?: number; /** The lanes of the straight that runs on beyond the oval's, 6 to 10 (the oval's number by default), as the straight of a 10-lane stadium is built (Manual 2.2.2). */ straightLanes?: number; /** The colour of the track: `…

What an athletics drawing takes beyond a surface: the number of lanes, the colour of the track, what is in the infield, which races and which field events are marked. Every part is optional; the standard 400 m track of World Athletics, with eight lanes, a red track, a soccer pitch inside it and every field event in its usual place, is drawn without any.

type AthleticsTrackLook

type AthleticsTrackLook = "red" | "blue" | "terracotta" | "green";

The colour of the track: Mondo red (the usual), Berlin's blue (the 2009 World Championships), the terracotta of a club track, or green. A colour of your own is colors.surface... or colors.track.

function breaklineStagger

breaklineStagger(o: Oval, n: number): number

The start of a race of distance metres run in lanes' own staggers, and the breakline of the 800 m: the stagger of lane n over lane 1's, from the involute of the breakline (Manual Tables 2.2.1.6a and 2.2.1.6b). The breakline is the involute of lane 1's second-bend circle through the end of the first bend; a lane's runner is equal to lane 1's when the lane's run to the breakline is one semicircle of lane 1's length.

function distanceOn

distanceOn(o: Oval, r: number, x: number, y: number): number

The distance along the running line of radius r from the finish line of the point of it nearest to (x, y): the inverse of pose.

function distanceRun

distanceRun(runner: AthleticsRunner, total: number, t: number): number

The distance a runner has run at a time, by the reaction time, the splits and the finish: straight lines between them, standing until the reaction.

function eventsOf

eventsOf(style: AthleticsStyle | undefined): AthleticsFieldEvent[]

The field events the options ask for: all of them, none, or a list.

type Facility

interface Facility { id: string; event: AthleticsFieldEvent; /** The throw's circle centre, the javelin arc's centre point, the high jump's bar centre, the pole vault's box, the jump's take-off board. */ x: number; y: number; /** The direction of the throw or the jump, in radians clockwise from the right as the eye sees it. */ heading: number; /** A box round all of it, for the ranges. */ box: { x0: number; y0: numb…

What a facility is, as data: where it stands, which way it throws or jumps, and the figures that say how big it is.

const FIELD_EVENT_IDS

FIELD_EVENT_IDS: readonly AthleticsFieldEvent[]

The ids of the field events.

const FIELD_EVENTS

FIELD_EVENTS: readonly FieldEventInfo[]

The field events, in the order a menu lists them: the jumps and then the throws.

type FieldAttempt

interface FieldAttempt { athlete?: string; /** The mark in metres: from the inside edge of the circle (or the javelin's arc) for a throw, from the take-off line for a jump. A foul has none. */ distance?: number | null; /** A throw's direction, in degrees from the sector's axis (positive to the right of the throw as the athlete faces it), or a jump's sideways place in metres from the runway's middle line. 0 by defaul…

An attempt in a jump or a throw.

function fieldEventById

fieldEventById: (id: unknown) => FieldEventInfo | undefined

A field event by its id, or undefined.

type FieldEventInfo

interface FieldEventInfo { id: AthleticsFieldEvent; name: { en: string; ja: string; fr: string }; }

A field event, as the package knows it.

function fieldMarks

fieldMarks(event: AthleticsFieldEvent, attempts: readonly FieldAttempt[], options?: FieldMarksOptions): Chart<FieldTotals> & { key: KeyItem[]; }

The attempts of a field event as marks: each valid throw in its sector at its distance and direction, each jump in its pit at its distance from the take-off line, the best of them picked out with a ring and its distance written beside it. A foul has no mark and is in the table. Put them on the stadium (athletics: { events: [event] }) or on the event alone (athletics: { layout: event }).

Example

import { fieldMarks } from "@johnmorrisdotca/kyogijo-stadiums/athletics";
const chart = fieldMarks("javelin-throw", [{ athlete: "A", distance: 84.2 }, { athlete: "A", distance: 88.1, across: -3 }, { athlete: "A", valid: false }]);
console.log(chart.totals.best?.distance, chart.totals.fouls);
// 88.1 1

type FieldMarksOptions

interface FieldMarksOptions extends ChartOptions { /** The facility of the event that the marks are on, by its id (`javelin-west`, `jump-0-west`): the event's first by default. */ facility?: string; /** Write each mark's distance beside it, not only the best's. Off by default. */ labels?: boolean; /** For the triple jump: the take-off line is the women's (11 m from the pit) and not the men's (13 m). */ women?: boole…

What fieldMarks takes.

type FieldTotals

interface FieldTotals { attempts: number; valid: number; fouls: number; best: { distance: number; athlete: string | undefined } | null; }

The numbers behind a field event's marks.

function firstStart

firstStart(o: Oval, distance: number, nominal?: number): number

The distance of lane 1's start from the finish line for a race of distance metres: the least start that is a whole number of laps before the finish line, round to the distance (Rule 14.3).

function formatTime

formatTime(seconds: number, places?: number): string

A time in seconds as it is written: 9.58, 1:40.91, 2:03:59.40.

const INDOOR_RACES

INDOOR_RACES: readonly AthleticsRace[]

The races the covered track marks: the 60 m and its hurdles on the infield straight, and the races of 200 m to 5000 m on the oval.

const JAVELIN_HALF

JAVELIN_HALF: number

The javelin sector's half angle: the arc's radius is 8 m and the runway 4 m wide, so the lines through the arc's ends make 28.96 degrees (Rule 32, landing sector; Figure 2.4.3.2).

const KYOGIJO_ATHLETICS_STYLE

KYOGIJO_ATHLETICS_STYLE: string

The rules of an athletics drawing's colours. A drawing that names its track (athletics.track) has its data-track, whose rules come after a look's so that the track wins.

const KYOGIJO_TRACK_STYLE

KYOGIJO_TRACK_STYLE: string

The rules of every named track, for a page that draws with style: "none".

function laneDraw

laneDraw(entries: readonly AthleticsEntry[], options?: AthleticsRaceOptions): Chart<LaneDrawTotals> & { key: KeyItem[]; }

The lane draw of a race: each athlete's name and flag on the lane of the start, behind the lane's number, for the close-up of the start (range: "start", the drawing's default). The positions are the start lines' as the Manual marks them: the stagger of the 200, 400 and 800 m and the 4 x 400 m, the straight line of the sprints, the curved line of a race run without lanes.

Example

import { laneDraw } from "@johnmorrisdotca/kyogijo-stadiums/athletics";
const chart = laneDraw([{ lane: 4, name: "M. Jacobs", country: "ITA" }, { lane: 5, name: "F. Kerley", country: "USA" }], { race: "100m" });
console.log(chart.totals.athletes, chart.title);
// 2 Lane draw, 100 m

type LaneDrawTotals

interface LaneDrawTotals { athletes: number; /** The first and the last lane drawn. */ firstLane: number; lastLane: number; }

The numbers behind a lane draw.

function laneEdge

laneEdge: (o: Oval, n: number) => number

The radius of the outer edge of lane n's line.

function laneRadius

laneRadius: (o: Oval, n: number) => number

The radius of lane n's running line (Rule 14.2, Manual 2.2.1.1): the kerb radius and 0.30 m for lane 1, and 0.20 m outside the line on the left for the others.

function lap

lap: (o: Oval, n: number) => number

The length of lane n's running line: one lap.

function markRace

markRace(ctx: RaceContext, race: RaceInfo): RaceMarks

The marks of one race on the oval or the straight.

type MBox

interface MBox { x0: number; y0: number; x1: number; y1: number; }

A box in metres.

type MMark

interface MMark { id: string; x: number; y: number; side: "top" | "bottom"; }

A part of the plan to label (landmarks): where it is, in metres, and the side of the drawing its label stands on.

type MRange

type MRange = MBox | { left: MBox; right: MBox };

A named part of the plan to crop to, in metres: one box, or one at each end (left and right as the stadium is drawn wide).

type Oval

interface Oval { /** The length of each straight, which is also the distance between the centres of the semicircles. */ straight: number; /** The radius of the semicircles at the kerb: the track's inside edge. */ radius: number; /** The width of a lane, the 50 mm line on its right included (Rule 14.4). */ lane: number; /** How many lanes the oval has. */ lanes: number; /** The width of the lines. */ line: number; /*…

An oval track: two straights of straight metres joined by two semicircles whose kerb radius is radius (Manual 2.2.1.1; Technical Rule 14.1).

function pose

pose(o: Oval, r: number, s: number): Pose

A point on the running line of radius r, s metres from the finish line. A distance beyond a lap goes round again.

type Pose

interface Pose { x: number; y: number; heading: number; }

A point on the track and the way the athlete is facing there (radians, clockwise from the right, as the eye sees it: 0 faces right, π/2 down).

const RACE_IDS

RACE_IDS: readonly AthleticsRace[]

The ids of the races.

function raceById

raceById: (id: unknown) => RaceInfo | undefined

A race by its id, or undefined.

type RaceInfo

interface RaceInfo { id: AthleticsRace; /** The distance in metres (the legs of a relay are in `legs`). */ distance: number; kind: StartKind; /** The legs of a relay, in metres. */ legs?: readonly number[]; /** The bends run in lanes (a stagger's), for a race that starts staggered. */ bends?: number; /** True for a race with a breakline, which the athletes may leave their lanes after. */ breakline?: boolean; /** Tru…

A race, as the package knows it.

type RaceMarks

interface RaceMarks { race: RaceInfo; /** The distance of lane 1's start line from the finish line along its running line: the least that is round whole laps to the distance of the race. */ s1: number; /** The start line of each lane (a staggered start's, or a straight's: `x` is then the line's). Empty for a curved start. */ starts: StartPlacement[]; /** The curved start line from the kerb out across the track. */ c…

One race's marks as data, for the description, the replay and the tests.

function raceReplay

raceReplay(runners: readonly AthleticsRunner[], options?: RaceReplayOptions): RaceReplay

A replay of a race: every runner, from the gun, on the curve of the lane (the stagger of the start as the Manual's table has it), in the pace of the reaction time, the splits and the finish time, to the line and on to a stop. A race that leaves its lanes (the 800 m after its breakline, the long races from the start) is drawn with the runners moving in to the inside. The positions are in the athletics frame (metres from the middle of the track), so the timeline is for the stadium drawn whole or close up. The relays are not replayed.

Example

import { raceReplay } from "@johnmorrisdotca/kyogijo-stadiums/athletics";
const replay = raceReplay([{ lane: 1, name: "A", time: 10.1 }, { lane: 2, name: "B", time: 10.3 }], { race: "100m" });
console.log(replay.timeline.frames.length > 40, replay.duration);
// true 10.3

type RaceReplay

interface RaceReplay { timeline: ReplayTimeline; /** The drawing's options: the stadium with the race marked. Pass them with the timeline to `drawFrame`, `createReplay` or `replayElement`. */ options: SurfaceOptions; /** A look for the frames: the runners drawn larger, no trails, the lane numbers on them. */ look: ReplayLook; title: string; summary: string; /** The time of the last runner to finish, in seconds. */ d…

What raceReplay gives back: the timeline, the drawing's options to lay it on, a look for it and a sentence.

type RaceReplayOptions

interface RaceReplayOptions extends AthleticsRaceOptions { /** Seconds between two frames. 0.2 by default. */ step?: number; /** How far past the finish line the runners go on, in metres. 8 by default. */ runOut?: number; }

What raceReplay takes.

function raceResults

raceResults(results: readonly AthleticsResult[], options?: AthleticsRaceOptions & { splits?: boolean; reactions?: boolean; }): Chart<ResultsTotals> & { key: KeyItem[]; }

The results of a race on the finish line's close-up: after the line, in each lane, a disc with the place (gold, silver and bronze for the first three), the athlete's name and time; the reaction time behind the start line and the splits along the lane as small marks with their times; and a table of all of it. Times are in seconds from the gun.

Example

import { raceResults } from "@johnmorrisdotca/kyogijo-stadiums/athletics";
const chart = raceResults([{ lane: 4, name: "M. Jacobs", place: 1, time: 9.8 }, { lane: 5, name: "F. Kerley", place: 2, time: 9.84 }], { race: "100m" });
console.log(chart.totals.winner?.name, chart.summary);
// M. Jacobs M. Jacobs won the 100 m in 9.80.

const RACES

RACES: readonly RaceInfo[]

The races, in the order a menu lists them. kind says how the start is made: straight (the sprints and the sprint hurdles, on the finish straight), stagger (the lanes' staggers of Table 2.2.1.6a: bends half-turns in lanes), curved (a curved start line, the waterfall, for a race run without lanes) and steeplechase (the water jump's lap). The breakline (Rule 17.5.1) is the 800 m's and the 4 x 400 m's; the group starts (Rule 17.5.2) are those of the races over 1000 m.

function racesOf

racesOf(style: AthleticsStyle | undefined, indoor?: boolean): RaceInfo[]

The races the options ask to be marked.

type ResultsTotals

interface ResultsTotals { athletes: number; finished: number; /** The winner's name and time, or null when nobody has a place. */ winner: { name: string; time: number } | null; /** The quickest reaction, in seconds. */ quickestReaction: number | null; }

The numbers behind results.

function scPose

scPose(o: Oval, variant: WaterJump, s: number): ScPose

A point of the steeplechase's running line.

const SECTOR_ANGLE

SECTOR_ANGLE: number

The angle of a throw's sector: 34.92 degrees (Rule 32, landing sector; Manual 2.4.1.4). Its lines are 12 m apart (the chord) 20 m from the circle's centre, 0.60 m more for each metre further: 2 asin(0.3).

const SECTOR_REACH

SECTOR_REACH: { readonly "shot-put": 25; readonly "discus-throw": 80; readonly "hammer-throw": 90; readonly "javelin-throw": 100; }

The sectors' lengths (Manual 2.1.1.3 and Figures 2.4.1.1, 2.4.2.1, 2.4.3.1, 2.4.4.1): the shot's 25 m, the discus's 80 m, the hammer's 90 m, the javelin's 100 m.

function stagger

stagger(o: Oval, n: number, bends: number): number

The staggered start of a lane: how far ahead of lane 1's start line it is, for a race run in lanes round bends bends, bends half-turns of π(r - r₁) each (Manual Table 2.2.1.6a).

function staggerOf

staggerOf(o: Oval, n: number, bends: number, breakline: boolean, fig: AthleticsFigures): number

The stagger of lane n for a race run in lanes round bends bends, with the breakline's correction where the race has one (Manual Tables 2.2.1.6a and 2.2.1.6b).

type StartPlacement

interface StartPlacement { lane: number; /** The distance of the start along the lane's running line from the finish line, forward. */ s: number; /** Where the lane's running line crosses the start line, and the way the athlete faces there. */ x: number; y: number; heading: number; /** How far ahead of lane 1's start it is, on the running line. */ stagger: number; }

Where a lane's start line stands.

const STEEPLECHASE_HURDLE

STEEPLECHASE_HURDLE: { readonly width: 3.94; readonly bar: 0.127; readonly inside: 0.3; readonly pit: 3.66; }

The steeplechase hurdle's figures (Rule 23.5 and 23.6; Figure 2.2.4.1c): at least 3.94 m wide and 0.127 m square at the top; the water jump's hurdle and pit are 3.66 m.

const STEEPLECHASE_INSIDE

STEEPLECHASE_INSIDE: { readonly lap: 396.0849; readonly b2: 27.3307; readonly b1: 13.41545; readonly radius: 16.2; readonly middle: 30.202; readonly centre: { readonly out: 13.863; readonly across: 15.101; }; readonly hurdles: readonly [21.997, 101.214, 180.431, 259.648, 338.865]; readonly start3000: 172.594; readonly start2000: 376.504; }

The figures of the diverted bend with the jump inside (Manual 2.2.4.1, comments on Figure 2.2.4.1a): the arcs of the oval's radius, the transitions and the straight between them.

const STEEPLECHASE_OUTSIDE

STEEPLECHASE_OUTSIDE: { readonly lap: 419.407; readonly straight: 9.86; readonly radius: 36.7; readonly hurdles: readonly [17.866, 101.747, 185.628, 269.509, 353.39]; readonly start3000: 355.256; readonly start2000: 97.035; }

The figures of the lap with the jump outside (Manual 2.2.4.1, Figure 2.2.4.1b and 2.2.4.1e).

const TRACK_COLOURS

TRACK_COLOURS: Readonly<Record<AthleticsTrackLook, { light: string; dark: string; }>>

The colours of each track look, light and dark.

const WA_SHORT_STAGGER_TABLE

WA_SHORT_STAGGER_TABLE: Readonly<Record<"two-bends" | "three-bends" | "one-bend", readonly number[]>>

The staggers of the lanes of the 200 m Standard Short Track (Marking Plan, 1 January 2026), in metres measured 0.20 m out from the lane line, for lanes 2 to 6. The first row serves the 200 m, 400 m, 800 m, 4 x 400 m and 4 x 800 m (two bends in lanes), the second the 4 x 200 m (three bends in lanes), the third the second leg of the 4 x 200 m (one bend).

const WA_SHORT_TRACK

WA_SHORT_TRACK: AthleticsFigures

The 200 m Standard Short Track (Marking Plan for the World Athletics Standard Short Track, 1 January 2026; Rules 41 to 44): the bends have a construction radius of 17.205 m at the kerb and the centres of the banked bends are 44.983 m apart; lanes are 1.000 m wide on the oval and 1.220 m on the infield straight, four to six of them (Rule 43: 0.90 to 1.22 m, a minimum of four lanes and a maximum of six); the bends are banked at 10.000 degrees. The plan's own transitions (the ascending and descending sections) are drawn here as one circular bend of the same radius, which is ours.

const WA_STAGGER_TABLE

WA_STAGGER_TABLE: Readonly<Record<"200m" | "400m" | "800m" | "4x400m", readonly number[]>>

The staggers of the lanes of the 400 m Standard Track as the Manual prints them (Table 2.2.1.6a), in metres on the running line, for lanes 2 to 8: 200 m, 400 m, 800 m and 4 x 400 m.

const WA_STANDARD_TRACK

WA_STANDARD_TRACK: AthleticsFigures

The 400 m Standard Track (Manual 2.2.1.1, Table 2.2.1.1): two semicircles of 36.50 m radius at the kerb joined by two straights of 84.39 m, lanes 1.22 m wide including the 50 mm line on the right (Rule 14.4), the running line 0.30 m outside the kerb in lane 1 and 0.20 m outside the line on the left in every other lane (Rule 14.2), eight lanes at least for the big competitions (Rule 14.5) and nine at the most (Manual 2.2.1.1). A straight of at least six lanes runs on beyond the oval's with a starting area of 3 m and a run-out of 17 m (Manual 2.2.2.1), and has no most (2.2.2.1). The safety zones are 1.00 m wide (2.2.1.5). The kerb is drawn 0.05 m wide, the narrowest the rules allow (Rule 14.1: 50 to 250 mm).

type WaterJump

type WaterJump = "inside" | "outside" | "none";

Where the steeplechase's water jump is: inside the second bend, outside it, or not drawn.

@johnmorrisdotca/kyogijo-stadiums/skating

disciplineOf distanceSkated DrawTotals ELEMENT_KINDS ELEMENT_NAMES ElementKind ElementTotals figureElements FigureElementsOptions FigureRinkFigures finishAt formatTime FURNITURE furnitureBoxes furnitureOf ISU_FIGURE_RINK ISU_LONG_TRACK ISU_LONG_TRACK_TABLE ISU_SHORT_TRACK JUDGES KYOGIJO_SKATING_STYLE Lane laneEdges laneRadius LONG_TRACK_RACE_IDS LONG_TRACK_RACES longOval LongOval LongTrackFigures LongTrackRace markRace MBox MMark MRange outerEdge pose Pose RACE_IDS raceById RaceInfo RaceMarks RACES racesOf SHORT_TRACK_RACE_IDS SHORT_TRACK_RACES shortOval ShortOval shortPose ShortTrackFigures ShortTrackRace SKATING_LEAGUES SkatingDiscipline SkatingElement SkatingEntry SkatingFigures SkatingFurniture skatingGrid SkatingInfield skatingLeague SkatingLeague SkatingPair skatingPairs skatingPlan SkatingPlan SkatingRace SkatingRaceOptions skatingReplay SkatingReplay SkatingReplayOptions SkatingResult skatingResults SkatingResultsTotals SkatingRunner SkatingStarter SkatingStyle startingPositions startOf StartPlacement startsOnOppositeStraight trackLines

function disciplineOf

disciplineOf: (o: Pick<SurfaceOptions, "sport" | "league">) => SkatingDiscipline

The discipline of a set of options.

function distanceSkated

distanceSkated(runner: SkatingRunner, total: number, t: number): number

The distance a skater has skated at a time, by the splits and the finish: straight lines between them, coasting for three seconds after the finish.

type DrawTotals

interface DrawTotals { skaters: number; /** Long track: the pairs; short track: 1. */ pairs: number; }

The numbers behind a draw.

const ELEMENT_KINDS

ELEMENT_KINDS: readonly ["jump", "spin", "step-sequence", "choreographic-sequence", "lift", "spiral", "twizzle", "death-spiral", "throw"]

The kinds of element of a figure skating programme a mark can stand for.

const ELEMENT_NAMES

ELEMENT_NAMES: Readonly<Record<"jump" | "spin" | "step-sequence" | "choreographic-sequence" | "lift" | "spiral" | "twizzle" | "death-spiral" | "throw", { en: string; ja: string; fr: string; }>>

The words of a kind of element.

type ElementKind

type ElementKind = (typeof ELEMENT_KINDS)[number];

type ElementTotals

interface ElementTotals { elements: number; /** The count of each kind. */ kinds: Readonly<Record<string, number>>; }

The numbers behind the elements.

function figureElements

figureElements(elements: readonly SkatingElement[], options?: FigureElementsOptions): Chart<ElementTotals> & { key: KeyItem[]; }

The elements of a programme as marks on the rink: a mark for each element where it starts, its shape for its kind, the track of a step sequence or a spiral as a line to where it ends, and the order and the name beside it. Put them on the rink (league: "isu-figure", the default for this chart).

Example

import { figureElements } from "@johnmorrisdotca/kyogijo-stadiums/skating";
const chart = figureElements([{ kind: "jump", name: "3Lz", x: -12, y: 4, order: 1 }, { kind: "spin", name: "CCoSp4", x: 6, y: -3, order: 2 }]);
console.log(chart.totals.elements, chart.totals.kinds.jump);
// 2 1

type FigureElementsOptions

interface FigureElementsOptions extends ChartOptions { /** Write the name beside each mark. On by default. */ labels?: boolean; }

What figureElements takes.

type FigureRinkFigures

interface FigureRinkFigures { kind: "figure"; /** The skating area: if possible 60 m by 30 m, not larger and not less than 56 m by 26 m (Rule 342.1). */ length: { min: number; max: number; drawn: number }; width: { min: number; max: number; drawn: number }; /** The radius of the corners, which the Rules do not give: ours. */ corner: number; /** The boards' thickness as drawn, and the depth of a judges' station and o…

The figures of a figure skating rink, in metres.

function finishAt

finishAt: (o: LongOval, line: "400m" | "1000m") => number

The coordinate of the finishing line on the lap 0's finishing straight, from the straight's beginning: the 400 m mark is 100 m on (Rule 227.2 and the layout plan: h = f - 100).

function formatTime

formatTime(seconds: number, places?: number): string

A time in seconds as it is written: 34.32, 1:40.91, 6:01.03.

const FURNITURE

FURNITURE: readonly SkatingFurniture[]

The furniture of a figure skating rink, in painter's order.

function furnitureBoxes

furnitureBoxes(r: FigureRink, fig?: FigureRinkFigures): Record<SkatingFurniture, MBox[]>

Where the furniture stands outside the boards of the rink on its long side at the bottom (and the kiss and cry at the end), as boxes in metres.

function furnitureOf

furnitureOf(style: SkatingStyle | undefined): SkatingFurniture[]

The furniture the options ask for.

const ISU_FIGURE_RINK

ISU_FIGURE_RINK: FigureRinkFigures

The figure skating rink (Rule 342.1): 60 m by 30 m if possible, 56 m by 26 m at the least. The corners and the boards are ours.

const ISU_LONG_TRACK

ISU_LONG_TRACK: LongTrackFigures

The standard 400 m speed skating track (Rules 203 and 227 and "Measurements of standard 400m speed skating tracks"): two lanes, the inner 4 m wide and the outer at least 4 m, the inner curve of radius 25 m to 26 m, the track measured 0.5 m outside the inner edge of each lane, a lap of 400 m made of two straights, an inner curve, an outer curve and the crossing, since a skater changes lanes once a lap.

const ISU_LONG_TRACK_TABLE

ISU_LONG_TRACK_TABLE: Readonly<Record<"25m" | "25.5m" | "26m", { radius: number; straight: number; crossing: number; }>>

The measurements of the three options of the standard 400 m track (the table "Measurements of standard 400m speed skating tracks"): the half of the mean axis (the straight, f) and the lengths a skater skates.

const ISU_SHORT_TRACK

ISU_SHORT_TRACK: ShortTrackFigures

The short track oval (Rule 280 and the diagram "Track numbers"): S = 28.85 m, C = R8.50 m, a lap of 111.12 m on a rink of 60 by 30 m at the least.

const JUDGES

JUDGES: 9

The seats of a panel of judges, side by side at the rinkboard: nine, as at the Olympic Winter Games (ours: the Rules fix the number elsewhere), 1 m apart.

const KYOGIJO_SKATING_STYLE

KYOGIJO_SKATING_STYLE: string

The rules of a skating drawing's colours.

type Lane

type Lane = 0 | 1;

The lane of a skater on the finishing straight: the inner or the outer (the one on the left, nearer the infield).

function laneEdges

laneEdges: (o: LongOval, warmUp: number) => { warmUp: number; inner: number; between: number; outer: number; }

The radius of the edges of the lanes, from the inner edge of the warm-up lane out to the outer edge of the outer lane: the warm-up lane's inner edge, the inner edge, the line between the lanes, the outer edge.

function laneRadius

laneRadius: (o: LongOval, lane: Lane) => number

The radius of the line a lane is measured on: 0 inner, 1 outer.

const LONG_TRACK_RACE_IDS

LONG_TRACK_RACE_IDS: readonly LongTrackRace[]

const LONG_TRACK_RACES

LONG_TRACK_RACES: readonly RaceInfo[]

The races of the long track, in the order a menu lists them. The mass start is 16 laps (6400 m); the team pursuit and the team sprint are marked by their lines in the middle of the straights.

function longOval

longOval(a?: number, w2?: number, fig?: LongTrackFigures): LongOval

The track for an inner radius a and an outer lane w2 wide: the straight is the one that makes a lap 400 m, f + sqrt(f^2 + w1^2) + pi (d + e) = lap, where f is the straight skated twice a lap (once on the finishing straight and once crossing). For a = 25 m, 25.5 m and 26 m with lanes of 4 m this is the table's 113.57 m, 112.00 m and 110.43 m.

type LongOval

interface LongOval { /** The radius of the inner edge of the inner lane (a in the table). */ a: number; /** The widths of the inner lane and of the outer lane. */ w1: number; w2: number; /** The radii of the lines each lane is measured on (d and e in the table): 0.5 m outside the inner edge of the lane. */ d: number; e: number; /** The length of each straight, from the end of one curve to the beginning of the next (…

The shape of a long track, from the radius of the inner edge, the width of the outer lane and the lap.

type LongTrackFigures

interface LongTrackFigures { kind: "long-track"; /** The radius of the inner edge of the inner lane: 25 m to 26 m (Rule 203.1); 25 m drawn. */ innerRadius: { min: number; max: number; drawn: number }; /** The width of the inner competition lane (Rule 203.3). */ innerLane: number; /** The width of the outer competition lane: at least 4 m (Rule 203.3), 4 m drawn. */ outerLane: { min: number; max: number; drawn: number…

The figures of the standard 400 m speed skating track, in metres.

type LongTrackRace

type LongTrackRace = "500m" | "1000m" | "1500m" | "3000m" | "5000m" | "10000m" | "mass-start" | "team-pursuit" | "team-sprint";

The races of the long track whose lines are marked: the distances, the mass start and the two team races. A race is named by its distance, in kebab case.

function markRace

markRace(o: LongOval, race: LongTrackRace): RaceMarks

The marks of a race on the oval.

type MBox

interface MBox { x0: number; y0: number; x1: number; y1: number; }

A box in metres.

type MMark

interface MMark { id: string; x: number; y: number; side: "top" | "bottom"; }

A part of the plan to label (landmarks): where it is, in metres, and the side of the drawing its label stands on.

type MRange

type MRange = MBox | { left: MBox; right: MBox };

A named part of the plan to crop to, in metres: one box, or one at each end (left and right as the rink is drawn wide).

function outerEdge

outerEdge: (o: LongOval) => number

The radius of the outer edge of the outer lane.

function pose

pose(o: LongOval, lane: Lane, v: number, noChange?: boolean): Pose

The pose v metres along the skater's own way, v measured from the beginning of the finishing straight (its left end, x = -f/2) of the lap the skater starts in, the lap 0. lane is the skater's lane there; it alternates every lap (Rule 254.5a). In a lap the skater skates the finishing straight and the first curve in the lap's lane, the crossing from it to the other lane, and the second curve in the other. With noChange the crossing of the lap 0 keeps the lane, as the first straight of the 1000 m and the 1500 m does (Rule 254.5b): the lap 0 has one lane, and the laps alternate on.

type Pose

interface Pose { x: number; y: number; heading: number; part: "finishing-straight" | "first-curve" | "crossing" | "second-curve"; /** The lap's number, from the lap the coordinate zero begins. */ lap: number; /** 0 on the inner lane's line, 1 on the outer's, between on the crossing. */ across: number; }

Where a skater is: a point of the track, the heading (0 is to the right, growing clockwise as the eye sees it), the part of the lap and how far across the lanes (0 on the inner line, 1 on the outer).

const RACE_IDS

RACE_IDS: readonly SkatingRace[]

function raceById

raceById: (id: unknown) => RaceInfo | undefined

A race by its id, or undefined.

type RaceInfo

interface RaceInfo { id: SkatingRace; /** The distance in metres; the mass start's is its laps (16 laps of 400 m). */ distance: number; /** The words of its name, in English, Japanese and French. */ name: { en: string; ja: string; fr: string }; }

A race, as the package knows it.

type RaceMarks

interface RaceMarks { race: LongTrackRace; /** The finishing line: the 400 m mark at the end of the finishing straight or the 1000 m mark in the middle of it. */ finish: "400m" | "1000m"; /** True when the first straight keeps its lane (the 1000 m and the 1500 m, Rule 254.5b). */ noChange: boolean; /** The starting lines: one for each lane, or one across both. */ starts: StartPlacement[]; }

What a race marks: the lines where it starts, the finishing line it ends at, and how its first straight is skated.

const RACES

RACES: readonly RaceInfo[]

Every race of both tracks, once by id.

function racesOf

racesOf(style: SkatingStyle | undefined, discipline: SkatingDiscipline): SkatingRace[]

The races the options ask to be marked, for a discipline.

const SHORT_TRACK_RACE_IDS

SHORT_TRACK_RACE_IDS: readonly ShortTrackRace[]

const SHORT_TRACK_RACES

SHORT_TRACK_RACES: readonly RaceInfo[]

The races of the short track: the individual 500, 1000 and 1500 m, the 3000 m of the World Cup, and the relays: the women's 3000 m, the men's 5000 m and the mixed 2000 m (Rule 281.1a, which gives the distances of the ISU Championships; the diagram of the tracks gives the laps of each distance).

function shortOval

shortOval(length?: number, width?: number, fig?: ShortTrackFigures): ShortOval

The oval for a rink.

type ShortOval

interface ShortOval { straight: number; radius: number; /** The radius of the blocks, 0.5 m inside the line of the track. */ blocks: number; lap: number; /** The rink: its length and width. */ length: number; width: number; }

The oval of the standard track.

function shortPose

shortPose(o: ShortOval, v: number, out?: number): { x: number; y: number; heading: number; }

The pose of a skater v metres along a lap that begins at the left end of the finishing straight (the bottom one), out metres outside the line of the track: the skaters go counter-clockwise.

type ShortTrackFigures

interface ShortTrackFigures { kind: "short-track"; /** The length of a lap (Rule 280.1a and the diagram): 2 x S + 2 x C x pi. */ lap: number; /** The distance between the centres of the curves, S, and the radius of the track's line, C (the diagram). */ straight: number; radius: number; /** The radius of the blocks: the line of the track is 0.5 m outside them (R8.00 in the diagram). */ blocks: number; /** The blocks …

The figures of the short track speed skating oval, in metres.

type ShortTrackRace

type ShortTrackRace = "500m" | "1000m" | "1500m" | "3000m" | "3000m-relay" | "5000m-relay" | "2000m-mixed-relay";

The races of the short track whose lines are marked.

const SKATING_LEAGUES

SKATING_LEAGUES: readonly SkatingLeague[]

The skating leagues: the long track (the sport's own), the short track and the figure skating rink.

type SkatingDiscipline

type SkatingDiscipline = "long-track" | "short-track" | "figure";

The disciplines a drawing can be of: the standard 400 m speed skating track, the short track oval or the figure skating rink. league says it too: isu-long-track, isu-short-track, isu-figure.

type SkatingElement

interface SkatingElement { /** The kind: a jump, a spin, a step sequence, a choreographic sequence, a lift, a spiral, a twizzle, a death spiral or a throw. */ kind: ElementKind; /** The name for the table and the label: `3Lz`, `CCoSp4`, `StSq3`. */ name?: string; /** Where it starts, in metres from the middle of the rink: x along it (positive to the right as the rink lies wide), y across (positive towards the judges…

An element of a figure skating programme as it lies on the ice.

type SkatingEntry

interface SkatingEntry { name: string; /** The country's code or name, for the table. */ country?: string; /** The flag: an SVG document as text, or an `https:` or relative address, as a team's `logo`. */ flag?: string; bib?: string | number; }

A skater in a race or on the ice.

type SkatingFigures

type SkatingFigures = LongTrackFigures | ShortTrackFigures | FigureRinkFigures;

The figures of a skating league.

type SkatingFurniture

type SkatingFurniture = "judges" | "referee" | "technical-panel" | "kiss-and-cry";

The things that stand at the rink of a figure skating competition: the judges and the referee at the rinkboard, the technical panel, and the kiss and cry.

function skatingGrid

skatingGrid(starters: readonly SkatingStarter[], options?: SkatingRaceOptions): Chart<DrawTotals> & { key: KeyItem[]; }

The starting positions of a short track race on the start's close-up: the five dots of the 500 m and the 1000 m (every 1.3 m from 50 cm out from the inner side, Rule 280.2c), each with its starter's name and flag. The 500 m and the 1500 m start on the straight opposite the finishing line; the others at the finishing line.

Example

import { skatingGrid } from "@johnmorrisdotca/kyogijo-stadiums/skating";
const chart = skatingGrid([{ position: 1, name: "A. Skater" }, { position: 2, name: "B. Skater" }], { race: "500m", surface: { league: "isu-short-track" } });
console.log(chart.totals.skaters);
// 2

type SkatingInfield

type SkatingInfield = "floor" | "ice";

What lies inside the long track: the ice of the warm-up lane carried on to the infield, or the floor of the arena.

function skatingLeague

skatingLeague(o: Pick<SurfaceOptions, "sport" | "league">): SkatingLeague

The league a set of options asks for: the long track (the sport's own), the short track or the figure skating rink.

type SkatingLeague

type SkatingLeague = LeagueBase<"skating", SkatingFigures>;

A league with a skating surface of its own.

type SkatingPair

interface SkatingPair { /** The pair's number in the race, from 1. */ pair: number; inner?: SkatingEntry; outer?: SkatingEntry; }

A pair of a long track race: two skaters, one in each lane (Rule 254.2).

function skatingPairs

skatingPairs(pairs: readonly SkatingPair[], options?: SkatingRaceOptions & { pair?: number; }): Chart<DrawTotals> & { key: KeyItem[]; }

The pairs of a long track race on the start's close-up: the pair pair (the first by default) with each skater's name and flag behind the starting line of his lane, and an armband's colour (white for the inner lane, red for the outer: Rule 253.3.1); a table lists every pair. The starting lines are the ISU's: the 500 m on the finishing straight, the 1000 m, 1500 m, 3000 m and 5000 m on the crossing straight, where each lane's line stands a different distance from the finish.

Example

import { skatingPairs } from "@johnmorrisdotca/kyogijo-stadiums/skating";
const chart = skatingPairs([{ pair: 1, inner: { name: "A. Skater" }, outer: { name: "B. Skater" } }], { race: "1000m" });
console.log(chart.totals, chart.title);
// { skaters: 2, pairs: 1 } Pairs, 1000 m

function skatingPlan

skatingPlan(o: Pick<SurfaceOptions, "sport" | "league" | "skating" | "range" | "detail">): SkatingPlan

The skating plan the options ask for.

type SkatingPlan

interface SkatingPlan { sport: "skating"; league: string; discipline: SkatingDiscipline; figures: SkatingFigures; /** The oval of the long track, or `undefined`. */ oval: LongOval | undefined; /** The short track's oval and rink, or `undefined`. */ short: ShortOval | undefined; /** The figure skating rink, or `undefined`. */ rink: FigureRink | undefined; /** The playing surface: the ice out to the padding (the long …

What a skating drawing is.

type SkatingRace

type SkatingRace = LongTrackRace | ShortTrackRace;

Every race a drawing can mark.

type SkatingRaceOptions

interface SkatingRaceOptions extends ChartOptions { /** The race: the 500 m by default. */ race?: SkatingRace; }

What the race charts take beyond a chart's options.

function skatingReplay

skatingReplay(runners: readonly SkatingRunner[], options?: SkatingReplayOptions): SkatingReplay

A replay of a race: every skater, from the gun, on his own way round the track (the long track's lane changed once a lap, on the crossing straight, and the first straight of the 1000 m and the 1500 m kept in the lane; the short track's oval of 111.12 m, the starters coming in from their positions), at the pace of the splits and the finish time. The positions are in the skating frame (metres from the middle of the rink). The team races, the mass start and the relays are not replayed.

Example

import { skatingReplay } from "@johnmorrisdotca/kyogijo-stadiums/skating";
const replay = skatingReplay([{ name: "A", lane: "inner", time: 34.4 }, { name: "B", lane: "outer", time: 34.6 }], { race: "500m" });
console.log(replay.timeline.frames.length > 40, replay.duration);
// true 34.6

type SkatingReplay

interface SkatingReplay { timeline: ReplayTimeline; /** The drawing's options: the track with the race marked. */ options: SurfaceOptions; /** A look for the frames: markers sized for the track, no trails, the lane or the position on them. */ look: ReplayLook; title: string; summary: string; duration: number; }

What skatingReplay gives back.

type SkatingReplayOptions

interface SkatingReplayOptions extends SkatingRaceOptions { /** Seconds between two frames. 0.25 by default. */ step?: number; }

What skatingReplay takes.

type SkatingResult

interface SkatingResult extends SkatingEntry { /** The place, from 1. A skater who did not finish has none. */ place?: number; /** The pair, on the long track. */ pair?: number; /** The lane the skater started in, on the long track: `inner` or `outer`. */ lane?: "inner" | "outer"; /** The time in seconds from the gun, or `null` for one who did not finish. */ time?: number | null; /** The times, from the gun, at dist…

One skater's result in a race. Times are in seconds from the gun.

function skatingResults

skatingResults(results: readonly SkatingResult[], options?: SkatingRaceOptions & { splits?: boolean; rows?: number; }): Chart<SkatingResultsTotals> & { key: KeyItem[]; }

The results of a race as a leaderboard on the ice inside the track: for each skater with a place, a disc with the place (gold, silver and bronze for the first three), the name and the time; the splits as small marks along the skater's lane on the long track; and a table with the pair, the lane, the time, the splits and the lap times. Times are in seconds from the gun.

Example

import { skatingResults } from "@johnmorrisdotca/kyogijo-stadiums/skating";
const chart = skatingResults([{ name: "A", place: 1, time: 34.3 }, { name: "B", place: 2, time: 34.5 }], { race: "500m" });
console.log(chart.totals.winner?.name, chart.summary);
// A A won the 500 m in 34.30.

type SkatingResultsTotals

interface SkatingResultsTotals { skaters: number; finished: number; winner: { name: string; time: number } | null; }

The numbers behind results.

type SkatingRunner

interface SkatingRunner extends SkatingEntry { /** The time, in seconds from the gun, at which the skater finishes. */ time: number; /** Long track: the lane the skater starts in. `inner` by default. */ lane?: "inner" | "outer"; /** Short track: the starting position, from 1 on the inner side. 1 by default. */ position?: number; /** Times, from the gun, at distances along the way, which the skater's pace follows; wi…

One skater of a replay.

type SkatingStarter

interface SkatingStarter extends SkatingEntry { position: number; }

A starter of a short track race and the starting position he has on the line (Rule 280.2c: five positions of the 500 m and the 1000 m, from 1 on the inner side).

type SkatingStyle

interface SkatingStyle { /** Long track: the radius of the inner edge of the inner lane, 25 m to 26 m (25 m by default). The straight follows, so that a lap is 400 m. */ innerRadius?: number; /** Long track: the width of the outer lane, 4 m or more (4 m by default). */ laneWidth?: number; /** Long track: the warm-up lane inside the competition lanes. On by default. */ warmUp?: boolean; /** Long track: what lies insi…

What a skating drawing takes beyond a surface. Every part is optional: the standard 400 m track with an inner radius of 25 m, two lanes of 4 m, the warm-up lane, the blocks and the padding is drawn without any, and so are the short track's standard oval with its five tracks and the figure skating rink of 60 m by 30 m.

function startingPositions

startingPositions(o: ShortOval, x: number, side: 1 | -1, fig?: ShortTrackFigures): { x: number; y: number; }[]

The starting positions of the 500 m and the 1000 m: five dots every 1.3 m, from 50 cm out from the inner side of the track (Rule 280.2c), on the line at x and the side side (-1 the top, 1 the bottom).

function startOf

startOf(o: LongOval, lane: Lane, distance: number, finish: number, noChange?: boolean): { v0: number; pose: Pose; }

Where a skater in lane lane starts a race of distance metres that finishes at finish metres along the finishing straight (finishAt): back from the finishing line along the skater's own way. noChange says the race's first straight keeps its lane (the 1000 m and the 1500 m, Rule 254.5b). Returns the skater's coordinate v0 at the start, inside the lap it starts in, and the pose there; a skater who has skated s metres is at pose(o, lane, v0 + s, noChange).

type StartPlacement

interface StartPlacement { lane: Lane | "both"; /** The coordinate along the skater's own way, from the beginning of the finishing straight of the lap the start is in. */ v0: number; x: number; y: number; heading: number; }

Where a skater starts: the lane (or both for a line across both lanes) and the point of the start.

function startsOnOppositeStraight

startsOnOppositeStraight: (race: ShortTrackRace) => boolean

Where each race starts: the half-lap races (500 m and 1500 m) on the starting lines of the straight opposite the finishing line, the others on the finishing line (the diagram's laps: 4 1/2, 9, 13 1/2, 18, 27 and 45).

function trackLines

trackLines(tracks: 5 | 7, fig?: ShortTrackFigures): number[]

The x of the starting line of each of the tracks, from the one at the left: a track moved shift along the straight starts 2 x shift on from the standard track's line (the diagram).

@johnmorrisdotca/kyogijo-stadiums/budo

AJJF_CONTEST BUDO_LEAGUES BUDO_LOOKS BUDO_SPORTS BudoFigures BUDOKAN_ARENA BudoKind budoLeague BudoLeague BudoLook budoPlan BudoPlan BudoScore budoScoreText BudoStyle budoView BudoView checker Compass dojo Dojo DOJO_LEAGUES DOJO_SIZES DOJO_STANDARDS DojoColours DojoEra DojoFigures dojoGallery DojoGallery DojoLeague DojoPattern dojoRoom DojoRoom DojoSize DojoSpec DojoWindow drawDojo ENTEKI Entrance FIK_SHIAI_JO floorOf fourCorners Hall HallBasis HALLS HallSurface IJF_CONTEST IJF_KATA isBudoSport JSA_DOHYO JUDO_LEAGUES JudoFigures JudoLeague KARATE_LEAGUES KarateFigures KarateLeague KENDO_LEAGUES KendoFigures KendoLeague Kimarite KIMARITE KIMARITE_COUNT KIMARITE_OTHERS KimariteGroup kimariteName kimariteOf kindOf KINTEKI KYOGIJO_BUDO_STYLE KYUDO_LEAGUES KyudoFigures KyudoLeague lay lookNames Mat matCount MatSize MBox MMark MRange Piece PieceKind ROOM_HEIGHTS RoomBalcony RoomBasis RoomOpening RoomWall shapeOfJo Side4 SUMO_LEAGUES SumoFigures SumoLeague Tatami TATAMI tint

const AJJF_CONTEST

AJJF_CONTEST: JudoFigures

The All Japan Judo Federation's: the tatami of 182 cm by 91 cm, the red tatami of the danger zone, and the red and white tape 4 m (2 ken) apart (Tournament Management Guidebook 2023, 2-1, 2-3, 2-4).

const BUDO_LEAGUES

BUDO_LEAGUES: readonly BudoLeague[]

Every budō league, once each.

const BUDO_LOOKS

BUDO_LOOKS: Readonly<Record<BudoKind, Readonly<Record<string, Pair>>>>

The looks of each sport: the names, the default first, and the colours.

const BUDO_SPORTS

BUDO_SPORTS: readonly BudoKind[]

The sports of the budō family.

type BudoFigures

type BudoFigures = JudoFigures | KarateFigures | KendoFigures | SumoFigures | KyudoFigures | DojoFigures;

The figures of a budō league.

const BUDOKAN_ARENA

BUDOKAN_ARENA: 27.5

The apothem of the Nippon Budokan's arena, an octagon 55 m across the flats (the Budokan's leaflet).

type BudoKind

type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";

The sports of the budō family.

function budoLeague

budoLeague(o: Pick<SurfaceOptions, "sport" | "league" | "budo" | "dojo">): BudoLeague

The league a set of options asks for: a rulebook's, else the sport's own.

type BudoLeague

type BudoLeague = JudoLeague | KarateLeague | KendoLeague | SumoLeague | KyudoLeague | DojoLeague;

A league with a budō surface of its own.

type BudoLook

interface BudoLook { /** The first tone of the mats (the contest area, the mats of a dojo), the second (the safety area, the other mats of a checker floor) and the third (a danger zone, a border: the sheet's `--ky-red`). */ surface: string; alt: string; red: string; /** The ground round the surface, the free zone round the mats, and the floor of the hall: wood, the boards of a kendo floor, the walls. */ apron: strin…

What a look sets: the properties of the stylesheet its sport's surface is painted with.

function budoPlan

budoPlan(o: Pick<SurfaceOptions, "sport" | "league" | "budo" | "dojo" | "range" | "detail">): BudoPlan

The budō plan the options ask for.

type BudoPlan

interface BudoPlan { sport: BudoKind; league: string; figures: BudoFigures; /** The look (the colours of the mats and the floor). */ look: string; /** The mats are drawn with the weave of rice straw. */ texture: boolean; /** The playing surface: the mats of a competition area, the shiai-jo's floor, the dohyō's clay, the whole range of a kyūdōjō, a dojo's floor. The data frame's own box. */ box: MBox; /** Everything …

What a budō drawing is.

type BudoScore

interface BudoScore { /** Judo, karate: the number of ippon (judo: 1 ends the contest; the IJF's scoreboard writes 100). */ ippon?: number; /** Judo (10 on the scoreboard), karate (2 points). Two waza-ari are ippon in judo. */ wazaari?: number; /** Judo (1 on the scoreboard), karate (1 point). */ yuko?: number; /** Judo: the shido, three of which are hansoku-make. Karate: the warnings (chui), up to three. */ shido?:…

What each team has scored, as the sport counts it. Every part is optional; what is not given is left out of the board.

function budoScoreText

budoScoreText(sport: Sport, team: Pick<Team, "budo" | "score">): string | undefined

The total of a side as its sport counts it, when the side gave no score of its own. Judo's scoreboard writes ippon (and two waza-ari, waza-ari-awasete-ippon) as 100, a waza-ari as 10 and a yuko as 1 (IJF SOR 2.5.5.1 and the scoreboard's notes); karate's points are yuko 1, waza-ari 2 and ippon 3 (WKF Kumite 8.6); kendo's is the number of its points (the techniques that are not hansoku).

type BudoStyle

interface BudoStyle { /** A named look: the colours of the mats and of the floor. Judo: `yellow-red` (the IJF's own, the default), `yellow-blue`, `blue-grey`, `green-beige` (the traditional tatami), `green-red` (green mats with a red danger zone) and `black-red`. Karate: `blue-red` (the default), `blue-black`, `green-beige`. Kendo: `wood` (the default), `blond`. Sumo: `clay`. Kyūdō: `wood`. Dojo: `green-beige` (the …

What a budō drawing takes beyond a surface. Every part is optional: a judo contest area is the IJF's of 10 m with a safety area of 4 m, a karate area the WKF's kumite square, a kendo shiai-jo of 10 m, a dohyō of the Japan Sumo Association and a kyūdōjō of 28 m with five shooters, drawn without any.

function budoView

budoView(score: BudoScore | undefined): BudoView | undefined

Read a team's budo.

type BudoView

interface BudoView { ippon?: number; wazaari?: number; yuko?: number; shido?: number; senshu?: boolean; techniques?: string[]; kimarite?: string; }

The budō score of a team, cleaned: each count a whole number held to what the sport allows.

function checker

checker(pieces: readonly Piece[], rect: MBox, mat: MatSize): Piece[]

The mats of a checker floor: the tone alternates from one mat to the next, as the squares of a chessboard do.

type Compass

type Compass = "north" | "south" | "east" | "west";

The sides of a hall, as a plan has them: north is the top of the drawing. top, bottom, left and right say the same.

function dojo

dojo(spec?: DojoSpec): Dojo

A dojo (道場): the floor in mats, laid as real tatami units, with the walls, doors, windows, shrine and kamiza. mats is the floor: a number is its area in jō (8, 12, 20, 50, 100 and 200 give the usual shapes), [across, along] the whole mats across and along, { width, length, unit } a size in metres, feet, mats or jō; pattern is how it is laid (traditional: no four corners meet; competition, checker, border).

Example

import { dojo } from "@johnmorrisdotca/kyogijo-stadiums/budo";
const d = dojo({ jo: 20 });
console.log(d.jo, d.width.toFixed(2), d.length.toFixed(2), d.counts.full, d.counts.half, d.pattern);
// 20 7.28 4.55 18 4 traditional

type Dojo

interface Dojo { /** The spec it was made from. */ spec: DojoSpec; /** The mat standard: a league of the `dojo` sport (`chukyoma`, `kyoma`, `edoma`, `danchima`, `judo-2x1`) and the mat's size in metres. */ standard: string; mat: { length: number; width: number }; /** The floor in metres: its width (x, the longer side) and its length (y). */ width: number; length: number; /** The floor's area in jō: the area over the…

A dojo made from a spec: the floor as data.

const DOJO_LEAGUES

DOJO_LEAGUES: readonly DojoLeague[]

const DOJO_SIZES

DOJO_SIZES: Readonly<Record<number, readonly [number, number]>>

The usual sizes of a room counted in jō, in the mats' widths across and along: 8 jō is 4 by 4 widths (3.64 m square of Chūkyōma mats), 20 jō 5 by 8.

const DOJO_STANDARDS

DOJO_STANDARDS: readonly string[]

The mat standards a dojo can be laid with.

type DojoColours

interface DojoColours { /** The mats. */ mat?: string; /** The second tone: the other mats of a `checker` floor, the worn side of a mat, the border of a `border` floor. */ alt?: string; /** The border of a `border` floor (a danger zone). */ border?: string; /** The heri (畳縁), the cloth edge along a traditional mat's long sides. */ heri?: string; /** The wood of the floor outside the mats, and of the walls' base. */ …

The colours a dojo may be given, each a CSS colour.

type DojoEra

interface DojoEra { /** The era's name, as `era` asks for it (`earlier`, `current`). */ name: string; nameJa?: string; /** The first and the last year it lasted, `null` where no source gives the year (an era with no years is picked by name, or as the last). */ from?: number | null; to?: number | null; look?: string; colours?: DojoColours; pattern?: DojoPattern; areas?: number | { across: number; down: number }; area…

One look of a dojo's floor in its history.

type DojoFigures

interface DojoFigures { kind: "dojo"; /** One mat, a jō (畳), twice as long as it is wide. */ mat: Mat; /** Where the standard is used. */ region: { en: string; ja: string }; }

The figures of a dojo's mat: one tatami of a region.

function dojoGallery

dojoGallery(o: Pick<SurfaceOptions, "sport" | "league" | "budo" | "dojo" | "range">): DojoGallery | undefined

The gallery a dojo's crowd sits in, from the same balcony data as dojoRoom(), so that the 2D stands and a 3D view agree: a thin band of 4 rows (ours) close to the walls, just beyond the wood surround. A dojo that asks for no balcony still has the default one when it has a crowd; balcony: false asks for none, and the sport's own stands are drawn. Not a dojo: nothing.

type DojoGallery

interface DojoGallery { offset: number; depth: number; rows: number; height: [number, number]; }

The gallery of a dojo for the crowd: where its first row is from the mats, its depth and the heights of its first and last rows, in metres, from the room's balcony data.

type DojoLeague

type DojoLeague = LeagueBase<"dojo", DojoFigures>;

type DojoPattern

type DojoPattern = "traditional" | "competition" | "checker" | "border";

The ways a dojo's mats are laid.

function dojoRoom

dojoRoom(o: Pick<SurfaceOptions, "sport" | "league" | "budo" | "dojo" | "range">): DojoRoom | undefined

The room of a budō surface as data: what a 3D view raises. A dojo has its walls (3 m, ours), wood surround, dais, banner, balconies, doors, windows and shrine as its spec asks for them; the other surfaces have what they have (the mats' thickness, the dohyō's platform at its height, the Budokan's octagon). Heights that no source gives are ours and are listed in notes.

Example

import { dojoRoom } from "@johnmorrisdotca/kyogijo-stadiums/budo";
const room = dojoRoom({ sport: "dojo", dojo: { jo: 20, dais: true, banner: true } });
console.log(room?.walls.length, room?.platforms[0]?.height, room?.banners.length);
// 4 0.3 1

type DojoRoom

interface DojoRoom { sport: string; unit: "m"; /** The surface's box: the mats of a competition area, the floor of a dojo, the dohyō's platform, the range of a kyūdōjō. */ floor: MBox; /** The mats: `thickness` is the height of a mat's top above the floor under it, and `edge` how its edge is shaped (a competition mat is covered in vinyl and its edge is rounded), where a competition area's mat stands up at the safety…

The room round a budō surface.

type DojoSize

type DojoSize = number | readonly [number, number] | { width?: number; length?: number; unit?: "m" | "ft" | "mat" | "jo"; jo?: number };

The size of a dojo's floor: the mats across and along, or a size with a unit, or the area in jō.

type DojoSpec

interface DojoSpec { /** * The floor. A number is its area in jō (畳): 8, 12, 20, 50, 100 or 200 give the usual shapes. `[across, along]` is the number of whole mats across the floor and along it * (`[5, 4]` is 20 jō). `{ width, length, unit }` is a size in metres (the default), feet, `mat` (as the array) or `jo`. 20 jō by default. */ mats?: DojoSize; /** The area in jō, as `mats: 20` says it. */ jo?: number; /** `tr…

A dojo (道場): the floor in mats, laid out as real tatami units, and what stands round it. Every part is optional: 20 jō of traditional mats with the kamiza at the north, no walls. dojo() makes its plan, and drawKyogijo({ sport: "dojo", dojo }) draws it.

type DojoWindow

interface DojoWindow { wall: Side4; at?: number; width?: number; }

A window of a dojo: the wall, where along it (0 to 1, the middle by default) and how wide (1.8 m by default).

function drawDojo

drawDojo(spec?: DojoSpec, more?: Partial<SurfaceOptions>): string

Draw a dojo: drawKyogijo with the spec as the options' dojo, and any other option the caller gives.

const ENTEKI

ENTEKI: KyudoFigures

The kyūdōjō for enteki (遠的), 60 m, with the target of 1 m.

type Entrance

interface Entrance { wall: Side4; at?: number; width?: number; }

A door of a dojo: the wall it is in, where along it (0 to 1, the middle by default) and how wide (1.8 m by default).

const FIK_SHIAI_JO

FIK_SHIAI_JO: KendoFigures

The kendo shiai-jo (FIK Regulations, Article 2, the Subsidiary Rules' Article 1 and Figure 1).

function floorOf

floorOf(spec: DojoSpec, mat: MatSize): { width: number; length: number; }

The floor's size in metres from the options: its width (across, x) and its length (along, y).

function fourCorners

fourCorners(pieces: readonly Piece[]): number

How many points are a corner of four pieces or more: where four mats meet. A floor laid in the traditional way has none. Pieces that only touch along a side are not counted (a T is a point where one piece's side runs past the corners of two others, not a corner of three).

type Hall

interface Hall { id: string; name: string; nameJa: string; readingJa?: string; aliases: readonly PlaceAlias[]; city: string; country: string; lat: number; lon: number; wikidata: string; /** The first year of the institution or the building (the Kodokan's is its founding, 1882). */ from: number; /** What the first year is, in a phrase. */ fromWhat: { en: string; ja: string }; /** The most the hall seats or holds, whe…

A hall as the data gives it.

type HallBasis

type HallBasis = "documented" | "count" | "generic";

What is known of a surface's layout. documented: the floor, its size and its contest areas are from a source (the dohyō of the Association's rules). count: a source gives the number of mats, and the shape is plausible. generic: no source gives the size or the layout, and a plausible one is drawn.

const HALLS

HALLS: readonly Hall[]

The halls, in the order of their surfaces' fame: the Kodokan, the Budokan, the Kokugikan, the Butokuden and the Budokwai.

type HallSurface

interface HallSurface { /** The key within the hall: `main-dojo`. */ id: string; name: { en: string; ja: string }; sport: BudoSport; /** The options (beyond `sport`) that draw it. */ options: { league?: string; budo?: BudoStyle; dojo?: DojoSpec }; basis: HallBasis; /** The number of mats (jō) a source gives for the floor, where it gives one. */ mats?: number; /** What is known and what is drawn, in a sentence each. …

One surface a hall offers: a floor of a sport, with the options that draw it.

const IJF_CONTEST

IJF_CONTEST: JudoFigures

The IJF's contest area (Sport and Organisation Rules 8.7, version of 21 January 2026) as drawn for the Games, the Worlds and the Masters.

const IJF_KATA

IJF_KATA: JudoFigures

The kata area (SOR B1.8): 10 m for the World Championships and 8 m for other events, a safety area of 2 m, two white tapes of 5 cm by 50 cm, 3 m from the centre.

function isBudoSport

isBudoSport: (sport: string) => sport is BudoKind

True for a sport of the budō family.

const JSA_DOHYO

JSA_DOHYO: SumoFigures

The dohyō of the Japan Sumo Association (Sumo Rules, Dohyō Regulations, Article 1, and the Association's quizzes).

const JUDO_LEAGUES

JUDO_LEAGUES: readonly JudoLeague[]

The budō leagues: for each sport the rulebook's own first.

type JudoFigures

interface JudoFigures { kind: "judo"; /** The contest area, a square: 8 m to 10 m a side (IJF SOR 8.7); 10 m at the Olympic Games, the World Championships and the Masters, which is the size drawn. */ contest: Span; /** The safety area round it: at least 3 m (SOR 8.7). At the Games it is 4 m (the common and the exterior one); that is drawn. The rules give no greatest, so the drawing allows up to 6 m (ours). */ safety…

The figures of a judo contest area (a competition tatami), in metres.

type JudoLeague

type JudoLeague = LeagueBase<"judo", JudoFigures>;

The leagues of each budō sport.

const KARATE_LEAGUES

KARATE_LEAGUES: readonly KarateLeague[]

type KarateFigures

interface KarateFigures { kind: "karate"; /** The matted square, 8 m a side measured from the outside (WKF Kumite 1.1, Kata 1.1). */ area: number; /** Kumite: the outer 1 m of the square is in another colour, marking the boundary (1.1). None for kata. */ band: number; /** The safety area round the square: 2 m (1.1). */ safety: number; /** The least distance between two competition areas: 2 m (1.2). */ between: numbe…

The figures of a karate competition area, in metres.

type KarateLeague

type KarateLeague = LeagueBase<"karate", KarateFigures>;

const KENDO_LEAGUES

KENDO_LEAGUES: readonly KendoLeague[]

type KendoFigures

interface KendoFigures { kind: "kendo"; /** A square or a rectangle of 9 m to 11 m a side, inclusive of the width of the line (Article 2). 10 m is drawn: the middle (ours). */ side: Span; /** The lines are 5 cm to 10 cm wide and white as a rule (Subsidiary Rules, Article 1). */ lineWidth: Span; /** The cross at the centre: 30 cm to 40 cm (Figure 1). */ cross: Span; /** The two kaishi-sen, each 50 cm long and 1.4 m f…

The figures of a kendo shiai-jo, in metres.

type KendoLeague

type KendoLeague = LeagueBase<"kendo", KendoFigures>;

type Kimarite

interface Kimarite { key: string; ja: string; en: string; group: KimariteGroup; }

One kimarite.

const KIMARITE

KIMARITE: readonly Kimarite[]

The kimarite, in the order of the groups: basic, leg tripping, throwing, twisting down, backwards body drop, special, and the non-techniques with the two other results.

const KIMARITE_COUNT

KIMARITE_COUNT: number

How many techniques there are, as the Association counts them.

const KIMARITE_OTHERS

KIMARITE_OTHERS: Kimarite[]

The five non-techniques and the two other results.

type KimariteGroup

type KimariteGroup = "kihon" | "kake" | "nage" | "hineri" | "sori" | "tokushu" | "hi";

The groups of the kimarite.

function kimariteName

kimariteName(query: string, language: "en" | "ja"): string

The name of a kimarite in a language: its Japanese name, or its key in capitals-first romanisation; text that is no kimarite is kept as it was written.

function kimariteOf

kimariteOf(query: string): Kimarite | undefined

The kimarite a key, a Japanese name or an English gloss names, or undefined.

function kindOf

kindOf(w: number, h: number, mat: MatSize, along: "x" | "y"): PieceKind

The kind of a piece of a floor laid with mats of a size.

const KINTEKI

KINTEKI: KyudoFigures

The kyūdōjō for kinteki (近的), the 28 m range of a competition (ANKF competition manual 6 and the National Sports Festival's rules).

const KYOGIJO_BUDO_STYLE

KYOGIJO_BUDO_STYLE: string

Every budō sport's rules and every look, for a page that draws with style: "none" and supplies the stylesheet itself.

const KYUDO_LEAGUES

KYUDO_LEAGUES: readonly KyudoLeague[]

type KyudoFigures

interface KyudoFigures { kind: "kyudo"; /** The distance from the shooting position (射位) to the face of the target: 28 m (kinteki) or 60 m (enteki) (Wakayama 2015; ANKF manual 4). */ distance: number; /** The target: 36 cm across (a kasumi-mato) for kinteki, 100 cm for enteki; its centre 27 cm above the azuchi's base and tilted 5 degrees, or 97 cm above the ground and tilted 15 degrees. */ target: { diameter: number…

The figures of a kyūdōjō, in metres.

type KyudoLeague

type KyudoLeague = LeagueBase<"kyudo", KyudoFigures>;

function lay

lay(rect: MBox, mat: MatSize, along: "x" | "y", stagger: boolean): Piece[]

Lay a rectangle with mats whose long sides run along along, in columns the width of a mat. With stagger every second column is set off by half a mat, so that no four corners meet (the traditional lay); without it the mats make a grid. The floor's ends and sides are cut to fit where its size is not a whole number of mats; a column that is set off begins and ends with half a mat.

function lookNames

lookNames: (sport: BudoKind) => string[]

The names of a sport's looks, the default first.

type Mat

interface Mat { length: number; width: number; }

A mat: its length and width in metres.

function matCount

matCount: (pieces: readonly Piece[], mat: MatSize) => number

The number of mats a floor holds, counted as the mats' own area: a half mat is a half, a cut piece the share of a mat it covers.

type MatSize

interface MatSize { length: number; width: number; }

A mat's size.

type MBox

interface MBox { x0: number; y0: number; x1: number; y1: number; }

A box in metres.

type MMark

interface MMark { id: string; x: number; y: number; side: "top" | "bottom"; }

A part of the plan to label (landmarks): where it is, in metres, and the side of the drawing its label stands on.

type MRange

type MRange = MBox | { left: MBox; right: MBox };

A named part of the plan to crop to, in metres: one box, or one at each end (left and right as the surface is drawn wide).

type Piece

interface Piece extends MBox { kind: PieceKind; /** The axis of its long side: `x` or `y`. */ along: "x" | "y"; /** Which colour it is painted: 1 the first, 2 the second, 3 the third (a danger zone or a border). */ tone: 1 | 2 | 3; }

One piece of the floor, a mat or part of one.

type PieceKind

type PieceKind = "full" | "half" | "cut";

What a piece of floor is: a whole mat, half of one (a square), or a mat cut to fit.

const ROOM_HEIGHTS

ROOM_HEIGHTS: Readonly<{ wall: 3; dais: 0.3; bannerBottom: 1; bannerTop: 3; door: 2; windowSill: 1; windowHead: 2.4; shrine: 1.2; rail: 1; balcony: 4; mat: 0.05; rowRise: 0.4; rowDepth: 0.8; balconyDepth: 2.4; balconyRows: 4; }>

Our heights, in metres, where no source gives one.

type RoomBalcony

interface RoomBalcony { side: Compass; box: MBox; /** The height of the balcony's front edge above the mats. */ height: number; depth: number; rows: number; rowDepth: number; rowRise: number; /** The height of the rail along the front edge. */ rail: number; }

A balcony: the strip of floor it covers, its height above the mats, and its rows of seats, from the rail outwards, each higher than the one before.

type RoomBasis

type RoomBasis = "source" | "plan" | "ours";

Where a figure of the room comes from: a source (a rule or a published figure), the plan (it follows from the drawn layout) or ours (a plausible figure of ours, no source gives it).

type RoomOpening

interface RoomOpening { id: string; wall: Compass; box: MBox; sill: number; head: number; }

A door or a window cut in a wall: the footprint in the wall, the sill (the height of its bottom) and the head (the height of its top).

type RoomWall

interface RoomWall { id: string; side: Compass | "octagon"; from: readonly [number, number]; to: readonly [number, number]; thickness: number; height: number | undefined; basis: RoomBasis; }

A wall: its centre line, thickness and height.

function shapeOfJo

shapeOfJo(jo: number): [number, number]

The mats' widths across and along of a floor of jo mats: the usual shape where there is one, else the whole-number shape nearest to 1 to 1.6.

type Side4

type Side4 = Compass | "top" | "bottom" | "left" | "right";

const SUMO_LEAGUES

SUMO_LEAGUES: readonly SumoLeague[]

type SumoFigures

interface SumoFigures { kind: "sumo"; /** The clay square: 6.7 m a side (Sumo Rules, Dohyō Regulations, Article 1). */ platform: number; /** Its height: 34 cm to 60 cm for a public dohyō (Article 1); 55 cm is drawn (ours). */ height: Span; /** The circle of tawara: 4.55 m, 15 shaku (Article 1). Taken as the inside of the ring, where the wrestlers stand (ours: the rule does not say which edge). */ circle: number; /**…

The figures of a sumo dohyō, in metres.

type SumoLeague

type SumoLeague = LeagueBase<"sumo", SumoFigures>;

type Tatami

type Tatami = Piece;

One mat of a dojo's floor, or a part of one, in metres from the floor's top left corner.

const TATAMI

TATAMI: Readonly<Record<NonNullable<"2x1" | "1x1" | "182x91" | undefined>, MatSize>>

The mats a tatami option names.

function tint

tint(pieces: readonly Piece[], zones: readonly { box: MBox; tone: 1 | 2 | 3; }[], rest: 1 | 2 | 3, mat: MatSize): Piece[]

Give every piece the tone of the zone it lies in: the first of zones that holds it, else rest. A mat that a boundary runs through is cut there, each part taking its own zone's tone.

@johnmorrisdotca/kyogijo-stadiums/charts

Ball Chart ChartOptions CURLING_COLORS CURLING_HIT_TYPES CurlingEndRecord CurlingEndScore curlingEndStats CurlingEndStats curlingFreeGuards CurlingGuardEnd CurlingGuards curlingGuardScores CurlingGuardScores curlingHammers CurlingHammerStats curlingHeat CurlingHeatOptions CurlingHeatTotals curlingInPlay CurlingInPlay curlingIsHit CurlingOptions curlingScore CurlingShot CurlingShotLine curlingShotStats CurlingShotStats CurlingSide CurlingStealStats CurlingStone CurlingStoneResult curlingStones CurlingTeamEnds CurlingTotals GoalMouth GoalMouthOptions KeyItem KYOGIJO_ROAD_STYLE mlbSprayChart MlbSprayOptions MlbTotals NbaShot nbaShotChart NbaShotOptions NbaTotals NflPass nflPassChart NflPassOptions NflTotals NhlShot nhlShotMap NhlShotOptions NhlTotals passNetwork PassNetworkOptions PassNetworkPass PassNetworkTotals ROAD_SIZES RoadChart RoadDecided RoadFinal RoadFinalist RoadGroupMatch RoadMatch RoadOptions RoadRecord RoadScore RoadSize RoadSizeInfo roadToFinal RoadTotals SoccerChart soccerGoalMouth SoccerShot soccerShotMap SoccerShotOptions SoccerTotals SplitRow splitsChart SplitsOptions SplitsTotals SwimmerSplits ZoneTotal

type Ball

interface Ball { /** Your id for the shot, written on its mark as `data-mark-id`. */ id?: string | number; x?: number | null; y?: number | null; /** A Retrosheet hit-location code in place of `x` and `y`. */ zone?: string; /** What happened: `single`, `double`, `triple`, `home run`, `out`, or the words of Statcast's `events` (`home_run`, `field_out`) or the usual short forms (`1B`, `HR`). Any other word is drawn as …

A ball in play. Give x and y in the frame, or for Retrosheet a zone (the location code, 8D).

type Chart

interface Chart<Totals = Record<string, never>> { /** The drawing's options: the sport, the layers, the language, a title and what the chart needs of the surface (the park and the season of a spray chart, the half of a pitch). Pass them to `drawKyogijo`, `drawSurface`, `mountKyogijo` or `kyogijoProps`; change any of them first if you like. */ options: SurfaceOptions; /** The layers, the same list as `options.layers`…

What every chart gives back: the layers and the drawing options to put them on, a sentence, the totals, and the ways to draw them.

type ChartOptions

interface ChartOptions { /** The language of the chart's words: its title, its key, the labels of its zones and its table. English by default. */ language?: Language; /** A prefix for the ids inside the drawing. Made from the chart when missing, so that two charts on a page do not share ids. */ id?: string; /** Draw the key (what each mark is) in the margin above the surface. On by default. */ key?: boolean; /** Dra…

The options every chart takes.

const CURLING_COLORS

CURLING_COLORS: { readonly home: "#cf2e2e"; readonly away: "#f2c230"; }

The colours of the two teams' stones, the handles' colours of a game: red and yellow.

const CURLING_HIT_TYPES

CURLING_HIT_TYPES: readonly string[]

The shot types that are hits: a stone thrown to take out another. Any other type is not a hit, unless the shot says hit.

type CurlingEndRecord

interface CurlingEndRecord { /** The number of the end, from 1. */ end: number; home: number; away: number; /** The team with the hammer, and the team that scored (`null` for a blank end). */ hammer: CurlingSide | null; scoring: CurlingSide | null; points: number; blank: boolean; /** The team without the hammer scored. */ steal: boolean; /** The team with the hammer scored exactly one point (it was forced to take on…

One end, worked out.

type CurlingEndScore

interface CurlingEndScore { home: number; away: number; hammer?: CurlingSide; }

What a team scored in an end: one team scores in an end and the other has nothing, or neither does (a blank end). hammer is the team that threw last, when the record says.

function curlingEndStats

curlingEndStats(ends: readonly CurlingEndScore[], options?: { hammer?: CurlingSide; }): CurlingEndStats

The ends of a game: for each end who scored and how many, who had the hammer, whether it was a blank end, a steal or a forced one; and for each team its hammer efficiency (points per end with the hammer), how often it scored, blanked, was held to one or was stolen from with the hammer, and how often it stole without it. Each end is what each team scored in it (one of them nothing: an end is scored by one team, so two scores in an end are an error), as a score board's ends give it; hammer is the team that threw last in the first end, or each end names its own.

Example

import { curlingEndStats } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const game = curlingEndStats(
  [{ home: 2, away: 0 }, { home: 0, away: 1 }, { home: 0, away: 1 }, { home: 1, away: 0 }, { home: 0, away: 2 }],
  { hammer: "home" },
);
console.log(game.home.points, game.away.points, game.home.withHammer.efficiency, game.away.withoutHammer.steals);
// 3 4 1 1

type CurlingEndStats

interface CurlingEndStats { ends: number; /** Ends in which neither team scored. */ blanks: number; records: CurlingEndRecord[]; home: CurlingTeamEnds; away: CurlingTeamEnds; }

What curlingEndStats gives back.

function curlingFreeGuards

curlingFreeGuards(stones: readonly CurlingStone[], options?: { units?: CurlingOptions["units"]; width?: number; }): CurlingGuards

The free guards of a layout. A free guard is a stone in play that is between the tee line and the hog line and is not in the house (the free guard zone, World Curling R6 and Curling Canada Rule 12): its centre is nearer the hog line than the tee line, and no part of it is within the 12-foot circle. Stones out of play are not counted. Places are feet from the button (in units), as curlingScore takes them.

Example

import { curlingFreeGuards } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const guards = curlingFreeGuards([
  { x: 0.2, y: -11, team: "home" },
  { x: -2, y: -16, team: "away" },
  { x: 0.5, y: 0.5, team: "home" },
  { x: 3, y: -19, team: "away" },
]);
console.log(guards.home, guards.away, guards.indices);
// 1 2 [ 0, 1, 3 ]

type CurlingGuardEnd

interface CurlingGuardEnd { hammerGuards: number; otherGuards: number; hammerPoints: number; otherPoints: number; }

One end for the free guard analysis: the free guards each team had after the first four stones of the end (or whenever the caller counts them), and the points each scored.

type CurlingGuards

interface CurlingGuards { home: number; away: number; /** The places in the list given of the stones that are free guards. */ indices: number[]; /** A team that is not `home` or `away` is counted under its name. */ others: Record<string, number>; }

The free guards of a layout: how many each team has, and which stones they are.

function curlingGuardScores

curlingGuardScores(ends: readonly CurlingGuardEnd[], options?: { bands?: readonly number[]; blanks?: boolean; }): CurlingGuardScores

How many points each team scored against how many free guards each had out: the free guard analysis (does the team with the hammer score more when it has put guards out, does the team without it score more when it has?). The guards are put in bands, 0, 1-2 and 3+ by default (bands are the lowest number of each band). blanks: false leaves the blank ends out of the hammer team's averages, as a team that blanks an end has chosen to score nothing.

Example

import { curlingGuardScores } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const table = curlingGuardScores([
  { hammerGuards: 0, otherGuards: 1, hammerPoints: 1, otherPoints: 0 },
  { hammerGuards: 2, otherGuards: 0, hammerPoints: 2, otherPoints: 0 },
  { hammerGuards: 1, otherGuards: 2, hammerPoints: 0, otherPoints: 1 },
]);
console.log(table.bands.join(" "), table.hammer.map((b) => b.average ?? "-").join(" "));
// 0 1-2 3+ 1 1 -

type CurlingGuardScores

interface CurlingGuardScores { /** The names of the bands of guards: `0`, `1-2` and `3+` by default. */ bands: string[]; /** For each band of guards the hammer team had: the ends, and the average points the hammer team scored in them. */ hammer: { band: string; ends: number; average: number | null }[]; /** The same for the guards the team without the hammer had and the points it scored. */ other: { band: string; end…

What curlingGuardScores gives back.

function curlingHammers

curlingHammers(ends: readonly CurlingEndScore[], first: CurlingSide): CurlingSide[]

The team with the hammer in each end. The team that scored in an end gives up the hammer: the other team has it in the next end. After a blank end the same team keeps it. An end that names its own hammer is taken as it says, and the rule goes on from there. first is the team with the hammer in the first end.

Example

import { curlingHammers } from "@johnmorrisdotca/kyogijo-stadiums/charts";
console.log(curlingHammers([{ home: 2, away: 0 }, { home: 0, away: 0 }, { home: 0, away: 1 }, { home: 1, away: 0 }], "home"));
// [ 'home', 'away', 'away', 'home' ]

type CurlingHammerStats

interface CurlingHammerStats { /** Ends with the hammer. */ ends: number; /** Ends in which the team scored, and its points in them. */ scored: number; points: number; /** Ends it blanked (to keep the hammer), ends it was stolen from, ends it scored exactly one, two or more, and three or more. */ blanked: number; stolen: number; forced: number; multi: number; big: number; /** Points per end with the hammer, the meas…

How a team did in the ends it had the hammer.

function curlingHeat

curlingHeat(stones: readonly CurlingStone[], options?: CurlingHeatOptions): Chart<CurlingHeatTotals> & { key: KeyItem[]; }

A heat chart of the stones of many ends on a house: where they came to rest, counted into cells (heatmap, hexbin), a density (contour), the zones of the house (zones) or drawn as translucent discs in their team's colours (discs). Only stones in play are drawn, as curlingScore judges them. team keeps one team's stones, and only the free guards or the stones in the house, so that "where do the guards of the team with the hammer end up" is one call. Places are feet from the button, as for curlingStones; give the stones of every end together. The totals say how many stones were drawn, the free guards and the stones in the house for each team, and the share in each zone.

Example

import { curlingHeat } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const stones = [
  { x: 0.3, y: 0.2, team: "home" },
  { x: -1.1, y: 0.9, team: "away" },
  { x: 0.1, y: -14, team: "away" },
  { x: 2.4, y: -17, team: "home" },
  { x: 0, y: -10, team: "home" },
];
const chart = curlingHeat(stones, { only: "guards", view: "discs" });
console.log(chart.totals.drawn, chart.totals.guards, chart.totals.house);
// 3 { home: 2, away: 1 } { home: 1, away: 1 }

type CurlingHeatOptions

interface CurlingHeatOptions extends ChartOptions { /** The house the stones are at: the `left` one (the top house of a tall drawing, the default) or the `right`. */ house?: "left" | "right"; /** The unit of the stones' places: feet by default. */ units?: Unit; /** Only this team's stones. Both by default. */ team?: "home" | "away"; /** Which stones: `all` of those in play (the default), `guards` (the free guards on…

What curlingHeat takes.

type CurlingHeatTotals

interface CurlingHeatTotals { /** Stones with a place, and the stones in play among them. */ stones: number; inPlay: number; /** The stones drawn: in play, and of the team and the kind asked for. */ drawn: number; /** Of those in play, the free guards and the stones in the house, for each team. */ guards: { home: number; away: number }; house: { home: number; away: number }; /** Each zone of the house and the free g…

The numbers behind a heat chart of stones.

function curlingInPlay

curlingInPlay(layouts: readonly (readonly CurlingStone[])[], options?: { units?: CurlingOptions["units"]; width?: number; }): CurlingInPlay

The stones in play after each throw of an end, from the layout after each throw (curlingScore's rules: a stone that touches a sideline, does not come to rest beyond the hog line or has wholly crossed the back line is out). The most stones in play is the measure of how crowded an end got.

Example

import { curlingInPlay } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const end = curlingInPlay([
  [{ x: 0, y: -12, team: "home" }],
  [{ x: 0, y: -12, team: "home" }, { x: 1, y: 0, team: "away" }],
  [{ x: 1, y: 0, team: "away" }],
]);
console.log(end.counts, end.max, end.at);
// [ 1, 2, 1 ] 2 1

type CurlingInPlay

interface CurlingInPlay { /** Stones in play in each layout, in the order given (after each throw). */ counts: number[]; /** The most stones in play at any time of the end, and the first layout that had that many. */ max: number; at: number; }

The stones in play in each layout of an end, and the most there were.

function curlingIsHit

curlingIsHit(shot: CurlingShot): boolean

True for a shot that is a hit: it says so, or its type is one of CURLING_HIT_TYPES.

type CurlingOptions

interface CurlingOptions extends ChartOptions { /** The number of the end, for the title: `6` gives "End 6". */ end?: number; /** The team that has the hammer, the last stone of the end. It is named in the summary. */ hammer?: "home" | "away"; /** The teams' names, for the summary and the stones' tooltips. */ names?: { home?: string; away?: string }; /** The house the stones are at: the `left` one (the top house of …

What curlingStones takes.

function curlingScore

curlingScore(stones: readonly CurlingStone[], options?: { units?: CurlingOptions["units"]; width?: number; }): CurlingTotals

Judge the stones of an end by the rules. A stone is out of play, and taken off the sheet, when it touches a sideline, when it does not come to rest wholly beyond the hog line, or when it has wholly crossed the back line. A stone with any part within 6 feet of the button (in or touching the house) can score; the team with the stone nearest the button scores one point for each of its stones that are nearer than every stone of the other team. Stones as near as each other are equal, and equal stones do not count: if the two nearest are of different teams and as near, the end is blank. Places are feet from the button (in units, feet by default), x across and y along.

Example

import { curlingScore } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const end = curlingScore([
  { x: 0.3, y: 0.2, team: "home" },
  { x: -1.5, y: 1, team: "home" },
  { x: 2, y: -2, team: "away" },
]);
console.log(end.scoring, end.points, end.shot);
// home 2 0

type CurlingShot

interface CurlingShot { team?: CurlingSide | (string & {}); /** The kind of shot, as the summary names it: `draw`, `take-out`, `guard`, `clearing`, `hit and roll`... */ type?: string; /** How well it was played, 0 to 4 (World Curling's scale). A shot without a score is counted in the types and left out of the percentage. */ score?: number | null; hit?: boolean; }

One shot, as a shot-by-shot summary gives it: who threw it, what kind it was, and how it was scored from 0 to 4 (4 is the perfect shot). hit says whether it was a hit, when the type does not.

type CurlingShotLine

interface CurlingShotLine { shots: number; /** Shots with a score, and the points they earned out of 4 each. */ scored: number; points: number; /** The shot percentage: the points as a percent of the most they could earn, 4 for each scored shot. */ percent: number | null; hits: number; /** Percent of the shots that were hits. */ hitRate: number | null; }

A group of shots: how many, how many were scored, and the shot percentage.

function curlingShotStats

curlingShotStats(shots: readonly CurlingShot[]): CurlingShotStats

The shots of a game, by team and by type: the shot percentage (the points earned, 0 to 4 for each shot, as a percent of 4 for each scored shot, the way World Curling's shot-by-shot summaries and broadcasts count it), the hit rate, and how often each type of shot was played. A score outside 0 to 4 is an error.

Example

import { curlingShotStats } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const shots = curlingShotStats([
  { team: "home", type: "draw", score: 4 },
  { team: "home", type: "take-out", score: 3 },
  { team: "away", type: "Hit and Roll", score: 2 },
  { team: "away", type: "guard", score: 4 },
]);
console.log(shots.percent, shots.teams.home.percent, shots.hitRate, shots.types[0].type);
// 81.3 87.5 50 draw

type CurlingShotStats

interface CurlingShotStats extends CurlingShotLine { /** For each team (as the shots name it). */ teams: Record<string, CurlingShotLine>; /** For each type of shot, most played first: the line, and its share of all the shots as a percent. */ types: (CurlingShotLine & { type: string; share: number | null })[]; /** Shots that were not scored, and shots with no place in `teams` or `types` because they name none. */ uns…

What curlingShotStats gives back.

type CurlingSide

type CurlingSide = "home" | "away";

A team of a game, as the stones and the board name them.

type CurlingStealStats

interface CurlingStealStats { ends: number; /** Ends in which it scored (stole), and its points in them. */ steals: number; points: number; /** Percent of its ends without the hammer in which it stole. */ stealRate: number | null; }

How a team did in the ends it had no hammer.

type CurlingStone

interface CurlingStone { x: number | null; y: number | null; /** The team that threw it: `home` or `away` take the two stone colours (red and yellow by default). */ team?: LayerTeam; /** Its number among its team's eight, 1 to 8, written on the stone. */ number?: number; /** The tooltip of the stone and its line in the table. Made from the team, the number and the distance when missing. */ title?: string; }

A stone at rest, as its place on the sheet: feet from the button of the house it was played to (the curling frame), x across the sheet (positive to the thrower's right) and y along it (positive towards the back line).

type CurlingStoneResult

interface CurlingStoneResult { /** The place of the stone in the list given. */ index: number; /** Its centre's distance from the button, in feet. */ distance: number; /** In play: it did not touch a sideline, and it stands wholly beyond the hog line and not wholly beyond the back line. */ inPlay: boolean; /** Any part of it is within 6 feet of the button (in or touching the house), so that it can score. */ inHouse:…

One stone as the chart judged it.

function curlingStones

curlingStones(stones: readonly CurlingStone[], options?: CurlingOptions): Chart<CurlingTotals> & { key: KeyItem[]; }

A chart of the stones of an end on the house: each stone as seen from above (grey granite and its team's handle, with its number), the stones that score ringed and the shot rock, the nearest, ringed twice, and the score of the end under the rules (curlingScore). It shows the half of the sheet from the hog line to the back line, with the free guard zone shaded, at the house on the left (the top of a tall drawing) or the right. Stones out of play are taken off, as the rules take them. layerTable and chart.table() list every stone with its distance from the button.

Example

import { curlingStones } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = curlingStones(
  [
    { x: 0.4, y: 0.3, team: "home", number: 8 },
    { x: -1.2, y: -0.8, team: "home", number: 6 },
    { x: 1.9, y: 1.1, team: "away", number: 7 },
    { x: 0, y: -12, team: "away", number: 3 },
  ],
  { end: 6, hammer: "home", names: { home: "Prairie Ridge", away: "Harbour City" } },
);
console.log(chart.totals.points, chart.totals.scoring, chart.title);
// 2 home Curling stones, End 6

type CurlingTeamEnds

interface CurlingTeamEnds { points: number; /** Ends in which the team scored. */ endsScored: number; withHammer: CurlingHammerStats; withoutHammer: CurlingStealStats; }

One team's ends.

type CurlingTotals

interface CurlingTotals { /** Stones with a place. */ stones: number; /** In play, and out of play (removed, so not drawn). */ inPlay: number; out: number; /** The stones that score, and the points. */ counting: number; points: number; /** The team that scores, or `null` for a blank end. */ scoring: string | null; /** The shot rock's place in the list given, or `null`. */ shot: number | null; /** No one scores: no s…

The numbers behind a curling chart.

type GoalMouth

interface GoalMouth { /** The drawing as an SVG string. */ svg: string; /** The layers of the marks, in the frame of the goal (`x` across from the centre, `y` the height, in metres), if you want to draw them another way. */ layers: Layer[]; /** The shots that had a place across the goal and the ones that did not. */ placed: number; skipped: number; /** The table of the shots with their place across the goal (`x` acr…

The goal-mouth view: a small drawing of the goal frame seen from the front, with where each shot crossed the goal line.

type GoalMouthOptions

interface GoalMouthOptions { language?: Language; id?: string; /** The unit of `goalY` and `goalZ`: `m` (the default), `yd` or `ft`. */ unit?: "m" | "yd" | "ft"; /** The size of the goal in metres: the Laws' 7.32 by 2.44 by default. */ goal?: { width: number; height: number }; /** Size the marks by expected goals. On when any shot has one. */ xg?: boolean; xgMax?: number; theme?: SurfaceOptions["theme"]; style?: Sur…

The options of the goal-mouth view.

type KeyItem

interface KeyItem { /** The outcome the mark stands for (`made`, `goal`). */ outcome: string; /** What the mark is called, in the chart's language. */ label: string; shape: MarkShape; /** Drawn open (a miss, a save) and not filled. */ hollow: boolean; }

One entry of a chart's key: a mark and what it means. A chart lists its own in key.

const KYOGIJO_ROAD_STYLE

KYOGIJO_ROAD_STYLE: string

The rules of the road-to-the-final chart (roadToFinal): the ground, the ink and the quiet ink in light and dark, the panel behind a round's name, the road down the middle, and the classes of the title, the names, the scores and the small words under them. The colours are custom properties on the chart (--ky-road-bg, --ky-road-ink, --ky-road-muted, --ky-road-panel, --ky-road-spine, --ky-road-left, --ky-road-right), so a page can set them. A chart writes these rules in, with the round mark's (KYOGIJO_MARK_STYLE); KYOGIJO_STYLE has them for a page that draws with style: "none".

function mlbSprayChart

mlbSprayChart(balls: readonly Ball[], options?: MlbSprayOptions): Chart<MlbTotals> & { key: KeyItem[]; }

A spray chart for baseball from batted-ball data: where each ball came down, on the ballpark of that season (park and year, past parks included, each with the fence it had then), each hit type in its own mark (a single a small circle, a double a diamond, a triple a triangle, a home run a star, an out an open circle), the balls that came down beyond the fence drawn beyond it, and if asked the spray zones (pull, centre and opposite by infield and outfield, by the side the batter bats from). Balls come as Statcast's hc_x and hc_y (mlbam), as feet from home plate (retrosheet) or as Retrosheet's hit-location codes.

Example

import { mlbSprayChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = mlbSprayChart(
  [
    { x: 125.4, y: 90, type: "single" },
    { x: 60, y: 60, type: "home_run" },
    { x: 150, y: 120, type: "field_out" },
    { zone: "8D", type: "double" },
  ],
  { park: "polo-grounds", year: 1915, bats: "R", zones: true },
);
console.log(chart.totals.balls, chart.totals.types["home run"], chart.options.park, chart.options.year);
console.log(chart.draw().includes("Polo Grounds"));
// 4 1 polo-grounds 1915
// true

type MlbSprayOptions

interface MlbSprayOptions extends ChartOptions { /** The frame the balls are in: `mlbam` (the default; Statcast's `hc_x` and `hc_y`), `retrosheet` (feet from home plate, or zone codes), or another frame of the field. */ from?: Frame | string; /** The ballpark: a key of `PARKS`, past parks included. The generic field when missing. */ park?: string; /** The season: a past park is drawn with the fence it had in it (`po…

What mlbSprayChart takes.

type MlbTotals

interface MlbTotals { balls: number; /** Balls by type: `single`, `double`, `triple`, `home run`, `out`, and any other word given. */ types: Record<string, number>; /** Balls that came down beyond the fence of the park in that season. */ beyondFence: number; skipped: number; /** The zones, when the chart has them: the balls in each and, for a hit rate, the hits. */ zones: ZoneTotal[]; }

The numbers behind a spray chart.

type NbaShot

interface NbaShot { /** Your id for the shot, written on its mark as `data-mark-id`. */ id?: string | number; x: number | null; y: number | null; /** True for a shot that went in. Or give `outcome`. */ made?: boolean; /** `made` or `missed`, in place of `made`. */ outcome?: "made" | "missed" | (string & {}); /** A number to carry with the shot (a distance, a value over average). */ value?: number | null; team?: Laye…

A shot as stats.nba.com's shot chart gives it: LOC_X and LOC_Y and whether it went in.

function nbaShotChart

nbaShotChart(shots: readonly NbaShot[], options?: NbaShotOptions): Chart<NbaTotals> & { key: KeyItem[]; }

A shot chart for the NBA from stats.nba.com's shot data: each shot as a mark (filled when it went in, an open x when it missed), and the six shot zones coloured by their field-goal percentage, with the percentage and made over taken written on each, a legend and a table. Give league (the league's percentage) and the zones are coloured by how far they are above or below it, on a diverging ramp.

Example

import { nbaShotChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = nbaShotChart(
  [
    { x: 0, y: 12, made: true },
    { x: -231, y: 18, made: false },
    { x: 120, y: 140, made: true },
    { x: -30, y: 70, made: true },
  ],
  { league: 46, minShots: 1 },
);
console.log(chart.totals.shots, chart.totals.made, chart.layers.map((layer) => layer.kind).join(" "));
console.log(chart.draw().startsWith("<svg"), chart.table().includes("Restricted area"));
// 4 3 zones points points text
// true true

type NbaShotOptions

interface NbaShotOptions extends ChartOptions { /** The frame the shots are in: `nba-stats` (the default), or another frame of the court. */ from?: Frame | string; /** The basket the shots were taken at: `right` (the default; the shot chart puts every shot at the right basket) or `left`. */ end?: "left" | "right"; /** Count the shots into the six shot zones (restricted area, in the paint, mid-range, both corner thre…

What nbaShotChart takes.

type NbaTotals

interface NbaTotals { shots: number; made: number; /** The percent that went in, one decimal, or null with no shots. */ percent: number | null; /** Rows left out for want of a place. */ skipped: number; zones: ZoneTotal[]; }

The numbers behind an NBA shot chart.

type NflPass

interface NflPass { /** Your id for the shot, written on its mark as `data-mark-id`. */ id?: string | number; /** Where the ball came down or was caught, in the frame. */ x: number | null; y: number | null; /** Where it was thrown from. With it a pass can be drawn as an arrow. */ fromX?: number | null; fromY?: number | null; /** `complete`, `incomplete`, `touchdown` or `interception`. */ outcome?: "complete" | "inco…

A pass. The destination is x and y; the passer's place is fromX and fromY when you have it.

function nflPassChart

nflPassChart(passes: readonly NflPass[], options?: NflPassOptions): Chart<NflTotals> & { key: KeyItem[]; }

A pass chart for the NFL from tracking data: where each pass came down as a mark (a completion filled, an incompletion open, a touchdown a star, an interception an x), the line of scrimmage, and the pass grid (short and deep by left, middle and right) coloured by completion percentage with the air yards of each pass in its table. The offence goes to the right whichever way direction says it went in the data.

Example

import { nflPassChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = nflPassChart(
  [
    { x: 72, y: 26, outcome: "complete" },
    { x: 88, y: 14, outcome: "incomplete" },
    { x: 104, y: 30, outcome: "touchdown" },
  ],
  { los: 65, minPasses: 1 },
);
console.log(chart.totals.passes, chart.totals.complete, chart.totals.airYards);
// 3 2 23

type NflPassOptions

interface NflPassOptions extends ChartOptions { /** The frame the passes are in: `nfl-tracking` (the default; yards, the end zones counted) or another frame of the field. */ from?: Frame | string; /** The line of scrimmage, in the frame's numbers along the field (60 for midfield in `nfl-tracking`). Midfield by default. */ los?: number; /** The way the offence goes in the frame's numbers: `right` (the default) or `le…

What nflPassChart takes.

type NflTotals

interface NflTotals { passes: number; complete: number; touchdowns: number; interceptions: number; /** The percent that were caught (touchdowns included), one decimal, or null with no passes. */ percent: number | null; /** The mean air yards of the passes that have a place, or null. */ airYards: number | null; skipped: number; zones: ZoneTotal[]; }

The numbers behind a pass chart.

type NhlShot

interface NhlShot { /** Your id for the shot, written on its mark as `data-mark-id`. */ id?: string | number; x: number | null; y: number | null; /** `goal`, `saved` (a shot on goal), `missed`, `blocked`, or `shot` for an attempt of no known result. */ outcome?: "goal" | "saved" | "missed" | "blocked" | "shot" | (string & {}); /** True for a goal, in place of `outcome`. */ goal?: boolean; /** The chance the shot had…

A shot as the NHL's play-by-play gives it.

function nhlShotMap

nhlShotMap(shots: readonly NhlShot[], options?: NhlShotOptions): Chart<NhlTotals> & { key: KeyItem[]; }

A shot map for the NHL from play-by-play shots: each shot a mark (a goal a star, a save an open circle, a miss an open x, a block an open triangle) sized by its chance of scoring, with an optional count of the shots into the slot zones or hexagons, a legend, a key and a table. With fold the shots at the left end are turned a half turn, so that every shot is at the right end.

Example

import { nhlShotMap } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = nhlShotMap(
  [
    { x: 75, y: 4, outcome: "goal", xg: 0.31 },
    { x: -60, y: -12, outcome: "saved", xg: 0.07 },
    { x: 40, y: 20, outcome: "missed", xg: 0.03 },
  ],
  { fold: true, zones: true },
);
console.log(chart.totals.shots, chart.totals.goals, chart.totals.xg);
console.log(chart.layers.map((layer) => layer.id).join(" "));
// 3 1 0.41
// zones shots key-marks key-text

type NhlShotOptions

interface NhlShotOptions extends ChartOptions { /** The frame the shots are in: `nhl-api` (the default), `hockeytech`, or another frame of the rink. */ from?: Frame | string; /** Turn the shots at the left end a half turn so that all of them are at the right end: one half of the rink, every shot. Off by default. */ fold?: boolean; /** `xg` sizes each mark by its chance of scoring (the area of the mark follows `xg`);…

What nhlShotMap takes.

type NhlTotals

interface NhlTotals { shots: number; goals: number; /** The sum of the shots' chances, two decimals, or null when no shot has one. */ xg: number | null; skipped: number; /** The zones, when the chart has them. */ zones: ZoneTotal[]; /** Shots and goals by team, when the shots name one. */ teams: Record<string, { shots: number; goals: number; xg: number | null }>; }

The numbers behind a hockey shot map.

function passNetwork

passNetwork(passes: readonly PassNetworkPass[], options?: PassNetworkOptions): Chart<PassNetworkTotals> & { key: KeyItem[]; }

A pass network for soccer: a node for each player at the average place of the passes he made and received, sized by touches, and a link for each pair of players, wider and darker the more passes there were between them. Links of fewer than minPasses passes (3) are left out. The numbers behind it are in totals, and table() lists the players and the links as text.

Example

import { passNetwork } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const passes = [
  { from: 4, to: 8, x: 30, y: 40, x2: 45, y2: 55, team: "home" },
  { from: 8, to: 4, x: 45, y: 55, x2: 32, y2: 42, team: "home" },
  { from: 4, to: 8, x: 31, y: 41, x2: 46, y2: 54, team: "home" },
  { from: 8, to: 9, x: 46, y: 54, x2: 70, y2: 50, team: "home" },
];
const chart = passNetwork(passes, { provider: "opta", minPasses: 2 });
console.log(chart.totals.passes, chart.totals.players, chart.totals.links, chart.totals.strongest);
// 4 3 1 { from: '4', to: '8', passes: 3 }

type PassNetworkOptions

interface PassNetworkOptions extends ChartOptions { /** The provider the passes are in (`opta`, `statsbomb`, `wyscout`, ...): the pitch is drawn in its numbers. Or a `Frame` of your own. `international` (metres) by default. */ provider?: SoccerProviderId | Frame; /** The end the passes attack, in the provider's numbers: `right` (the default) or `left`. */ attacking?: "left" | "right"; /** Leave out links of fewer pa…

What passNetwork takes.

type PassNetworkPass

type PassNetworkPass = PassDatum;

A pass of a pass network: who made it and who received it, from where to where.

type PassNetworkTotals

interface PassNetworkTotals { /** Passes counted, and rows left out for want of a passer or a receiver. */ passes: number; skipped: number; /** Players with a place, and links drawn (those of at least `minPasses` passes). */ players: number; links: number; /** The player with most touches, or null with none. */ busiest: { id: string | number; name: string; touches: number } | null; /** The link with most passes, or …

The numbers behind a pass network.

const ROAD_SIZES

ROAD_SIZES: Readonly<Record<RoadSize, RoadSizeInfo>>

The sizes in pixels: card 1080 by 1350, square 1080 by 1080, wide 1200 by 675 and phone 390 wide and as tall as the rounds need.

type RoadChart

interface RoadChart { /** The options the chart was made with, with the finalists: pass changes to `draw`. */ options: RoadOptions; /** The chart's accessible name, in the chart's language. */ title: string; /** What the chart shows, in a sentence or two, for a caption. */ summary: string; /** The numbers behind it. */ totals: RoadTotals; /** The drawing as an SVG string. `extra` is laid over the options (`size`, `t…

What roadToFinal gives back.

type RoadDecided

type RoadDecided = "ft" | "aet" | "pens";

How a match was decided: ft (full time, the default), aet (after extra time) or pens (a shoot-out after extra time).

type RoadFinal

interface RoadFinal { /** The score, the first finalist's first. */ score: RoadScore; decidedBy?: RoadDecided; /** The shoot-out, the first finalist's first. */ pens?: readonly [number | string, number | string]; note?: string; }

The final, when it has been played.

type RoadFinalist

interface RoadFinalist extends Team { /** The knockout matches before the final, the earliest first. */ path: readonly RoadMatch[]; /** The group stage, in the order played. A row of small marks, codes and scores at the foot. */ group?: readonly RoadGroupMatch[]; }

A finalist: a team and the matches it played on the way, the earliest first.

type RoadGroupMatch

type RoadGroupMatch = Omit<RoadMatch, "round"> & { round?: string };

One match of the group stage: as a knockout match, without a round.

type RoadMatch

interface RoadMatch { /** The round, as a key. `round-of-32`, `round-of-16`, `quarter-final` and `semi-final` have words in English and Japanese; any other key is written as it is, or as `options.labels` names it. */ round: string; /** The team it played: its name, abbreviation, `logo` (the flag or the crest) and `color`. */ opponent: Team; /** The score, the finalist's first: `[2, 1]` is a 2-1 win. */ score: RoadSc…

One match on a finalist's way.

type RoadOptions

interface RoadOptions { /** The words of the chart: its title, the rounds' names, how a match was decided and the table. English by default. */ language?: Language; /** A prefix for the ids inside the drawing. Made from the chart when missing, so that two charts on a page do not share ids. */ id?: string; /** A renderer of ready-made emblems for this chart alone, so that a finalist's `logo` may be an emblem's id. Ov…

What roadToFinal takes besides the finalists.

type RoadRecord

interface RoadRecord { name: string; /** Matches played in the group and the knockout rounds (not the final), and how many it won, drew and lost. */ matches: number; won: number; drawn: number; lost: number; /** The goals it scored and let in over the matches whose score is a pair of numbers. */ scored: number; conceded: number; }

One finalist's record on the way.

type RoadScore

type RoadScore = readonly [number | string, number | string] | string;

A match's score from the finalist's side: [for, against] (numbers, or text such as a series' 4), or one text, "4-2", that is split at its dash.

type RoadSize

type RoadSize = "card" | "square" | "wide" | "phone";

The sizes of the chart: card 1080 by 1350 (a tall post), square 1080 by 1080, wide 1200 by 675 (the finalists at the two ends) and phone 390 wide and as tall as it needs.

type RoadSizeInfo

interface RoadSizeInfo { readonly width: number; /** `undefined` for a size that is as tall as the rounds need. */ readonly height: number | undefined; readonly about: string; }

The size in pixels a size draws at, and what it is for.

function roadToFinal

roadToFinal(finalists: readonly [RoadFinalist, RoadFinalist], options?: RoadOptions): RoadChart

The road to the final: the two finalists as large round marks with their names at the top, each one's matches on its side (the round mark of both teams with the score between them, and FT, AET or the shoot-out under it), the round names down the middle, and the group stage as a compact row of small marks, codes and scores. A finalist is a team (name, abbr, logo for the flag or the crest, color) with its path, the matches before the final, the earliest first, and optionally its group. A match is { round, opponent, score: [for, against], decidedBy, pens }; a round is a key (quarter-final) with words in English and Japanese, or any key you name in labels, so a series (First round, 4-2) draws as well as a knockout. A team with no logo is drawn as its initials on its colour. The side that won each match is brighter; the sizes are card (1080 by 1350), square, wide (1200 by 675) and phone (390 wide). table() lists every match as text.

Example

import { roadToFinal, type RoadFinalist } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const owls: RoadFinalist = { name: "Northern Owls", abbr: "NOW", color: "#3a2d6b", path: [
  { round: "quarter-final", opponent: { name: "Coastal Pines", abbr: "CPN" }, score: [2, 0] },
  { round: "semi-final", opponent: { name: "Valley Stars", abbr: "VAL" }, score: [1, 1], decidedBy: "pens", pens: [4, 3] },
] };
const gulls: RoadFinalist = { name: "Harbour City Gulls", abbr: "HCG", color: "#1d4f91", path: [
  { round: "quarter-final", opponent: { name: "Summit Peaks", abbr: "SMT" }, score: [3, 1] },
  { round: "semi-final", opponent: { name: "Metro Bolts", abbr: "MTB" }, score: [2, 1], decidedBy: "aet" },
] };
const chart = roadToFinal([owls, gulls], { title: "Northern Cup 2030", size: "square" });
console.log(chart.totals.rounds.join(" "), chart.draw().startsWith("<svg"), chart.table().includes("Semi-final"));
// quarter-final semi-final true true

type RoadTotals

interface RoadTotals { finalists: [RoadRecord, RoadRecord]; /** The rounds of the chart, the earliest first, as keys. */ rounds: string[]; /** The final has a score. */ played: boolean; /** The winner of the final, or null when it has not been played or is level. */ champion: string | null; }

The numbers behind a road chart.

type SoccerChart

interface SoccerChart extends Chart<SoccerTotals> { /** The goal-mouth view of the shots that have `goalY` and `goalZ`: a small drawing of the goal frame, seen from the front, with where each shot crossed the goal line. */ goalMouth(options?: GoalMouthOptions): GoalMouth; }

A soccer shot map: the chart, and the goal-mouth view of the same shots.

function soccerGoalMouth

soccerGoalMouth(shots: readonly SoccerShot[], options?: GoalMouthOptions): GoalMouth

The goal-mouth view of soccer shots: a small drawing of the goal frame seen from the front (the shooter's left on the left), with where each shot crossed the goal line, across from the centre of the goal (goalY, positive to the shooter's right) and up from the ground (goalZ). A shot wide or high of the frame is drawn beside or above it; a block has no place across the goal and is left out, and the count says how many were.

Example

import { soccerGoalMouth } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const view = soccerGoalMouth([
  { x: 100, y: 40, outcome: "goal", goalY: 0.8, goalZ: 1.9, xg: 0.2 },
  { x: 90, y: 40, outcome: "missed", goalY: -4.6, goalZ: 1.2 },
  { x: 85, y: 30, outcome: "blocked" },
]);
console.log(view.placed, view.skipped, view.svg.includes("Goal mouth"));
// 2 1 true

type SoccerShot

interface SoccerShot { /** Your id for the shot, written on its mark as `data-mark-id`. */ id?: string | number; x: number | null; y: number | null; /** `goal`, `saved`, `blocked`, `missed` or `post`. */ outcome?: "goal" | "saved" | "blocked" | "missed" | "post" | (string & {}); /** True for a goal, in place of `outcome`. */ goal?: boolean; /** Expected goals, 0 to 1. */ xg?: number | null; /** Where the shot crosse…

A shot. The place is in the provider's frame; goalY and goalZ say where it crossed the goal line.

function soccerShotMap

soccerShotMap(shots: readonly SoccerShot[], options?: SoccerShotOptions): SoccerChart & { key: ReturnType<typeof keyItems>; }

A shot map for soccer: each shot on the pitch (all of it, or the half the shots attack) in a data provider's own numbers, sized by its expected goals, a goal a filled star and the rest open marks (a save a circle, a miss an x, a block a triangle, the post a diamond), with the expected-goals total, a key and a table. goalMouth() of the result is the goal-mouth view of the same shots.

Example

import { soccerShotMap } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = soccerShotMap(
  [
    { x: 104, y: 36, outcome: "goal", xg: 0.31, goalY: -1.2, goalZ: 0.6 },
    { x: 98, y: 50, outcome: "saved", xg: 0.08, goalY: 2.4, goalZ: 1.1 },
    { x: 90, y: 30, outcome: "missed", xg: 0.04, goalY: 5, goalZ: 0.5 },
  ],
  { provider: "statsbomb", half: true, goalUnit: "yd" },
);
console.log(chart.totals.shots, chart.totals.goals, chart.totals.xg, chart.options.half);
console.log(chart.goalMouth().svg.startsWith("<svg"), chart.goalMouth().placed);
// 3 1 0.43 right
// true 3

type SoccerShotOptions

interface SoccerShotOptions extends ChartOptions { /** The provider the shots are in (`opta`, `statsbomb`, `wyscout`, ...): the pitch is drawn in its numbers and the marks use them. Or a `Frame` of your own, for a pitch drawn as usual. `international` (metres) by default. */ provider?: SoccerProviderId | Frame; /** Show only the half the shots attack: `true` for the right half (the default for `attacking: "right"`),…

What soccerShotMap takes.

type SoccerTotals

interface SoccerTotals { shots: number; goals: number; /** The sum of the shots' expected goals, two decimals, or null when none has one. */ xg: number | null; skipped: number; teams: Record<string, { shots: number; goals: number; xg: number | null }>; }

The numbers behind a soccer shot map.

type SplitRow

interface SplitRow { /** The distance at which the split ends, in metres of the race. */ distance: number; /** The seconds of the split itself, and the cumulative time at its end. */ seconds: number; cumulative: number; /** Seconds per 100 m. */ pace: number; }

One split of a swimmer: the metres it covers, the seconds it took, and its pace.

function splitsChart

splitsChart(race: PoolRace, options?: SplitsOptions): Chart<SplitsTotals>

The splits of a race as a chart: pace bars in the lanes of a pool (pool.race.pace), a title, a sentence that sums it up, the numbers (totals) and an HTML table of every split for a reader who cannot see the drawing. The splits are cumulative times, one every 50 m (every 25 m in a short course pool) unless the race says another distance.

Example

import { splitsChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
const chart = splitsChart({
  distance: 100,
  swimmers: [
    { lane: 4, name: "A. Swimmer", splits: [25.1, 52.4] },
    { lane: 5, name: "B. Swimmer", splits: [25.6, 52.9] },
  ],
});
console.log(chart.totals.fastest?.name, chart.totals.fastest?.pace, chart.draw().startsWith("<svg"));
// A. Swimmer 50.2 true

type SplitsOptions

interface SplitsOptions extends ChartOptions { /** The pool: the course (`short` for 25 m), the lanes, the look of the water and the deck. */ pool?: Omit<PoolStyle, "race">; /** Write each bar's time on it. On by default. */ labels?: boolean; }

What a splits chart takes beyond a chart's own options.

type SplitsTotals

interface SplitsTotals { swimmers: number; /** The metres of the race and the metres between splits. */ distance: number; splitEvery: number; /** The swimmers' splits, in the order down the pool. */ rows: SwimmerSplits[]; /** The fastest split of the field: whose it is, which one (from 1) and its pace; undefined with no splits. */ fastest: { lane: number; name: string; split: number; seconds: number; pace: number } …

The numbers behind a splits chart.

type SwimmerSplits

interface SwimmerSplits { lane: number; name: string; splits: SplitRow[]; }

What a swimmer's splits come to.

type ZoneTotal

interface ZoneTotal { id: string; name: string; taken: number; made: number; /** The percent that went in, one decimal, or null for a zone with no shots. */ percent: number | null; }

One zone of a chart: its name, the shots in it and the share that went in.

@johnmorrisdotca/kyogijo-stadiums/layouts

Background BACKGROUND_OPACITY BACKGROUND_PATTERNS BackgroundFit backgroundMarkup BackgroundMarkup BackgroundOptions BackgroundPattern drawGrid GRID_MAX_CELLS GRID_PRESETS GridCell GridDrawing GridLegendItem GridOptions GridPreset GridPresetInfo KYOGIJO_BACKGROUND_STYLE KYOGIJO_GRID_STYLE laidOn resolveBackground

type Background

type Background = BackgroundPattern | "none" | BackgroundOptions;

A ground: a pattern's name, "none", or the options in full.

const BACKGROUND_OPACITY

BACKGROUND_OPACITY: Readonly<Record<BackgroundPattern, number>>

The opacity each pattern is drawn at when opacity says nothing.

const BACKGROUND_PATTERNS

BACKGROUND_PATTERNS: readonly BackgroundPattern[]

The patterns, in the order a menu lists them.

type BackgroundFit

type BackgroundFit = "cover" | "contain" | "stretch";

How a photograph fills the ground: cover fills it and cuts what overhangs, contain shows all of it, stretch fills it out of shape.

function backgroundMarkup

backgroundMarkup(background: Background | undefined, box: { x: number; y: number; w: number; h: number; }, id: string): BackgroundMarkup

The ground as SVG: a rectangle box in the drawing's own numbers, filled with the colour, the photograph and the pattern, in that order, and the definitions they need (a pattern and, for a photograph that overhangs, a clip), with ids that begin with id. The tile is a fortyeighth of the box's longer side, times scale, so that a ground looks the same on a rink in feet and on a card in pixels. An empty string for each when there is no ground.

type BackgroundMarkup

interface BackgroundMarkup { defs: string; shapes: string; }

What a ground is made of: the pieces to write in the drawing's <defs> and the shapes to lay over the apron.

type BackgroundOptions

interface BackgroundOptions { /** A pattern drawn over the ground, in a low opacity. */ pattern?: BackgroundPattern; /** A colour under the pattern and the photograph (a hex colour, `rgb()`, `hsl()`, a name or `var(--name)`). Without it the ground keeps its own colour. */ color?: string; /** The opacity of the pattern, 0 to 1. Each pattern has its own subtle one. */ opacity?: number; /** The size of the pattern's ti…

A ground in full. Every part is optional; with none, there is no ground.

type BackgroundPattern

type BackgroundPattern = "felt" | "grass" | "wood" | "ice" | "paper" | "grid" | "dots";

The patterns: a tile of shapes repeated over the ground.

function drawGrid

drawGrid(cells: readonly (GridCell | GridDrawing)[], options?: GridOptions): string

Many drawings in one SVG, as small multiples: one per park, per period or per player, in columns, under a title and a subtitle and over a credit line, with a key they share. A cell is the options of drawKyogijo, a drawing's SVG, a chart, or { drawing, label }. Every drawing keeps its own title and description for a screen reader, with ids of its own, so that the same drawing may appear twice; the group has a title and a description made from them. Each is drawn at the largest size its cell allows, the last row centred. preset fits a social card (card, 1200 by 675; square, 1080 by 1080) or a phone (phone, 390 wide in one column); without a size the grid is 960 wide and as tall as the drawings need. The scene and every renderer read it like any other drawing. The same cells and options always give the same string.

Example

import { drawGrid } from "@johnmorrisdotca/kyogijo-stadiums/layouts";
const svg = drawGrid(
  [{ drawing: { sport: "nhl" }, label: "Rink" }, { drawing: { sport: "nba" }, label: "Court" }],
  { columns: 2, title: "Two surfaces", endnote: "Drawn by Kyogijo" },
);
console.log(svg.startsWith("<svg"), svg.includes("Two surfaces"));
// true true

const GRID_MAX_CELLS

GRID_MAX_CELLS: 200

The most drawings a grid takes.

const GRID_PRESETS

GRID_PRESETS: Readonly<Record<GridPreset, GridPresetInfo>>

The presets of drawGrid: card is 1200 by 675 (the 16:9 card that social sites show for a link), square is 1080 by 1080, and phone is 390 wide in one column with the height the drawings need. A width, a height or columns in the options wins over the preset's.

type GridCell

interface GridCell { drawing: GridDrawing; /** A caption over the drawing (one line, made smaller to fit). The drawing keeps its own title and description for a screen reader. */ label?: string; }

A drawing and the caption written over it. A cell may also be the drawing alone.

type GridDrawing

type GridDrawing = string | SurfaceOptions | ScoreOptions | { draw(): string };

What a cell holds: the options of drawKyogijo, a drawing's SVG, or a chart (anything with a draw() that returns an SVG).

type GridLegendItem

interface GridLegendItem { /** What the mark means. */ label: string; /** The mark's colour (`safeColour`); the layers' own when missing. */ color?: string; /** The mark's shape. A circle by default. */ shape?: MarkShape; /** Drawn open, as a miss or a save is. */ hollow?: boolean; }

One mark of the key: what it means, in what colour and shape.

type GridOptions

interface GridOptions { /** A size to fit: `card`, `square` or `phone` (see `GRID_PRESETS`). */ preset?: GridPreset; /** The drawings in a row. By default the number that makes the drawings largest in a fixed size, else as many as fit with a cell at least 280 wide. */ columns?: number; /** The space between cells, in the grid's units. About a sixtieth of the width by default. */ gap?: number; /** The width of the pi…

What drawGrid takes.

type GridPreset

type GridPreset = "card" | "square" | "phone";

The sizes a grid is made for: a social card, a square post and a phone's width.

type GridPresetInfo

interface GridPresetInfo { readonly width: number; readonly height: number | undefined; readonly columns: number | undefined; /** The ground behind the grid when `background` says nothing: a solid one for a picture that will be posted, none for a phone's page. */ readonly ground: boolean; readonly about: string; }

What a preset fixes: the size in pixels (a height of undefined is whatever the drawings need), the columns, and whether the page has a ground of its own.

const KYOGIJO_BACKGROUND_STYLE

KYOGIJO_BACKGROUND_STYLE: string

The rules of a ground, written into a drawing that has one (and in KYOGIJO_STYLE, for a page that supplies the stylesheet): the pattern's ink and light.

const KYOGIJO_GRID_STYLE

KYOGIJO_GRID_STYLE: string

The rules of a grid of drawings (drawGrid): the ground colour and the quiet ink of the subtitle and the credit line in light and dark, and the classes of the title, the subtitle, a cell's label, the key and the credit line. A grid is a .kyogijo as well, so the base rules give it the ink, the font and the colours of the layers; KYOGIJO_STYLE has these for a page that draws with style: "none".

function laidOn

laidOn(base: string, background: Background | undefined, id: string): { base: string; defs: string; }

A drawing's base with a ground laid on its apron: the base's first shape is the apron's rectangle, and the ground is written right after it, over exactly the same rectangle, so that it lies under the surface and over the apron's colour. defs is what the ground needs in the drawing's <defs>. A base that does not begin with a rectangle, or a ground with nothing in it, leaves the base as it is.

function resolveBackground

resolveBackground(value: unknown): BackgroundOptions | undefined

The options of a ground, read: a name becomes a pattern, "none", anything that is not a ground and a ground with nothing in it are undefined.

@johnmorrisdotca/kyogijo-stadiums/replay

ActiveEvent activeEvents alongPool createReplay describeEvent distanceAt drawFrame eventsBetween formatTime interpolate parseTime possession Possession Progress progressOf RaceReplayOptions raceTimeline REPLAY_SPEED_RANGE REPLAY_SPEEDS ReplayChange replayClock ReplayController ReplayEvent ReplayEventKind replayEvents ReplayFrame replayInfo replayLayers ReplayLook ReplayObject ReplayReason ReplaySettings replaySnapshot ReplaySnapshot ReplayState replaySummary replayTable ReplayTeam ReplayTimeline replayTimes ReplayTrail trails

type ActiveEvent

interface ActiveEvent { /** Its place in the timeline's `events`. */ index: number; event: ReplayEvent; x?: number; y?: number; x2?: number; y2?: number; }

An event that is on the surface now, with its places worked out.

function activeEvents

activeEvents(timeline: ReplayTimeline, t: number, hold?: number, smooth?: boolean | number): ActiveEvent[]

The events that are on the surface at a time: from the moment of each until hold seconds after it ends (3 by default), with their places worked out.

Example

import { activeEvents } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const ball = { id: "ball", team: "ball" as const, x: 10, y: 10 };
const timeline = { frames: [{ t: 0, objects: [ball] }, { t: 10, objects: [ball] }], events: [{ t: 4, kind: "goal", team: "home" as const }] };
console.log(activeEvents(timeline, 3).length, activeEvents(timeline, 5).map((a) => a.event.kind).join(), activeEvents(timeline, 9).length);
// 0 goal 0

function alongPool

alongPool(d: number, length: number): number

The place along the pool, in metres from the starting wall, of a swimmer who has swum d metres in a pool of length: down the first length, back the next.

function createReplay

createReplay(timeline: ReplayTimeline, settings?: ReplaySettings): ReplayController

The controller of a replay: a state machine over a timeline's clock. It plays, pauses, seeks, steps by a frame, changes speed, loops, and says which events went by in each move so that they can be announced. It never reads a clock or the page: advance(seconds) is how time passes.

Example

import { createReplay } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const frames = [0, 1, 2, 3, 4].map((t) => ({ t, objects: [{ id: "ball", team: "ball" as const, x: t * 10, y: 0 }] }));
const replay = createReplay({ frames, events: [{ t: 2, kind: "goal" }] }, { speed: 2 });
replay.play();
replay.advance(1.5);
console.log(replay.state().t, replay.state().playing);
replay.advance(5);
console.log(replay.state().t, replay.state().playing, replay.state().ended);
// 3 true
// 4 false true

function describeEvent

describeEvent(event: ReplayEvent, language?: Language, start?: number): string

What is said of an event, for a live region and a tooltip: its label, else the word for its kind and its team, then the time.

Example

import { describeEvent } from "@johnmorrisdotca/kyogijo-stadiums/replay";
console.log(describeEvent({ t: 72, kind: "goal", team: "home" }));
console.log(describeEvent({ t: 72, kind: "goal", team: "home" }, "ja"));
// Goal, home, 1:12
// ホームのゴール、1分12秒

function distanceAt

distanceAt(progress: Progress, t: number): number

The metres a swimmer has swum t seconds after the signal, by straight lines between the points of progress; before the reaction it is 0, after the last it stays.

function drawFrame

drawFrame(options: SurfaceOptions | ScoreOptions, timeline: ReplayTimeline, t: number, look?: ReplayLook): string

One frame of a replay as an SVG string: the drawing drawKyogijo makes of options, with the players, their numbers, the ball and its trail, and the events that are on the surface at t, laid on it as data layers. The same options, timeline and time always give the same string. Without an id the drawing's ids come from the options and the timeline, not from the time, so that every frame of a replay has the same ids.

Example

import { drawFrame } from "@johnmorrisdotca/kyogijo-stadiums/replay";
import type { ReplayTimeline } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const timeline: ReplayTimeline = {
  from: "nhl-api",
  frames: [0, 1, 2].map((t) => ({ t, objects: [
    { id: "h1", team: "home", number: 9, x: 10 + t * 12, y: 5 },
    { id: "a1", team: "away", number: 4, x: 40 - t * 5, y: -8 },
    { id: "puck", team: "puck", x: 10 + t * 12, y: 5 },
  ] })),
  events: [{ t: 2, kind: "goal", team: "home", x: 34, y: 5 }],
};
const svg = drawFrame({ sport: "nhl" }, timeline, 1.5);
console.log(svg.startsWith("<svg"), svg === drawFrame({ sport: "nhl" }, timeline, 1.5), svg.includes("Shirt numbers"));
// true true true

function eventsBetween

eventsBetween(timeline: ReplayTimeline, from: number, to: number): ReplayEvent[]

The events with a time in (from, to], for the live region: what went by while the clock moved from from to to.

function formatTime

formatTime(seconds: number): string

A time in seconds as swimming writes it, to the hundredth: 26.12, 1:46.91, 14:31.02. A time that rounds to the next minute carries into it.

Example

import { formatTime } from "@johnmorrisdotca/kyogijo-stadiums/replay";
console.log(formatTime(26.121), formatTime(106.9), formatTime(59.996));
// 26.12 1:46.90 1:00.00

function interpolate

interpolate(timeline: ReplayTimeline, t: number, options?: { smooth?: boolean | number; }): ReplayObject[]

Where everything is at a time: each object of the frame at or before t placed between that frame and the next, in a straight line or, with smooth, along a curve through the frames. An object in both frames moves; one only in the earlier frame stays put until the next; one only in the later frame appears when its frame comes. t is held to the first and last moments. The objects keep the order of their first appearance.

Example

import { interpolate } from "@johnmorrisdotca/kyogijo-stadiums/replay";
import type { ReplayTimeline } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const timeline: ReplayTimeline = { frames: [
  { t: 0, objects: [{ id: "ball", team: "ball", x: 100, y: 40 }] },
  { t: 2, objects: [{ id: "ball", team: "ball", x: 140, y: 20 }] },
] };
console.log(interpolate(timeline, 0.5).map((o) => `${o.id} ${o.x} ${o.y}`).join());
// ball 110 35

function parseTime

parseTime(value: unknown): number | undefined

A time in seconds from a number or from text: 26.12, "26.12", "1:46.91". undefined when it is neither.

function possession

possession(timeline: ReplayTimeline, t: number): Possession | undefined

Who has the ball at a time, when the data says: the possession of the latest frame at or before t that has one. undefined when no frame says, or when the latest says nobody does (null).

Example

import { possession } from "@johnmorrisdotca/kyogijo-stadiums/replay";
import type { ReplayTimeline } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const timeline: ReplayTimeline = { frames: [
  { t: 0, possession: "h7", objects: [{ id: "h7", team: "home", x: 1, y: 1 }, { id: "ball", team: "ball", x: 1, y: 1 }] },
  { t: 1, possession: null, objects: [{ id: "h7", team: "home", x: 2, y: 1 }, { id: "ball", team: "ball", x: 5, y: 1 }] },
] };
console.log(possession(timeline, 0.5), possession(timeline, 1));
// { holder: 'h7', team: 'home' } undefined

type Possession

interface Possession { holder?: string; team?: "home" | "away"; }

Who has the ball, when the data says.

type Progress

interface Progress { /** Distance in metres against the seconds after the signal; the first point is the reaction time at 0 m. */ points: [number, number][]; /** Whether the swimmer is a swimmer in the water at all (a swimmer who did not start stays on the block). */ swims: boolean; }

The marks of a swimmer's progress: the seconds after the start signal at which the swimmer has swum each distance, from the splits (and the final time).

function progressOf

progressOf(row: RaceRow, distance: number, every: number, reaction: number): Progress

The progress of one row over a race of distance metres with a split every every metres.

type RaceReplayOptions

interface RaceReplayOptions { /** Frames a second. 10 by default. */ fps?: number; /** Seconds of waiting before the start signal and of staying on after the last touch. 1.5 and 3 by default. */ lead?: number; tail?: number; /** The language of the events' words. English by default. */ language?: Language; /** The reaction time of a swimmer that has none, in seconds. 0.7 by default. */ reaction?: number; /** How far…

What a race replay is made with.

function raceTimeline

raceTimeline(options: Pick<SurfaceOptions, "pool">, replay?: RaceReplayOptions): ReplayTimeline & { events: ReplayEvent[]; }

The replay of a race by lane. Give the options of a swimming drawing ({ pool: { race } }, with a course and lanes if they are not the 50 m pool's of eight): each swimmer with splits is played back from them. The timeline is in the pool's frame (from: "swimming") in metres; its events are the turns at the walls and the finish, each with the swimmer's name and, at the finish, the time.

Example

import { raceTimeline, drawFrame } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const pool = { race: { distance: 100, swimmers: [{ lane: 4, name: "A. Swimmer", splits: [26, 54.2] }, { lane: 5, name: "B. Swimmer", splits: [26.4, 55] }] } };
const timeline = raceTimeline({ pool });
console.log(timeline.frames.length > 100, timeline.events.map((e) => e.kind).join(), drawFrame({ sport: "swimming", pool }, timeline, 20).startsWith("<svg"));
// true start,turn,turn,finish,finish true

const REPLAY_SPEED_RANGE

REPLAY_SPEED_RANGE: readonly [number, number]

The slowest and the fastest speed of play.

const REPLAY_SPEEDS

REPLAY_SPEEDS: readonly number[]

The speeds the controls offer. Any speed from the least to the greatest is allowed.

type ReplayChange

interface ReplayChange { state: Readonly<ReplayState>; reason: ReplayReason; /** The events that went by in this move (moving forward only), for the live region. */ crossed: readonly ReplayEvent[]; }

What a listener of the controller is told.

function replayClock

replayClock(seconds: number): string

Seconds as a clock, 0:12 or 1:05:09.

type ReplayController

interface ReplayController { /** A copy of the state now. */ state(): Readonly<ReplayState>; /** Start playing, from the start if it stopped at the end. In step mode, play moves one step and never plays by itself. */ play(): void; pause(): void; toggle(): void; /** Go to a time, in seconds on the timeline's clock; held to the first and last moments. */ seek(t: number): void; /** Pause and go to the next frame (1) or…

What createReplay gives back.

type ReplayEvent

interface ReplayEvent { /** Seconds, on the same clock as the frames. */ t: number; kind: ReplayEventKind; /** The team it belongs to; it colours the mark. */ team?: "home" | "away"; /** Where it began, in the timeline's frame; else the place of the object `by` at `t`; else the ball's or the puck's at `t`. */ x?: number; y?: number; /** Where it ended (a pass's receiver, a shot's end), in the timeline's frame; else …

Something that happened at a time: a pass, a shot, a goal, a whistle.

type ReplayEventKind

type ReplayEventKind = "pass" | "shot" | "goal" | "whistle" | (string & {});

The standard kinds of event. Any other word is an event of your own; its label is what is said.

function replayEvents

replayEvents(timeline: ReplayTimeline): readonly ReplayEvent[]

The events of a timeline, in time order.

type ReplayFrame

interface ReplayFrame { /** Seconds. The frames of a timeline run forward in time (they are sorted if they do not). */ t: number; objects: readonly ReplayObject[]; /** * Who has the ball or the puck, when the data says: the `id` of an object, or `home` or `away` for a team without a named player. * `null` says nobody does (a loose ball, a pass in flight). It holds until a later frame says otherwise. */ possession?: …

The positions at one time.

function replayInfo

replayInfo(timeline: ReplayTimeline): { start: number; end: number; duration: number; frames: number; objects: number; events: number; step: number; }

The first and last moments of a timeline, how long it is, how many frames and objects it has, and the usual gap between frames, all in seconds.

function replayLayers

replayLayers(timeline: ReplayTimeline, t: number, look?: ReplayLook, language?: Language): Layer[]

The data layers of one frame, to put in a drawing's layers (after your own): the trails, the events' marks, the players with their numbers, the ball.

type ReplayLook

interface ReplayLook { /** Seconds of trail behind the objects named in `trailOf`; 0 for none. 2 by default. */ trail?: number; /** Whose trail is drawn. The ball and the puck by default. */ trailOf?: readonly ReplayTeam[]; /** Round the corners of the path between frames: `true` for a Catmull-Rom curve through the frames, or a share from 0 (straight lines, the default) to 1. */ smooth?: boolean | number; /** Second…

How a frame is drawn.

type ReplayObject

interface ReplayObject { /** Who it is, the same in every frame it is in: `h7`, `ball`. */ id: string; team: ReplayTeam; /** Where it is, in the timeline's frame (`from`) and unit. */ x: number; y: number; /** A name for the tooltip: "Rivera". */ label?: string; /** A shirt number, written on the mark of a player. */ number?: number | string; }

One tracked object in one frame of the data.

type ReplayReason

type ReplayReason = "play" | "pause" | "seek" | "step" | "tick" | "speed" | "loop" | "motion";

Why the controller told its listeners.

type ReplaySettings

interface ReplaySettings extends ReplayLook { /** The speed of play: 0.25 to 4. 1 by default. */ speed?: number; /** Start again at the end. */ loop?: boolean; /** Play when the replay is attached. Ignored for a reader who prefers reduced motion, and while the page is hidden. */ autoplay?: boolean; /** The bar of controls under the drawing (play, step, speed, loop, position). On by default; the keyboard works withou…

What the controller and the controls can be told, over how a frame is drawn.

function replaySnapshot

replaySnapshot(timeline: ReplayTimeline, t: number, look?: ReplayLook): ReplaySnapshot

What is on the surface at a time: the objects, the trails behind the ones look.trailOf names, and the events.

type ReplaySnapshot

interface ReplaySnapshot { t: number; objects: ReplayObject[]; trails: ReplayTrail[]; events: ActiveEvent[]; }

The same frame of a timeline always draws the same layers: this is what a frame is, before it is drawn.

type ReplayState

interface ReplayState { /** Seconds on the timeline's clock. */ t: number; /** The first and last moments of the timeline. */ start: number; end: number; playing: boolean; /** Stopped at the end (and not looping). */ ended: boolean; speed: number; loop: boolean; /** `step` for a reader who prefers reduced motion: play moves a step, never by itself. */ motion: "smooth" | "step"; }

What the controller is doing.

function replaySummary

replaySummary(timeline: ReplayTimeline, language?: Language): string

The sentence a replay adds to the drawing's description: its length, what it tracks and how many events it holds. It does not change with the time.

function replayTable

replayTable(timeline: ReplayTimeline, options?: { language?: Language; className?: string; }): string

The events of a timeline as an HTML table, for a reader who cannot see the drawing: the time, the kind, the team and what is said of it. Every cell is escaped.

Example

import { replayTable } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const html = replayTable({ frames: [{ t: 0, objects: [] }], events: [{ t: 12, kind: "goal", team: "home", label: "Goal, Rivera" }] });
console.log(html.includes("<td>0:12</td>"), html.includes("Goal, Rivera"));
// true true

type ReplayTeam

type ReplayTeam = "home" | "away" | "ball" | "puck" | "official";

What a tracked object is. home and away are players; the rest are drawn smaller and in the layer's plain colours.

type ReplayTimeline

interface ReplayTimeline { /** The positions, from the first moment to the last. */ frames: readonly ReplayFrame[]; /** What happened, by time. */ events?: readonly ReplayEvent[]; /** The frame the positions are in (a name in `FRAMES`, or a `Frame`), as for a layer. Without it they are the surface's own: feet from the top left of the surface as drawn wide. */ from?: Frame | string; /** The unit of the positions, as …

Tracking data over time on a surface. Do not change a timeline after it has been used: make a new one.

function replayTimes

replayTimes(timeline: ReplayTimeline): readonly number[]

The times of the frames, and of the events, in order: the moments a step can land on.

type ReplayTrail

interface ReplayTrail { id: string; team: ReplayTeam; /** The places over the last seconds, oldest first, the last the object's place now. */ points: [number, number][]; }

A tracked object at a moment, and the trail behind it.

function trails

trails(timeline: ReplayTimeline, t: number, seconds: number, options?: { teams?: readonly ReplayTeam[]; ids?: readonly string[]; smooth?: boolean | number; }): ReplayTrail[]

The path each object took over the last seconds before t, oldest place first and ending at its place at t: the frames in between, and the places at the two ends. Only objects that are there at t have a trail. teams and ids choose whose; all by default.

Example

import { trails } from "@johnmorrisdotca/kyogijo-stadiums/replay";
import type { ReplayTimeline } from "@johnmorrisdotca/kyogijo-stadiums/replay";
const timeline: ReplayTimeline = { frames: [0, 1, 2, 3].map((t) => ({ t, objects: [{ id: "puck", team: "puck", x: t * 10, y: 0 }] })) };
console.log(trails(timeline, 3, 2)[0].points.map((point) => point[0]).join());
// 10,20,30

@johnmorrisdotca/kyogijo-stadiums/scene

FlatOptions Matrix Paint parseSvg PathCommand pathCommands Scene SceneBase SceneCircle SceneClip SceneEllipse sceneFromSvg SceneGradient SceneImage SceneItem SceneLine sceneOf SceneOptions ScenePath ScenePattern ScenePoly SceneRect SceneShape SceneSource SceneStyle SceneText sceneToSvg SvgNode writeSvg

type FlatOptions

interface FlatOptions extends SceneOptions { /** The width of the picture in pixels. Without a width or a height the SVG has neither and takes the size of its place. */ width?: number; height?: number; /** A colour to fill the picture with first (a PNG has no page behind it). */ background?: string; }

What a flat SVG is written with.

type Matrix

type Matrix = readonly [number, number, number, number, number, number];

An affine transform as the six numbers of an SVG matrix(a b c d e f): x' = a x + c y + e, y' = b x + d y + f.

type Paint

type Paint = string | { readonly gradient: string } | { readonly pattern: string };

A colour in CSS (#3f8f55, rgba(255,255,255,.92), a name), none, or a reference to a gradient or a pattern of the scene.

function parseSvg

parseSvg(text: string): SvgNode

Read an SVG string into a tree. Anything that is not the markup this package writes (an unclosed tag, an unquoted attribute) is an error that says where.

Example

import { parseSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
const tree = parseSvg('<svg viewBox="0 0 10 5"><rect x="1" y="1" width="3" height="2"/></svg>');
console.log(tree.name, tree.attrs.viewBox, tree.children.length);
// svg 0 0 10 5 1

type PathCommand

type PathCommand = readonly ["M", number, number] | readonly ["L", number, number] | readonly ["C", number, number, number, number, number, number] | readonly ["Q", number, number, number, number] | readonly ["Z"];

A path segment in absolute coordinates. An arc has been turned into cubic curves, so a renderer needs only these five.

function pathCommands

pathCommands(d: string): PathCommand[]

Path data as absolute segments: M, L, C, Q and Z. Relative commands, H, V, S and T are worked out, and an arc is cubic curves.

Example

import { pathCommands } from "@johnmorrisdotca/kyogijo-stadiums/scene";
console.log(pathCommands("M0 0h10v5H0Z").map((c) => c.join(" ")));
// [ 'M 0 0', 'L 10 0', 'L 10 5', 'L 0 5', 'Z' ]

type Scene

interface Scene { /** The theme the colours were worked out for. */ theme: "light" | "dark"; /** The sport the drawing is of, when it says. */ sport?: string; /** The drawing's id: the prefix of the ids a renderer writes, so two scenes on one page do not share one. */ id: string; title: string; description: string; /** `portrait` when the surface lies tall: `turn` is then the quarter turn that laid it down, in the f…

A drawing as data.

type SceneBase

interface SceneBase { /** The element's class, as written. */ class: string; /** The element's id, when it has one. */ id?: string; /** The `data-*` attributes of the element and the groups it is in, without the `data-` (the nearest wins). */ data?: Readonly<Record<string, string>>; /** The element's `<title>`, a tooltip. */ label?: string; /** The drawing's frame to the shape's: everything above it, and its own `tr…

What every shape has.

type SceneCircle

interface SceneCircle extends SceneBase { kind: "circle"; cx: number; cy: number; r: number; }

type SceneClip

interface SceneClip { id: string; /** The frame of the group the clip was set on, to the drawing's. */ matrix: Matrix; shapes: readonly SceneShape[]; }

A region shapes are cut to: the shapes of a <clipPath>, in the frame of the group that was clipped.

type SceneEllipse

interface SceneEllipse extends SceneBase { kind: "ellipse"; cx: number; cy: number; rx: number; ry: number; }

function sceneFromSvg

sceneFromSvg(svg: string, options?: SceneOptions): Scene

A drawing the package wrote, as a scene. theme works the colours out for light or dark: without it, the drawing's own data-theme, else light. A drawing made with style: "none" is read with the package's own stylesheet (KYOGIJO_STYLE); css adds a page's rules after the drawing's.

Example

import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
import { sceneFromSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
const scene = sceneFromSvg(drawKyogijo({ sport: "nhl", id: "rink" }), { theme: "dark" });
console.log(scene.theme, scene.items[0].kind, scene.items[0].style.fill);
// dark rect #1a2025

type SceneGradient

interface SceneGradient { id: string; /** In the frame of the shape painted with it. */ x1: number; y1: number; x2: number; y2: number; stops: readonly { offset: number; color: string; opacity: number }[]; }

type SceneImage

interface SceneImage extends SceneBase { kind: "image"; href: string; x: number; y: number; width: number; height: number; preserveAspectRatio?: string; }

type SceneItem

type SceneItem = SceneShape | SceneText | SceneImage;

type SceneLine

interface SceneLine extends SceneBase { kind: "line"; x1: number; y1: number; x2: number; y2: number; }

function sceneOf

sceneOf(source: SurfaceOptions | ScoreOptions | string | Scene, options?: SceneOptions): Scene

A drawing as plain data: the options of drawKyogijo (or the SVG of a chart or a replay frame, or a scene already made) as the shapes it is painted with, in order, each with its colours worked out for light or dark.

Example

import { sceneOf } from "@johnmorrisdotca/kyogijo-stadiums/scene";
const scene = sceneOf({ sport: "nhl", id: "rink" });
console.log(scene.viewBox, scene.items.length > 30, scene.items[0].kind);
// { x: -8, y: -8, w: 216, h: 101 } true rect

type SceneOptions

interface SceneOptions { /** The theme to work the colours out for. Without one, the drawing's own `data-theme`, else light. */ theme?: "light" | "dark"; /** More rules, read after the drawing's own: a page's stylesheet for the classes you add. */ css?: string; }

What a scene is made with.

type ScenePath

interface ScenePath extends SceneBase { kind: "path"; d: string; commands: readonly PathCommand[]; }

A path: d as written, and the same as absolute segments (lines, curves, arcs as curves).

type ScenePattern

interface ScenePattern { id: string; width: number; height: number; shapes: readonly SceneShape[]; }

A tile of shapes, repeated over width by height in the frame of the shape painted with it.

type ScenePoly

interface ScenePoly extends SceneBase { kind: "polygon" | "polyline"; points: readonly (readonly [number, number])[]; }

A closed (polygon) or open (polyline) run of points.

type SceneRect

interface SceneRect extends SceneBase { kind: "rect"; x: number; y: number; width: number; height: number; rx: number; }

A rectangle, with rounded corners when rx is more than 0.

type SceneShape

type SceneShape = SceneRect | SceneCircle | SceneEllipse | SceneLine | ScenePoly | ScenePath;

A shape that is outlined and filled: everything but text and images.

type SceneSource

type SceneSource = SurfaceOptions | ScoreOptions | string | Scene;

A scene's source: the options of drawKyogijo, an SVG the package wrote, or a scene.

type SceneStyle

interface SceneStyle { fill: Paint; stroke: Paint; strokeWidth: number; /** The shape's own opacity multiplied by those of the groups it is in (so overlapping parts of one group are not blended as a group would be). */ opacity: number; fillOpacity: number; strokeOpacity: number; lineJoin: "miter" | "round" | "bevel"; lineCap: "butt" | "round" | "square"; /** The dash lengths, or none for a solid line. */ dash?: read…

What a shape is painted with. Every field is a number, a colour or a keyword: nothing is left to a stylesheet.

type SceneText

interface SceneText extends SceneBase { kind: "text"; x: number; y: number; text: string; fontSize: number; fontWeight: number; fontFamily: string; anchor: "start" | "middle" | "end"; letterSpacing: number; }

function sceneToSvg

sceneToSvg(source: SceneSource, options?: FlatOptions): string

A drawing as an SVG that needs no stylesheet: every colour, width and font worked out for the theme, so that a tool that does not read CSS variables (an image converter, an email, a slide) draws what a browser does. Light by default, or the drawing's own data-theme.

Example

import { sceneToSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
const svg = sceneToSvg({ sport: "nhl", id: "rink" }, { theme: "dark", width: 800 });
console.log(svg.includes("var("), svg.includes("<style"), svg.startsWith("<svg"));
// false false true

type SvgNode

interface SvgNode { name: string; attrs: Record<string, string>; children: (SvgNode | string)[]; /** Written as `<a/>` rather than `<a></a>`. */ empty: boolean; }

An element, or a run of text. Attribute values and text are as written (escaped); attr and textOf read them.

function writeSvg

writeSvg(node: SvgNode): string

An SVG tree as text: the string parseSvg was given, exactly.

@johnmorrisdotca/kyogijo-stadiums/canvas

CanvasContext CanvasGradientLike CanvasOptions Drawn drawToCanvas withAlpha

type CanvasContext

interface CanvasContext { save(): void; restore(): void; setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; beginPath(): void; moveTo(x: number, y: number): void; lineTo(x: number, y: number): void; bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void; quadraticCurveTo(cx: number, cy: number, x: number, y: number): void; closePath(): void; f…

The members of a 2D canvas context that are used, with the types of the standard ones. A CanvasRenderingContext2D, an OffscreenCanvasRenderingContext2D and the contexts of the Node canvas libraries all have them. Members marked optional are used only when the context has them.

type CanvasGradientLike

interface CanvasGradientLike { addColorStop(offset: number, color: string): void; }

A gradient the context makes: only addColorStop is called on it.

type CanvasOptions

interface CanvasOptions extends SceneOptions { /** The width the surface is drawn at, in the context's units (pixels). With a `height` too, the drawing is fitted inside and centred. */ width?: number; height?: number; /** Pixels for one unit of the drawing (a foot, for most). Instead of a width or a height. 1 when none of the three is given. */ scale?: number; /** Where the drawing's top left corner goes. 0, 0. */ x…

Where and how large a scene is drawn, and what is not in the scene.

type Drawn

interface Drawn { /** The box the drawing took, in the context's units. */ width: number; height: number; /** Pixels for one unit of the drawing. */ scale: number; }

What drawToCanvas drew.

function drawToCanvas

drawToCanvas(ctx: CanvasContext, source: SceneSource, options?: CanvasOptions): Drawn

Draw a scene (or the options of drawKyogijo, or an SVG the package wrote) onto a canvas context. Give a width or a height in pixels, or a scale; the drawing keeps its shape. Light or dark by theme. A team's logo is drawn only if images returns a picture for its address.

Example

import { drawToCanvas, type CanvasContext } from "@johnmorrisdotca/kyogijo-stadiums/canvas";
// Any object with the context's members will do: this one only counts what is drawn.
const counts: Record<string, number> = {};
const ctx = new Proxy({} as CanvasContext, { get: (_, name: string) => () => void (counts[name] = (counts[name] ?? 0) + 1), set: () => true });
const drawn = drawToCanvas(ctx, { sport: "nhl", id: "rink" }, { width: 800 });
console.log(drawn.width, Math.round(drawn.height), counts.fill > 10);
// 800 374 true

function withAlpha

withAlpha(color: string, alpha: number): string

The colour with an alpha: rgb(...) of a hex or rgb colour, or the colour itself when it cannot be read (and the alpha is 1).

@johnmorrisdotca/kyogijo-stadiums/d3

AppendOptions appendSurface D3Selection ScaleFunction surfaceDomain SurfaceDomain

type AppendOptions

interface AppendOptions extends SceneOptions { /** A class to put on the group. `kyogijo-surface`. */ className?: string; }

What appendSurface takes besides the drawing.

function appendSurface

appendSurface(selection: D3Selection, source: SceneSource, x: ScaleFunction, y: ScaleFunction, options?: AppendOptions): D3Selection

Draw a surface into a d3 selection, where the caller's scales put it: the drawing's numbers go through x and y (linear scales), so a mark you add with the same scales lands on the right spot of the rink. Every colour, width and font is an attribute of the shape, so the page needs no stylesheet and the drawing is light or dark by theme. Returns the group it made, with a class on each shape for selecting.

Example

import { appendSurface, surfaceDomain } from "@johnmorrisdotca/kyogijo-stadiums/d3";
// With d3: appendSurface(d3.select("svg"), options, d3.scaleLinear().domain(d.x).range([0, 800]), d3.scaleLinear().domain(d.y).range([0, 374]))
const options = { sport: "nhl", id: "rink" } as const;
const made: string[] = [];
const selection = { append: (name: string) => (made.push(name), selection), attr: () => selection, text: () => selection };
const d = surfaceDomain(options);
const scale = (domain: number[], size: number) => (v: number) => ((v - domain[0]) / (domain[1] - domain[0])) * size;
appendSurface(selection, options, scale(d.x, 800), scale(d.y, 374));
console.log(made[0], made.includes("path"), made.includes("clipPath"));
// g true false

type D3Selection

interface D3Selection { append(name: string): D3Selection; attr(name: string, value: string): D3Selection; text(value: string): D3Selection; }

The members of a d3 selection that are used: append, attr and text. Every selection of d3 has them.

type ScaleFunction

type ScaleFunction = (value: number) => number;

A scale as a function of a number, which is all of d3's and Plot's scaleLinear that is used here.

function surfaceDomain

surfaceDomain(source: SceneSource, options?: SceneOptions): SurfaceDomain

The numbers to give two scales so that the drawing fills them: scaleLinear().domain(d.x).range([0, width]) and scaleLinear().domain(d.y).range([0, height]). The numbers are the drawing's own, feet from the surface's left end and its top (a tall drawing, as the curling sheet is by default, is turned: ask for orientation: "landscape" to have the surface's own).

Example

import { surfaceDomain } from "@johnmorrisdotca/kyogijo-stadiums/d3";
console.log(surfaceDomain({ sport: "nhl", id: "rink" }));
// { x: [ -8, 208 ], y: [ -8, 93 ] }

type SurfaceDomain

interface SurfaceDomain { x: [number, number]; y: [number, number]; }

The least and the greatest of x and of y in the drawing's own numbers (feet), the domains to give two scales.

@johnmorrisdotca/kyogijo-stadiums/plot

PlotContext PlotDocument PlotElement PlotScales surfaceMark SurfaceMark surfaceScales SurfaceScales

type PlotContext

interface PlotContext { document?: PlotDocument; }

What Plot gives a mark when it draws it. Only document of the context is read.

type PlotDocument

interface PlotDocument { createElementNS(namespace: string, name: string): PlotElement; }

The members of a document that are used. A browser's document has them, and so does the one Plot gives its marks in context.document.

type PlotElement

interface PlotElement { setAttribute(name: string, value: string): void; appendChild(child: PlotElement): unknown; textContent: string | null; }

type PlotScales

interface PlotScales { x: ScaleFunction; y: ScaleFunction; }

The scales Plot gives a mark: x and y are functions. (Plot's scales are also nested in scales, which is not read.)

function surfaceMark

surfaceMark(source: SceneSource, options?: SceneOptions & { className?: string; }): SurfaceMark

A surface as a mark for Observable Plot, drawn where the plot's x and y scales put the drawing's numbers, so that the marks after it land on the rink, the court or the field. Plot takes a function as a mark, so there is nothing to import; put it first in marks, with surfaceScales for the scales. Every colour and width is an attribute of the shape, so the page needs no stylesheet; light or dark by theme.

Example

import { surfaceMark, surfaceScales } from "@johnmorrisdotca/kyogijo-stadiums/plot";
// With Plot: Plot.plot({ ...surfaceScales(options), marks: [surfaceMark(options), Plot.dot(shots, { x: "x", y: "y" })] })
const options = { sport: "nhl", id: "rink" } as const;
const made: string[] = [];
const document = { createElementNS: (_: string, name: string) => { made.push(name); return { setAttribute: () => undefined, appendChild: () => undefined, textContent: null as string | null }; } };
const scale = (v: number) => (v + 8) * 4;
const node = surfaceMark(options)(0, { x: scale, y: scale }, {}, {}, { document });
console.log(made[0], made.length > 30, typeof node);
// g true object

type SurfaceMark

type SurfaceMark = (index: unknown, scales: PlotScales, values: unknown, dimensions: unknown, context: PlotContext) => PlotElement;

A mark for Plot.plot: a function that Plot calls with the scales when it renders.

function surfaceScales

surfaceScales(source: SceneSource, options?: SceneOptions): SurfaceScales

The options that give a plot the surface's scales: the drawing's numbers (feet from the surface's left end and its top, see surfaceDomain) on both axes with y growing down as in a drawing, the same size for a foot across and down, no axis and no margin. Spread them into Plot.plot and put surfaceMark first.

Example

import { surfaceScales } from "@johnmorrisdotca/kyogijo-stadiums/plot";
console.log(surfaceScales({ sport: "nhl", id: "rink" }));
// {
//   x: { domain: [ -8, 208 ], axis: null },
//   y: { domain: [ -8, 93 ], axis: null, reverse: true },
//   aspectRatio: 1,
//   margin: 0,
//   inset: 0
// }

type SurfaceScales

interface SurfaceScales { x: { domain: [number, number]; axis: null }; y: { domain: [number, number]; axis: null; reverse: true }; aspectRatio: 1; margin: 0; inset: 0; }

The options that make a plot's x and y scales the surface's: its extent, no axes, no margin and a foot as wide as it is tall. Spread them into Plot.plot.

@johnmorrisdotca/kyogijo-stadiums/geojson

Feature FeatureCollection GeoJsonOptions Geometry Position sceneToGeoJSON

type Feature

interface Feature { type: "Feature"; geometry: Geometry; properties: { /** The shape's place in the paint order, from 0. */ order: number; kind: SceneItem["kind"]; class: string; fill?: string; stroke?: string; strokeWidth?: number; opacity?: number; label?: string; text?: string; fontSize?: number; href?: string; data?: Record<string, string>; }; }

A shape of the drawing, with how it is painted.

type FeatureCollection

interface FeatureCollection { type: "FeatureCollection"; features: Feature[]; /** `[west, south, east, north]` of the numbers, which for this collection are `x` and `y`. */ bbox?: [number, number, number, number]; /** A foreign member (RFC 7946, 6.1): what the numbers are. */ kyogijo: { sport?: string; frame: string; unit: string; note: string }; }

The drawing: every shape, and what the numbers are.

type GeoJsonOptions

interface GeoJsonOptions extends SceneOptions { /** A provider's coordinate frame (`"nhl-api"`, `"opta"`, a `Frame` of your own) to write the numbers in. Without one, the surface's own: feet from its left end and its top. */ frame?: string | Frame; /** The surface's size, for a frame that is for a surface of any size (the soccer providers). */ size?: SurfaceSize; /** Leave out the shapes this returns false for (the …

What sceneToGeoJSON takes besides the drawing.

type Geometry

type Geometry = | { type: "Point"; coordinates: Position } | { type: "LineString"; coordinates: Position[] } | { type: "MultiLineString"; coordinates: Position[][] } | { type: "Polygon"; coordinates: Position[][] } | { type: "MultiPolygon"; coordinates: Position[][][] };

type Position

type Position = [number, number];

A position, a ring of them and so on, as GeoJSON has them.

function sceneToGeoJSON

sceneToGeoJSON(source: SceneSource, options?: GeoJsonOptions): FeatureCollection

A scene as a GeoJSON FeatureCollection (RFC 7946): a filled shape is a Polygon or MultiPolygon (a ring inside another is its hole, the winding as the RFC has it), a line a LineString, a label a Point with its text. The numbers are the drawing's own, feet from the surface's left end and its top (a tall drawing is turned back), or those of a provider's frame; this is a flat plane and not longitude and latitude, so give it to a tool that takes one (Leaflet's CRS.Simple, d3's geoIdentity, deck.gl's Cartesian coordinates). The painting of each shape is in its properties.

Example

import { sceneToGeoJSON } from "@johnmorrisdotca/kyogijo-stadiums/geojson";
const rink = sceneToGeoJSON({ sport: "nhl", id: "rink" }, { filter: (item) => item.class.includes("ky-surface") });
console.log(rink.type, rink.features.length, rink.features[0].geometry.type, rink.bbox);
// FeatureCollection 1 Polygon [ 0, 0, 200, 85 ]

@johnmorrisdotca/kyogijo-stadiums/png

PNG_PEER_MISSING PngOptions toPNG

const PNG_PEER_MISSING

PNG_PEER_MISSING: "toPNG needs the optional package @resvg/resvg-js: install it with `npm install @resvg/resvg-js`. It runs in Node only; in a browser draw to a canvas with drawToCanvas and call canvas.toBlob."

The message of the error thrown when the peer is not there.

type PngOptions

interface PngOptions extends SceneOptions { /** The width of the picture in pixels. 1200 when neither a width nor a height is given. */ width?: number; height?: number; /** A colour behind the drawing; the picture is transparent where there is none. */ background?: string; /** Options for resvg's font handling, passed as its `font` option (`{ fontFiles: [...], loadSystemFonts: false }`). */ font?: Record<string, unk…

What toPNG takes besides the drawing.

function toPNG

toPNG(source: SceneSource, options?: PngOptions): Promise<Uint8Array>

A drawing as PNG bytes, by resvg-js (an optional peer dependency, loaded when this is called, in Node only). The drawing may be the options of drawKyogijo, an SVG the package wrote or a scene; light or dark by theme. Without the peer installed this throws an error that says what to install.

Example

import { toPNG } from "@johnmorrisdotca/kyogijo-stadiums/png";
// Without the peer installed this stays undefined; with it, the bytes start with the PNG signature.
const png = await toPNG({ sport: "nhl", id: "rink" }, { width: 800, theme: "dark" }).catch(() => undefined);
console.log(png === undefined || (png[1] === 80 && png[2] === 78 && png[3] === 71));
// true

@johnmorrisdotca/kyogijo-stadiums/zones

betweenHashes homeRun HomeRunOptions HomeRunVerdict inCrease inPaint inPenaltyArea inRedZone inRestrictedArea inSixYardBox inSlot isFair isHomeRun isThreePointer onWarningTrack overWall PARKS_WITHOUT_WALL_HEIGHTS PlaceLike RULE_IDS RuleId wallHeightAt ZoneAnswer zoneAt zoneAtSurface zoneName ZoneOptions zoneResolver

function betweenHashes

betweenHashes: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is between the hash marks.

function homeRun

homeRun(park: string | Park, point: PlaceLike, options?: HomeRunOptions): HomeRunVerdict

Whether a ball is a home run, from the park's own fence: the fence's distance at the bearing and, when the height of the ball is given, the wall's height there (wall data of the park: the stretches of fence with their heights). A ball in fair territory that came down beyond the fence is a home run; one that was lower than the wall where it crossed struck the wall and is in play. Where the park holds no height for the bearing the answer to a question that needs it is homeRun: undefined, and wallData says whether the park has any heights at all. park is a key of PARKS (or a park); the place is in the frame of options.frame (retrosheet and mlbam are the usual) or the drawing's own.

Example

import { homeRun } from "@johnmorrisdotca/kyogijo-stadiums/zones";
// Fenway Park's Green Monster is 37 feet 2 inches high, 310 feet down the line in left.
console.log(homeRun("fenway-park", [-250, 250], { frame: "retrosheet", height: 30 }).reason, homeRun("fenway-park", [-250, 250], { frame: "retrosheet", height: 40 }).reason);
// struck-wall cleared

type HomeRunOptions

interface HomeRunOptions extends ZoneOptions { /** The height of the ball in feet as it crossed the fence's line. Without it, a ball that came down beyond the fence in fair territory is a home run. */ height?: number; }

What a caller can give about the ball.

type HomeRunVerdict

interface HomeRunVerdict { /** True: a home run. False: in play or foul. `undefined`: it cannot be told, because the ball was over the fence and its height was given but the park holds no wall height there. */ homeRun: boolean | undefined; /** The place is in fair territory. */ fair: boolean; /** How far from home plate, and the fence's distance in that direction (at the bearing), in feet. */ distance: number; fence…

What a home-run decision rests on.

function inCrease

inCrease: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in a goal crease.

function inPaint

inPaint: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in the paint (the free-throw lane, the key), outside the restricted area.

function inPenaltyArea

inPenaltyArea: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in a penalty area, at the end the team attacks or the one it defends.

function inRedZone

inRedZone: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in the red zone: inside the opponent's 20-yard line.

function inRestrictedArea

inRestrictedArea: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in the restricted area under the basket.

function inSixYardBox

inSixYardBox: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in a goal area (the six-yard box), at either end.

function inSlot

inSlot: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in the slot (the package's definition: the inner and outer slot in front of the net, between the face-off circles), at either end.

function isFair

isFair: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is in fair territory: between the foul lines, which are fair themselves.

function isHomeRun

isHomeRun: (park: string | Park, point: PlaceLike, options?: HomeRunOptions) => boolean | undefined

homeRun as a yes or no: true for a home run, false for foul, in play or the wall struck, undefined where the park's wall height is missing and the decision needs it.

function isThreePointer

isThreePointer: (point: PlaceLike, options?: ZoneOptions) => boolean

True when a shot from the place would count three: beyond the three-point line (the arc, or past the straight lines at the sidelines), at the basket the team attacks.

function onWarningTrack

onWarningTrack: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place is on the warning track.

function overWall

overWall: (point: PlaceLike, options?: ZoneOptions) => boolean

True when the place, in fair territory, lies beyond the park's fence: where a ball came down over the wall. park is a key of PARKS.

const PARKS_WITHOUT_WALL_HEIGHTS

PARKS_WITHOUT_WALL_HEIGHTS: readonly string[]

The ballparks whose wall heights the package holds nowhere in their fence: of the 94 parks, the 35 whose article gives heights have them for some stretch, and for the rest the height is not known here, so a decision that needs it is undecided (homeRun: undefined), never guessed.

type PlaceLike

type PlaceLike = readonly [number, number] | { readonly x: number; readonly y: number };

A place: [x, y] or { x, y }.

const RULE_IDS

RULE_IDS: readonly ["defensive-zone", "neutral-zone", "offensive-zone", "crease", "trapezoid", "behind-goal-line", "inner-slot", "outer-slot", "slot", "faceoff-circle", "centre-circle", "frontcourt", "backcourt", "restricted-area", "paint", "mid-range", "corner-three-left", "corner-three-right", "above-the-break", "three-point", "defensive-third", "middle-third", "attacking-third", "six-yard-box", "own-six-yard-box"…

The zones and areas that zoneAt names from a sport's rulebook, as ids. A zone set of the package's (ZONE_SETS) has ids of its own, which zoneAt passes on as they are.

type RuleId

type RuleId = (typeof RULE_IDS)[number];

The id of a zone the rules engine names itself.

function wallHeightAt

wallHeightAt(park: Park, angle: number, year?: number): number | null

The wall height at a bearing: the stretch of the park's fence that covers it in the season, in feet, or null where the park's data holds none.

type ZoneAnswer

interface ZoneAnswer { sport: Sport; /** The point on the surface, in feet in the drawing's wide frame, the team attacking the right. */ at: [number, number]; /** True when the point is on the surface, or on its edge. Off it, `zone`, `subzone` and `zones` are empty (a ballpark is the exception: its foul ground and the stands beyond the fence are zones). */ inBounds: boolean; /** The zone the point is in: one of a pa…

What the rules say of a point.

function zoneAt

zoneAt(sport: Sport, point: PlaceLike, options?: ZoneOptions): ZoneAnswer

Where a point lies by the rules of a sport: the zone, the area in it and every other area that holds, each by id and name. Hockey answers in the offensive, neutral and defensive zones with the crease, the trapezoid, the slot, the face-off circles and behind the goal line; basketball in the frontcourt and backcourt with the restricted area, the paint, the mid-range, the corner threes and the arc; soccer in thirds with the six-yard box, the penalty area, the D, the half-spaces and the wings; baseball in the infield, the outfield, foul territory, the warning track and over the wall by the park's own fence; football in the end zones, the halves, the red zone and between the hashes. Every other sport answers in its zone sets.

Example

import { zoneAt } from "@johnmorrisdotca/kyogijo-stadiums/zones";
// The NHL API's (69, 22) is the face-off dot in the right end zone: the offensive zone, inside a face-off circle.
const dot = zoneAt("nhl", [69, 22], { frame: "nhl-api" });
console.log(dot.zone, dot.subzone, dot.zones.includes("faceoff-circle"));
// offensive-zone faceoff-circle true

function zoneAtSurface

zoneAtSurface(sport: Sport, at: readonly [number, number], options?: ZoneOptions): ZoneAnswer

zoneAt for a place already on the surface: feet in the drawing's wide frame, the team attacking the right. What a drawing's click is asked with (pointer.ts).

function zoneName

zoneName: (id: string, language?: Language, league?: string) => string

The name of a zone id in a language: the rules engine's own ids have words in both; any other id (a zone set's) comes back as it is. A court of league: "fiba" has the words of FIBA's rule book, which the Japan Basketball Association follows: the restricted area is the no-charge semicircle (ノーチャージセミサークル); in the NBA and the leagues that follow its words it is the restricted area (リストリクテッドエリア).

type ZoneOptions

interface ZoneOptions extends Partial<Pick<SurfaceOptions, "league" | "year" | "pitch" | "park" | "fence" | "units" | "sheet" | "net" | "field" | "pool" | "athletics" | "skating" | "language">> { /** The frame the point is in: the name of a frame (`nhl-api`, `opta`), a frame, or nothing for the drawing's own (feet, or `units`). */ frame?: Frame | string; /** The size of the surface when the frame is a variable one, …

What zoneAt and the predicates take besides the point: the surface's own options (the league, the season, the park, the pitch) and the frame the point is in.

function zoneResolver

zoneResolver(sport: Sport, options?: ZoneOptions): (x: number, y: number) => string | undefined

A function from a place on the surface (feet, the drawing's wide frame, the team attacking the right) to the id of the finest zone it lies in: what a data layer's marks carry as data-zone. Built once for the options of a drawing; undefined where the sport has no zone there.

@johnmorrisdotca/kyogijo-stadiums/pointer

clientPointToField clientPointToFrames ElementLike FieldPoint fieldPointToClient MatrixLike PointerOptions SvgLike

function clientPointToField

clientPointToField(svg: SvgLike | Element, clientX: number, clientY: number, options?: PointerOptions): FieldPoint | null

Where a page position is on the field of a drawing, in a provider's numbers, with the zone the rules put it in. svg is the drawing's <svg> element, and the position is a pointer event's clientX and clientY. It holds whatever the drawing is: wide or tall, any size or padding, a range, a cropped viewBox. It answers null for a drawing with no surface to ask (a badge, a special game, or the 3D view, whose floor is laid in CSS 3D). provider names a frame of the drawing's sport (nhl-api, opta, statsbomb, mlbam...) and a frame of another sport is an error; without one, the numbers are the surface's own in feet.

Example

``ts no-run import { clientPointToField } from "@johnmorrisdotca/kyogijo-stadiums/pointer"; const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!; svg.addEventListener("click", (event) => { const at = clientPointToField(svg, event.clientX, event.clientY, { provider: "nhl-api" }); if (at !== null) console.log(at.x, at.y, at.zone, at.inBounds); }); ``

function clientPointToFrames

clientPointToFrames(svg: SvgLike | Element, clientX: number, clientY: number, options?: Omit<PointerOptions, "provider"> & { providers?: readonly (Frame | string)[]; }): (Omit<FieldPoint, "x" | "y" | "provider"> & { points: { provider: string; title: string; unit: string; x: number; y: number; }[]; }) | null

The same page position in each of several frames at once, with the zone: what a readout that shows a place in every provider asks (the demo's). providers defaults to every frame of the drawing's sport.

Example

``ts no-run import { clientPointToFrames } from "@johnmorrisdotca/kyogijo-stadiums/pointer"; const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!; svg.addEventListener("pointermove", (event) => { const all = clientPointToFrames(svg, event.clientX, event.clientY); console.log(all?.points.map((p) => ${p.provider}: ${p.x}, ${p.y}), all?.zone); }); ``

type ElementLike

interface ElementLike { getAttribute(name: string): string | null; getScreenCTM?(): MatrixLike | null; }

What the functions need of an element of the drawing: its attributes, and for the surface group its matrix to the page.

type FieldPoint

interface FieldPoint { /** The place in the provider's frame (feet in the drawing's wide frame for `"surface"`). */ x: number; y: number; /** The frame's name. */ provider: string; /** The place on the surface: feet in the drawing's wide frame (x along the surface from the left, y across it from the top). */ surface: { x: number; y: number }; /** True when the pointer is on the surface, or on its edge: not on the ap…

Where the pointer is on the field.

function fieldPointToClient

fieldPointToClient(svg: SvgLike | Element, point: PlaceLike, options?: PointerOptions): { x: number; y: number; } | null

The page position of a place on the field: the undoing of clientPointToField. The place is in a provider's numbers (or the surface's own, in feet or units); the answer is clientX and clientY, to put a marker over the drawing with. null for a drawing with no surface to ask.

Example

``ts no-run import { fieldPointToClient } from "@johnmorrisdotca/kyogijo-stadiums/pointer"; const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!; const at = fieldPointToClient(svg, [69, 22], { provider: "nhl-api" }); declare const marker: HTMLElement; if (at !== null) marker.style.transform = translate(${at.x}px, ${at.y}px); ``

type MatrixLike

interface MatrixLike { a: number; b: number; c: number; d: number; e: number; f: number; }

The six numbers of a matrix, as an SVG's DOMMatrix has them.

type PointerOptions

interface PointerOptions { /** The frame the answer is written in: the name of a frame of the drawing's sport (`nhl-api`, `opta`, `statsbomb`, `mlbam`), a frame, or `"surface"` for the drawing's own numbers (feet, or `units`). `"surface"` when missing. */ provider?: Frame | string; /** The unit of the `"surface"` numbers: feet when missing. */ units?: Unit; /** The end the provider's team attacks, as in `toSurface`:…

What the pointer functions take besides the position.

type SvgLike

interface SvgLike extends ElementLike { querySelector(selectors: string): unknown; closest?(selectors: string): unknown; }

What the functions need of the drawing's <svg>: to find the surface group in it. A real SVGSVGElement is one.

@johnmorrisdotca/kyogijo-stadiums/target

drawGoalMouth drawStrikeZone drawStumps drawTarget GOAL_SPORTS goalSize GoalSport goalZoneIds KYOGIJO_TARGET_STYLE LENGTH_ZONES lengthZoneOf TARGET_SPORTS TargetDrawing TargetFrame TargetOptions TargetPlace TargetShot TargetSport TargetUnit targetZoneAt

function drawGoalMouth

drawGoalMouth(shots: readonly TargetShot[], options: Omit<TargetOptions, "sport"> & { sport: GoalSport; }): TargetDrawing

The mouth of a goal seen from the shooter's side, with the shots laid on it: where each crossed the goal line (x across from the middle, positive to the shooter's right, and z up). A shot wide of the posts or over the bar is drawn outside the frame, a post or crossbar on it; the nine zones (the top shelf and the five-hole in hockey, the top corners and the bottom corners everywhere) are drawn when asked and counted in zones.

Example

import { drawGoalMouth } from "@johnmorrisdotca/kyogijo-stadiums/target";
const view = drawGoalMouth(
  [
    { x: -2.4, z: 3.6, outcome: "goal" },
    { x: 0, z: 0.4, outcome: "saved" },
    { x: 3.4, z: 2, outcome: "wide" },
  ],
  { sport: "nhl", zones: true },
);
console.log(view.placed, view.zones["top-left-corner"], view.zones["wide-right"], view.target.width, view.target.height);
// 3 1 1 6 4

function drawStrikeZone

drawStrikeZone(pitches: readonly TargetShot[], options?: Omit<TargetOptions, "sport">): TargetDrawing

The strike zone seen from the catcher's position: home plate's 17 inches across, the zone from the batter's knees to his belt (bottom and top, in feet), the nine cells of the zone (1 to 9, top left to bottom right as the catcher sees them) and the four chase zones round them (11 to 14), the batter's side, and the pitches numbered in order, coloured by result or type and shaped by what became of them. The place is Statcast's plate_x and plate_z.

Example

import { drawStrikeZone } from "@johnmorrisdotca/kyogijo-stadiums/target";
const view = drawStrikeZone([
  { x: 0.1, z: 2.6, outcome: "called-strike", type: "FF" },
  { x: 1.3, z: 1.1, outcome: "ball", type: "SL" },
  { x: -0.4, z: 2.0, outcome: "swinging-strike", type: "SL" },
], { bats: "R" });
console.log(view.placed, view.zones["zone-5"], view.zones["zone-13"] ?? 0, view.key.map((k) => k.id).join(" "));
// 3 1 0 ball called-strike swinging-strike

function drawStumps

drawStumps(deliveries: readonly TargetShot[], options?: Omit<TargetOptions, "sport">): TargetDrawing

The stumps seen from the bowler's end: three stumps across 9 inches and 28 inches high with the bails, the line zones either side of them, and the deliveries where they reached the stumps' line (x across, z up), each with the length it pitched at if you give one (length): lengthZoneOf names its length zone.

Example

import { drawStumps, lengthZoneOf } from "@johnmorrisdotca/kyogijo-stadiums/target";
const view = drawStumps([{ x: 0.02, z: 0.4, outcome: "wicket", length: 5.1 }, { x: 0.5, z: 0.9, outcome: "dot", length: 7.4 }]);
console.log(view.zones["on-stumps"], view.zones["wide-right"], lengthZoneOf(5.1), lengthZoneOf(7.4));
// 1 1 good-length short-of-a-length

function drawTarget

drawTarget(shots: readonly TargetShot[], options: TargetOptions): TargetDrawing

Draw a goal, the stumps or a strike zone with shots laid on it.

const GOAL_SPORTS

GOAL_SPORTS: readonly ["nhl", "soccer", "handball", "waterpolo", "lacrosse", "fieldhockey"]

The sports whose target is a goal.

function goalSize

goalSize(sport: GoalSport, options?: Pick<TargetOptions, "league" | "pitch">): { width: number; height: number; unit: "m" | "ft"; source: string; }

The size of a goal in its rulebook unit. Hockey: the NHL's net (Rule 2.1) 6 by 4 feet. Soccer: the Laws' 7.32 by 2.44 m, or the format's. Handball: the IHF's 3 by 2 m. Water polo: World Aquatics' 3 m, the crossbar 0.9 m over the water. Lacrosse: 6 by 6 feet (World Lacrosse 1.83 m, NCAA, PLL), the box floor's 4 ft 9 in by 4 ft. Field hockey: the FIH's 3.66 by 2.14 m.

type GoalSport

type GoalSport = (typeof GOAL_SPORTS)[number];

function goalZoneIds

goalZoneIds(sport: GoalSport): string[]

The ids of the 3 by 3 grid across a goal, left to right and top to bottom: the shooter's left first.

const KYOGIJO_TARGET_STYLE

KYOGIJO_TARGET_STYLE: "\n.kyogijo .ky-tg-bg{fill:var(--ky-surface)}\n.kyogijo.ky-target-field .ky-tg-bg{fill:var(--ky-apron)}\n.kyogijo .ky-tg-open{fill:var(--ky-zone);opacity:.55}\n.kyogijo.ky-target-field .ky-tg-open{fill:var(--ky-surface);opacity:.5}\n.kyogijo .ky-tg-cell{fill:none;stroke:var(--ky-line);opacity:.6}\n.kyogijo .ky-tg-out{fill:none;stroke:var(--ky-line);opacity:.4}\n.kyogijo .ky-tg-cell:hover,.kyogi…

const LENGTH_ZONES

LENGTH_ZONES: readonly { id: string; to: number; }[]

The length zones of a delivery by where it pitched, in metres from the batter's stumps (the convention here is ours: broadcasters' bands differ with the bowler's pace).

function lengthZoneOf

lengthZoneOf(metres: number): string

The length zone of a delivery that pitched this many metres from the batter's stumps.

const TARGET_SPORTS

TARGET_SPORTS: readonly ["nhl", "soccer", "handball", "waterpolo", "lacrosse", "fieldhockey", "cricket", "mlb"]

The things that have a target seen end-on: the sports with a goal, cricket's stumps and baseball's strike zone.

type TargetDrawing

interface TargetDrawing { /** The drawing as an SVG string. */ svg: string; /** The layers of the marks, in the frame of the target (x across and y up, in `units`), if you want to draw them another way. */ layers: Layer[]; /** The shots that had a place and the ones that did not. */ placed: number; skipped: number; /** How many marks are in each zone, by id. */ zones: Record<string, number>; /** What the colours and…

The target as drawn and as data.

type TargetFrame

interface TargetFrame { sport: TargetSport; unit: TargetUnit; /** The opening: the goal's width and height, the stumps' 9 inches by 28, the strike zone's plate width and the zone's height. */ width: number; height: number; /** Where the opening's bottom is: 0 for a goal and the stumps, the bottom of the zone for the strike zone. */ floor: number; /** The view: from `x0` to `x1` across, from `z0` to `z1` up. */ view:…

The size of a target and of what is drawn round it, in the unit it is in.

type TargetOptions

interface TargetOptions { /** `nhl`, `soccer`, `handball`, `waterpolo`, `lacrosse` or `fieldhockey` for a goal, `cricket` for the stumps, `mlb` for the strike zone. */ sport: TargetSport; /** The unit of the shots' `x` and `z`, and of `goal`, `top` and `bottom`: the unit the sport's rulebook prints the target in (feet for hockey, lacrosse and baseball, metres for the rest). */ units?: TargetUnit; /** A league of the…

What the target draws.

type TargetPlace

interface TargetPlace { zone: string; placement: "in" | "wide" | "high" | "wide-high" | "frame"; }

Where a shot lies across a target: the id of its zone, and whether it is inside the opening, wide of it, over it or on its frame.

type TargetShot

interface TargetShot { /** Your id for the mark, written on it as `data-mark-id`. */ id?: string | number; /** Across the target from its middle, positive to the viewer's right (the shooter's, the bowler's; the catcher's for a pitch: Statcast's `plate_x`), in the options' `units`. */ x: number | null; /** Up from the ground (Statcast's `plate_z`), in `units`. */ z: number | null; /** * What became of it. A goal: `go…

A shot, a delivery or a pitch where it crossed the target.

type TargetSport

type TargetSport = (typeof TARGET_SPORTS)[number];

A sport that has a target.

type TargetUnit

type TargetUnit = "m" | "ft" | "in" | "cm" | "yd";

The unit of a number across a target.

function targetZoneAt

targetZoneAt(options: TargetOptions, x: number, z: number): TargetPlace

The zone of a place across a goal, the stumps or the strike zone, in the options' units. A goal: its nine cells (the top shelf and the five-hole in hockey), or wide-left, wide-right and over-the-bar outside the opening; the stumps: the line zones; the strike zone: zone-1 to zone-9 and the chase zones zone-11 to zone-14, else outside.

Example

import { targetZoneAt } from "@johnmorrisdotca/kyogijo-stadiums/target";
// A hockey net is 6 by 4 feet: a shot 2.5 feet to the right and 3.7 feet up is under the crossbar in the top right corner.
console.log(targetZoneAt({ sport: "nhl" }, 2.5, 3.7), targetZoneAt({ sport: "nhl" }, 0, 0.3).zone);
// { zone: 'top-right-corner', placement: 'in' } five-hole

@johnmorrisdotca/kyogijo-stadiums/turn

Box3 buildTurn cameraMatrix clampView CURLING_PRESETS depthOf describeUprights drag eyeDistance fit floorOptions heightUnit KYOGIJO_TURN_STYLE KyogijoTurn Layout layoutOf LONG_PRESETS Mat4 matrix3d mountKyogijoTurn multiply NET_PRESETS nudge openingView PartKind PITCH_MAX Placement Plane planeMarkup planeMatrix planesOf presetOf presetsFor presetView project step STEP TURN_DEFAULT TURN_PRESET_NAMES TURN_PRESETS turnMarkup turnMarkupOf TurnModel TurnPreset TurnSettings turnSupported TurnToward TurnView Upright UprightKind UprightOptions Uprights uprightsFor V3 ZOOM_MAX ZOOM_MIN

type Box3

interface Box3 { x0: number; y0: number; x1: number; y1: number; z0: number; z1: number; }

A box in the scene, in pixels from the scene's middle, the floor at z0 and the highest thing at z1.

function buildTurn

buildTurn(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): TurnModel

Work out a 3D view from the options of a drawing.

function cameraMatrix

cameraMatrix(view: TurnView, at: Placement, distance: number): number[]

The matrix that puts a point of the scene on the screen for a view in a placement: the scene (whose middle is the origin) is scaled, turned about the vertical, tipped back, shifted to where the fit puts it, and seen with a perspective distance pixels deep. This is project's arithmetic as one matrix, so that an element can be given its whole place in one matrix3d and need no 3D context of the browser's: a browser that draws nested 3D layers flat still draws it right.

function clampView

clampView(view: TurnView, fallback?: TurnView): TurnView

A view held to what the controls allow: the pitch from overhead to 84 degrees, the zoom from half to four times. A number that is not one is left as it was.

const CURLING_PRESETS

CURLING_PRESETS: Readonly<Record<TurnPreset, TurnView>>

The same four for a curling sheet, which is ten times as long as it is wide: from the side it is a strip across the box, so the broadcast and the corner views stay nearly square on to it, and the end view looks along it.

function depthOf

depthOf(point: readonly [number, number, number], view: TurnView, at: Placement): number

How far a point is from the eye's plane after the scene is turned and tipped, in pixels towards the eye: bigger is nearer. Planes are drawn far first.

function describeUprights

describeUprights(o: SurfaceOptions, uprights: Uprights, language?: Language): string

What stands on the surface, in a sentence, with heights: for a reader who cannot see the 3D view. An empty string when nothing stands on it.

function drag

drag(view: TurnView, dx: number, dy: number, tilt?: boolean, degreesPerPixel?: number): TurnView

The view after a drag of the pointer by (dx, dy) pixels, degreesPerPixel apart: the surface is taken hold of, so a drag to the right carries the side nearest the eye to the right (the yaw falls) and a drag down tips the top toward the eye (the pitch falls).

function eyeDistance

eyeDistance: (w: number, h: number, lens?: number) => number

How far the eye is from the screen, in pixels, for a container: a long lens, so that the floor keeps its shape and the heights lean a little.

function fit

fit(b: Box3, view: TurnView, w: number, h: number, distance: number, pad?: number): Placement

The placement that fits the scene in a container of width w and height h pixels, centred, with pad of its width and height left round it. The perspective bends the sum, so it is found by a few rounds of scaling and re-centring; six is plenty.

function floorOptions

floorOptions(options: SurfaceOptions | ScoreOptions): SurfaceOptions | ScoreOptions

The options with what makes the floor of a 3D view: the whole surface, lying wide, at its full size.

function heightUnit

heightUnit(o: Pick<SurfaceOptions, "sport" | "league" | "units">): Unit

The unit the heights are said in: the options' own, else the rulebook's of the sport.

const KYOGIJO_TURN_STYLE

KYOGIJO_TURN_STYLE: string

The 3D view's stylesheet: the colours of what stands on the surface, in light and dark, and the rules that lay the scene out. Like the drawing's own, the defaults are in :where(), which has no weight, so a page's own rule always wins; the colours are custom properties (--ky-up-*) a page can set.

type KyogijoTurn

interface KyogijoTurn { /** `3d`, or `flat` where the browser cannot draw the 3D view (or there is nothing to turn): the flat drawing is shown and the rest of this does nothing. */ readonly mode: "3d" | "flat"; /** The view now: where it is going if it is gliding. */ view(): TurnView; /** Turn to a view (the parts of it given): it glides there unless `glide` is false or the reader prefers reduced motion. */ set(view…

What mountKyogijoTurn gives back.

type Layout

interface Layout { at: Placement; /** The floor, then the planes in the model's order. */ transforms: string[]; /** The stack of each: 0 for the ground of a hall, 1 for the floor, then the planes far to near from 2. */ stack: number[]; /** How much of each is left: 1 for all but the walls that are between the eye and the surface, which are cut away as the view is low and on their side (`fadeOf`). */ fade: number[]; }

Where everything is for a view in a container: the placement, the matrix of each element and its place in the stack.

function layoutOf

layoutOf(model: TurnModel, view: TurnView, width: number, height: number, lens?: number): Layout

Work a view out in a container width by height pixels.

const LONG_PRESETS

LONG_PRESETS: Readonly<Record<TurnPreset, TurnView>>

The same four for a long, narrow surface (a football field, a hockey rink): turned less, because a diagonal view would shrink it in the box.

type Mat4

type Mat4 = readonly number[];

A 4 by 4 matrix, as CSS writes it in matrix3d: sixteen numbers, a column after a column.

function matrix3d

matrix3d: (m: Mat4) => string

A matrix as the CSS transform that applies it.

function mountKyogijoTurn

mountKyogijoTurn(host: HTMLElement, options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): KyogijoTurn

Mount a surface as a 3D view in a container, over the static drawing it would otherwise hold: the drawing is the floor, tilted and turned by the pointer, the touch, the wheel and the keys, with what stands on the surface (a net and its posts, a basket, a goal, the posts of a football field) standing on it.

Example

``ts no-run import { mountKyogijoTurn } from "@johnmorrisdotca/kyogijo-stadiums/turn"; const view = mountKyogijoTurn(document.querySelector("#court")!, { sport: "volleyball", home: { name: "Harbour City Gulls", score: 3 }, away: { name: "Prairie Ridge Bison", score: 2 } }, { preset: "end" }); view.preset("corner"); view.set({ yaw: 60, pitch: 40 }); ``

function multiply

multiply(a: Mat4, b: Mat4): number[]

Two matrices as one: b first, then a.

const NET_PRESETS

NET_PRESETS: Readonly<Record<TurnPreset, TurnView>>

The same four for the net sports, whose net runs across the court: seen from the side line it is a stick, so the broadcast view stands high behind an end line, as a television camera does, and the end-on view is lower behind it. The corner view is the same.

function nudge

nudge(view: TurnView, toward: TurnToward, amount: number): TurnView

The view after a step of amount degrees toward a side, the way a drag toward that side takes the view: left and right carry the near side to the left and to the right (the yaw rises and falls), up tips the surface as a drag up does and down as a drag down does (the pitch rises and falls). The arrow keys and the four turn buttons are this, so that a drag, a key and a button toward one side all move the surface the same way.

function openingView

openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string) => TurnView

The view a set of settings opens on.

type PartKind

type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target";

What a plane is a part of: it is the class of its element (ky-up-<kind>) and what a test finds it by.

const PITCH_MAX

PITCH_MAX: 84

type Placement

interface Placement { k: number; tx: number; ty: number; }

How a view is placed in a container: the scale (at a zoom of 1) that fits the scene, and where the middle of what is seen falls.

type Plane

interface Plane { /** A name that says what it is and where: `net`, `post-left`, `goal-right-back`. */ id: string; kind: PartKind; o: V3; u: V3; v: V3; svg: string; /** * A plane that turns to face the eye, about the vertical through its middle: a scoreboard is read from wherever the view is. `u` and `v` give its size and its * place as it lies at a yaw of 0; `v` must be vertical. */ billboard?: boolean; /** * A wal…

One flat rectangle. Its own x runs along u (its whole length) and its own y along v; o is the corner both start from. svg is what is drawn on it, in feet along u and v.

function planeMarkup

planeMarkup(p: Plane, scale: number, transform: string, stack: number, fade?: number): string

One plane as an element: its box in pixels and the SVG drawn on it in feet.

function planeMatrix

planeMatrix(p: Plane, scale: number, centre?: readonly [number, number]): number[]

The matrix that stands a plane in the scene, before the camera: its x along u, its y along v, its corner o, in pixels from the middle of the scene (centre, in feet).

function planesOf

planesOf: (u: Pick<Uprights, "items">) => Plane[]

All the planes of all the things.

function presetOf

presetOf(view: TurnView, sport?: string): TurnPreset | undefined

The name of the viewpoint a view is in a sport, or undefined when it is none of them (it has been turned by hand).

function presetsFor

presetsFor: (sport?: string) => Readonly<Record<TurnPreset, TurnView>>

The four viewpoints of a sport.

function presetView

presetView: (name: string | undefined, sport?: string) => TurnView | undefined

The view a viewpoint name stands for in a sport, or undefined for a name that is not one.

function project

project(point: readonly [number, number, number], view: TurnView, at: Placement, distance: number): [number, number, number]

Where a point of the scene falls on the screen, in pixels from the middle of the container, when the scene is placed as given and the eye is distance pixels away.

function step

step(view: TurnView, change: { yaw?: number; pitch?: number; zoom?: number; }): TurnView

The view after a step of a key or a button.

const STEP

STEP: { readonly yaw: 6; readonly yawBig: 24; readonly pitch: 4; readonly pitchBig: 12; readonly zoom: 1.2; }

The steps of the keys and the buttons, in degrees, and the factor of a zoom step. A held Shift takes the larger.

const TURN_DEFAULT

TURN_DEFAULT: TurnPreset

The view a mount opens on.

const TURN_PRESET_NAMES

TURN_PRESET_NAMES: readonly TurnPreset[]

The viewpoints, in the order the controls and the number keys list them.

const TURN_PRESETS

TURN_PRESETS: Readonly<Record<TurnPreset, TurnView>>

The four viewpoints. overhead looks straight down; broadcast is the main camera, high on one side line and turned a little towards the near end so that the boards, the nets and the goals show their faces; end stands behind an end line, along the surface, which is where a net, a goal or a basket is best seen; corner is low, from a corner, with the nearest net or goal in front.

function turnMarkup

turnMarkup(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): string

A surface as a 3D view, as markup: the flat drawing for a floor, what stands on it as planes, and the controls under it. Pure, so it can be made on a server; it is a still picture until mountKyogijoTurn (or the element) makes it turn in a page.

Example

import { turnMarkup } from "@johnmorrisdotca/kyogijo-stadiums/turn";
const html = turnMarkup({ sport: "volleyball" }, { preset: "end" });
console.log(html.includes("ky-turn"), html.includes('data-part="net"'));
// true true

function turnMarkupOf

turnMarkupOf(model: TurnModel, settings?: TurnSettings): string

The 3D view of a model as markup, laid out for settings.size; a page's script then makes it turn.

type TurnModel

interface TurnModel { id: string; language: Language; /** The options the floor is drawn from: the whole surface, lying wide. */ options: SurfaceOptions | ScoreOptions; title: string; /** What the drawing says, and then what stands on it. */ description: string; /** The sentence about what stands on the surface, or an empty string. */ standing: string; /** The floor: the markup of the flat drawing. */ floor: string;…

Everything a 3D view is made of.

type TurnPreset

type TurnPreset = "overhead" | "broadcast" | "end" | "corner";

The named viewpoints.

type TurnSettings

interface TurnSettings { /** The viewpoint it opens on: `overhead`, `broadcast`, `end` or `corner` (the default). */ preset?: TurnPreset; /** An exact opening view, laid over the preset's. */ view?: Partial<TurnView>; /** Whether the buttons under the view are drawn (a row of viewpoints, turn, raise, zoom and reset). True when left out; the keys and the pointer work either way. */ controls?: boolean; /** What stands…

What can be asked of a 3D view.

function turnSupported

turnSupported(win?: { CSS?: { supports?: (property: string, value: string): boolean; } | null; } | undefined) => boolean

True where the page can draw a plane in 3D by one matrix3d: that is, nearly everywhere with a stylesheet.

type TurnToward

type TurnToward = "left" | "right" | "up" | "down";

A side a key, a button or a drag takes the surface toward.

type TurnView

interface TurnView { yaw: number; pitch: number; zoom: number; }

How the scene is looked at: turned about the vertical (degrees, clockwise as the eye sees the floor), tipped back from straight overhead (degrees), and magnified.

type Upright

interface Upright { id: string; kind: UprightKind; /** Where it stands on the surface, in feet. */ at: readonly [number, number]; /** The height of its top above the surface, in feet. */ height: number; /** A second height worth saying (a net's height at the centre, a post's crossbar), in feet. */ low?: number; planes: Plane[]; }

A thing that stands on the surface: where, how high, and the planes it is made of.

type UprightKind

type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" …

What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts.

type UprightOptions

interface UprightOptions { /** Volleyball: the net at the men's height (2.43 m, the default) or the women's (2.24 m). */ netHeight?: "men" | "women"; /** Where the score of a score drawing goes: `board` (the default) hangs it up as a scoreboard (over the middle of a court or a rink, behind an end of a field) and takes the panel off the floor; `floor` leaves it on the floor. */ score?: "board" | "floor"; /** Leave th…

What can be asked of the uprights.

type Uprights

interface Uprights { items: Upright[]; /** How far the surface itself stands over the ground, in feet: a table tennis table's top, 0 on every other surface. */ lift: number; /** The thinnest line or strip drawn on this surface, in feet. */ thin: number; /** The playing surface itself in feet, the part the view is fitted to (the ground round it may run out of the picture): `undefined` for a surface with no uprights, …

What stands on a surface.

function uprightsFor

uprightsFor(o: SurfaceOptions, settings?: UprightOptions): Uprights

What stands on the surface the options draw: nothing on a curling sheet, which is flat.

type V3

type V3 = readonly [number, number, number];

A point or a direction: x, y, z in feet.

const ZOOM_MAX

ZOOM_MAX: 4

const ZOOM_MIN

ZOOM_MIN: 0.5

@johnmorrisdotca/kyogijo-stadiums/element/define

defineKyogijoField

function defineKyogijoField

defineKyogijoField(tag?: string): typeof KyogijoField

Register <kyogijo-field> (or the tag you give), once. Returns the class.