@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 safeLogo
safeLogo(value: unknown, context?: EmblemContext): string | undefined
A logo as it may be written into an image's href, or undefined. An https: or http: address, a relative path, an image data: address, an SVG document as text (written as an image, which cannot run scripts), or, with a renderer of emblems plugged in, the id of an emblem ("trophy:cup", "medal:gold", "sport:nhl", "crest:shield", a trophy or award by its short name such as "cup", with /line for the outline style), drawn for an image with the outline in dark or pale ink by the device's setting. The second argument gives a crest the team's colours and letters. With a renderer, a name that is an emblem's id is the emblem, not a file of that name: write ./cup for the file. Without one, an id is an address like any other, and "trophy:cup" is refused.
Example
import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
// https://example.com/gulls.svg 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/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/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 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_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/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.