@johnmorrisdotca/kyogijo-stadiums 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/NOTICE.md +179 -3
  3. package/README.md +43 -11
  4. package/dist/arenas-entry.d.ts +4 -0
  5. package/dist/arenas-entry.js +3 -0
  6. package/dist/arenas.d.ts +125 -0
  7. package/dist/arenas.data.d.ts +2 -0
  8. package/dist/arenas.data.js +3763 -0
  9. package/dist/arenas.js +72 -0
  10. package/dist/atlas-entry.d.ts +3 -1
  11. package/dist/atlas-entry.js +3 -2
  12. package/dist/atlas.d.ts +37 -19
  13. package/dist/atlas.js +92 -7
  14. package/dist/background.js +2 -1
  15. package/dist/budo/describe.d.ts +13 -0
  16. package/dist/budo/describe.js +120 -0
  17. package/dist/budo/dojo.api.d.ts +64 -0
  18. package/dist/budo/dojo.api.js +46 -0
  19. package/dist/budo/dojo.d.ts +72 -0
  20. package/dist/budo/dojo.js +300 -0
  21. package/dist/budo/judo.d.ts +61 -0
  22. package/dist/budo/judo.js +109 -0
  23. package/dist/budo/karate.d.ts +29 -0
  24. package/dist/budo/karate.js +80 -0
  25. package/dist/budo/kendo.d.ts +18 -0
  26. package/dist/budo/kendo.js +44 -0
  27. package/dist/budo/kimarite.d.ts +19 -0
  28. package/dist/budo/kimarite.js +33 -0
  29. package/dist/budo/kit.d.ts +16 -0
  30. package/dist/budo/kit.js +24 -0
  31. package/dist/budo/kyudo.d.ts +28 -0
  32. package/dist/budo/kyudo.js +66 -0
  33. package/dist/budo/looks.d.ts +28 -0
  34. package/dist/budo/looks.js +58 -0
  35. package/dist/budo/parts.d.ts +32 -0
  36. package/dist/budo/parts.js +10 -0
  37. package/dist/budo/plan.d.ts +64 -0
  38. package/dist/budo/plan.js +139 -0
  39. package/dist/budo/room.d.ts +133 -0
  40. package/dist/budo/room.js +111 -0
  41. package/dist/budo/sumo.d.ts +31 -0
  42. package/dist/budo/sumo.js +71 -0
  43. package/dist/budo/tatami.d.ts +55 -0
  44. package/dist/budo/tatami.js +156 -0
  45. package/dist/budo/types.d.ts +192 -0
  46. package/dist/budo/types.js +2 -0
  47. package/dist/budo-entry.d.ts +22 -0
  48. package/dist/budo-entry.js +13 -0
  49. package/dist/budo.board.d.ts +28 -0
  50. package/dist/budo.board.js +81 -0
  51. package/dist/budo.score.d.ts +18 -0
  52. package/dist/budo.score.js +52 -0
  53. package/dist/budo.style.d.ts +6 -0
  54. package/dist/budo.style.js +67 -0
  55. package/dist/charts.baseball.js +1 -1
  56. package/dist/charts.js +4 -4
  57. package/dist/charts.soccer.js +3 -3
  58. package/dist/charts.types.d.ts +10 -0
  59. package/dist/colour.d.ts +4 -4
  60. package/dist/colour.js +4 -4
  61. package/dist/crowd.d.ts +11 -1
  62. package/dist/crowd.draw.js +15 -9
  63. package/dist/crowd.js +17 -2
  64. package/dist/draw.js +156 -31
  65. package/dist/element-entry.d.ts +2 -0
  66. package/dist/element-entry.js +1 -0
  67. package/dist/element.d.ts +2 -2
  68. package/dist/element.js +2 -1
  69. package/dist/emblems-source.d.ts +50 -0
  70. package/dist/emblems-source.js +56 -0
  71. package/dist/eras.d.ts +3 -1
  72. package/dist/eras.js +8 -3
  73. package/dist/features.js +73 -0
  74. package/dist/field.data.d.ts +16 -0
  75. package/dist/field.data.js +50 -0
  76. package/dist/fields.kit.d.ts +2 -2
  77. package/dist/fields.types.d.ts +9 -3
  78. package/dist/formations.js +18 -7
  79. package/dist/formations.sports.d.ts +8 -0
  80. package/dist/formations.sports.js +251 -0
  81. package/dist/frames.providers.js +9 -1
  82. package/dist/halls.d.ts +65 -0
  83. package/dist/halls.js +122 -0
  84. package/dist/heat.js +17 -10
  85. package/dist/heat.zones.d.ts +4 -2
  86. package/dist/heat.zones.js +55 -3
  87. package/dist/homeside.js +7 -0
  88. package/dist/index.d.ts +15 -3
  89. package/dist/index.js +8 -1
  90. package/dist/layers-entry.d.ts +2 -1
  91. package/dist/layers-entry.js +1 -0
  92. package/dist/layers.d.ts +2 -1
  93. package/dist/layers.env.d.ts +10 -1
  94. package/dist/layers.env.js +33 -3
  95. package/dist/layers.js +30 -11
  96. package/dist/layers.types.d.ts +9 -2
  97. package/dist/leagues/budo.d.ts +196 -0
  98. package/dist/leagues/budo.js +105 -0
  99. package/dist/leagues/hockey.js +41 -19
  100. package/dist/leagues/sources.js +21 -0
  101. package/dist/leagues/types.d.ts +1 -1
  102. package/dist/leagues.d.ts +4 -1
  103. package/dist/leagues.js +102 -2
  104. package/dist/lineup.codes.d.ts +23 -0
  105. package/dist/lineup.codes.js +122 -0
  106. package/dist/logo.d.ts +25 -0
  107. package/dist/logo.js +47 -0
  108. package/dist/outline.d.ts +1 -1
  109. package/dist/outline.js +17 -3
  110. package/dist/pageant.js +7 -3
  111. package/dist/pointer-entry.d.ts +2 -0
  112. package/dist/pointer-entry.js +2 -0
  113. package/dist/pointer.d.ts +113 -0
  114. package/dist/pointer.js +151 -0
  115. package/dist/pool.d.ts +7 -7
  116. package/dist/pool.figures.d.ts +57 -55
  117. package/dist/pool.figures.js +59 -55
  118. package/dist/pool.js +7 -7
  119. package/dist/pool.labels.js +2 -1
  120. package/dist/ranges.names.js +6 -0
  121. package/dist/road.draw.js +3 -2
  122. package/dist/road.js +2 -1
  123. package/dist/road.types.d.ts +3 -0
  124. package/dist/rules.d.ts +138 -0
  125. package/dist/rules.js +511 -0
  126. package/dist/rules.types.d.ts +4 -0
  127. package/dist/rules.types.js +64 -0
  128. package/dist/score.d.ts +3 -0
  129. package/dist/score.js +23 -1
  130. package/dist/situation.d.ts +135 -0
  131. package/dist/situation.draw.d.ts +34 -0
  132. package/dist/situation.draw.js +202 -0
  133. package/dist/situation.js +337 -0
  134. package/dist/situation.style.d.ts +2 -0
  135. package/dist/situation.style.js +45 -0
  136. package/dist/situation.types.d.ts +77 -0
  137. package/dist/situation.types.js +3 -0
  138. package/dist/situation.widgets.d.ts +39 -0
  139. package/dist/situation.widgets.js +129 -0
  140. package/dist/strings.d.ts +175 -1
  141. package/dist/strings.js +364 -20
  142. package/dist/style.js +9 -2
  143. package/dist/surfaces/artistic.d.ts +3 -3
  144. package/dist/surfaces/artistic.js +3 -3
  145. package/dist/surfaces/baseball.js +2 -0
  146. package/dist/surfaces/basketball.js +1 -1
  147. package/dist/surfaces/budo.d.ts +6 -0
  148. package/dist/surfaces/budo.js +104 -0
  149. package/dist/surfaces/dive.d.ts +3 -3
  150. package/dist/surfaces/dive.js +3 -3
  151. package/dist/surfaces/football.js +7 -0
  152. package/dist/surfaces/hockey.js +3 -2
  153. package/dist/surfaces/polo.d.ts +4 -4
  154. package/dist/surfaces/polo.js +5 -5
  155. package/dist/surfaces/pool.d.ts +1 -1
  156. package/dist/surfaces/pool.js +3 -3
  157. package/dist/surfaces/surface.d.ts +32 -0
  158. package/dist/surfaces/venue.d.ts +1 -1
  159. package/dist/surfaces/venue.js +1 -1
  160. package/dist/target-entry.d.ts +2 -0
  161. package/dist/target-entry.js +2 -0
  162. package/dist/target.d.ts +200 -0
  163. package/dist/target.js +525 -0
  164. package/dist/team.common.d.ts +1 -1
  165. package/dist/team.common.js +3 -3
  166. package/dist/team.formation.d.ts +3 -1
  167. package/dist/team.formation.js +3 -3
  168. package/dist/team.layers.d.ts +2 -2
  169. package/dist/team.layers.js +4 -1
  170. package/dist/team.lineup.d.ts +27 -0
  171. package/dist/team.lineup.js +207 -0
  172. package/dist/team.network.js +6 -5
  173. package/dist/team.shape.js +5 -5
  174. package/dist/team.types.d.ts +40 -1
  175. package/dist/tenants.data.js +848 -0
  176. package/dist/turn-entry.d.ts +2 -2
  177. package/dist/turn-entry.js +1 -1
  178. package/dist/turn.camera.d.ts +16 -3
  179. package/dist/turn.camera.js +53 -7
  180. package/dist/turn.dom.js +11 -9
  181. package/dist/turn.kit.d.ts +2 -2
  182. package/dist/turn.style.js +4 -3
  183. package/dist/types.d.ts +43 -7
  184. package/dist/units.js +7 -0
  185. package/dist/uprights.budo.d.ts +17 -0
  186. package/dist/uprights.budo.js +87 -0
  187. package/dist/uprights.js +10 -1
  188. package/dist/uprights.pools.js +4 -4
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/zones-entry.d.ts +2 -0
  192. package/dist/zones-entry.js +2 -0
  193. package/package.json +39 -3
@@ -0,0 +1,27 @@
1
+ import { type Env, type Made } from "./layers.env.ts";
2
+ import { type Spot } from "./team.formation.ts";
3
+ import { type TeamTable } from "./team.common.ts";
4
+ import type { Formation, FormationLayer, LineupLayer, LineupPlayer } from "./team.types.ts";
5
+ /** How wide a text is, in ems of a bold sans-serif face, by a rough count: wide for Japanese and capitals, narrow for the letters that are. */
6
+ export declare function textWidth(text: string): number;
7
+ /** A name cut to fit `ems` of width, with an ellipsis; nothing when not even three letters fit. */
8
+ export declare function fit(name: string, ems: number): string;
9
+ /** The jersey as a path through its corners, `size` wide, centred on `(x, y)`: sleeves, shoulders, a neckline and the body. */
10
+ export declare function jerseyPath(x: number, y: number, size: number): string;
11
+ /** The formation a lineup is drawn in, and how the players stand in it. */
12
+ export interface Lineup {
13
+ formation: Formation;
14
+ /** The layer as a formation layer: the players put on the places they were given. */
15
+ layer: FormationLayer;
16
+ spots: Spot[];
17
+ /** The players with no place in the formation. */
18
+ unplaced: LineupPlayer[];
19
+ /** The player standing on each place, with everything the lineup knows of him. */
20
+ stands: Map<string, LineupPlayer>;
21
+ }
22
+ /** The formation a lineup uses: the one named, one of your own, or the best fit among those of the surface's sport (the softball one for a softball field). */
23
+ export declare function lineupFormation(layer: LineupLayer, env: Env): Formation;
24
+ /** A lineup's places on the surface and the players on them. */
25
+ export declare function computeLineup(layer: LineupLayer, env: Env): Lineup;
26
+ export declare function drawLineup(layer: LineupLayer, env: Env): Made;
27
+ export declare function lineupTables(layer: LineupLayer, env: Env): TeamTable[];
@@ -0,0 +1,207 @@
1
+ // The lineup layer: a team's players placed by position code on the surface, each as a disc or a jersey with the shirt number in it and the name under it, with a captain's
2
+ // armband and a small flag where they are given. It reads where each position stands from a formation (formations.ts, formations.sports.ts) and so needs no coordinates; two
3
+ // teams are two layers. Written for this package.
4
+ import { safeLogo } from "./logo.js";
5
+ import { esc, num } from "./format.js";
6
+ import { FRAMES } from "./frames.js";
7
+ import { positionName } from "./formations.js";
8
+ import { assign, bestFormation, formationsFor, spotsOf } from "./lineup.codes.js";
9
+ import { isNum, newMade, opacityOf, unitOfLayer } from "./layers.env.js";
10
+ import { chooseLeague } from "./leagues.js";
11
+ import { SURFACES, computeFormation, formationOf } from "./team.formation.js";
12
+ import { attrs, labelOf, paintOf, round, titled } from "./team.common.js";
13
+ import { fill } from "./strings.js";
14
+ import { FEET_PER } from "./units.js";
15
+ /** The sports whose team turns round for the visitors: a side that attacks the other end. Baseball's fielders, football's offence and a cricket field do not. */
16
+ const TURNS = new Set(["soccer", "nhl", "nba", "rugby", "afl", "volleyball", "handball", "waterpolo", "fieldhockey", "netball"]);
17
+ /** How wide a text is, in ems of a bold sans-serif face, by a rough count: wide for Japanese and capitals, narrow for the letters that are. */
18
+ export function textWidth(text) {
19
+ let width = 0;
20
+ for (const ch of text) {
21
+ const code = ch.codePointAt(0) ?? 0;
22
+ if (code >= 0x2e80)
23
+ width += 1;
24
+ else if ("iljtfI.,:;'!| ()".includes(ch))
25
+ width += 0.32;
26
+ else if (/[A-Z0-9MWmw@%]/.test(ch))
27
+ width += 0.7;
28
+ else
29
+ width += 0.6;
30
+ }
31
+ return width;
32
+ }
33
+ /** A name cut to fit `ems` of width, with an ellipsis; nothing when not even three letters fit. */
34
+ export function fit(name, ems) {
35
+ if (textWidth(name) <= ems)
36
+ return name;
37
+ const letters = [...name];
38
+ while (letters.length > 0 && textWidth(`${letters.join("").trimEnd()}…`) > ems)
39
+ letters.pop();
40
+ return letters.length < 3 ? "" : `${letters.join("").trimEnd()}…`;
41
+ }
42
+ /** The jersey as a path through its corners, `size` wide, centred on `(x, y)`: sleeves, shoulders, a neckline and the body. */
43
+ export function jerseyPath(x, y, size) {
44
+ const p = (dx, dy) => `${num(x + dx * size)} ${num(y + dy * size)}`;
45
+ return `M${p(-0.17, -0.48)}Q${p(0, -0.36)} ${p(0.17, -0.48)}L${p(0.5, -0.3)}L${p(0.42, -0.02)}L${p(0.34, -0.07)}L${p(0.34, 0.48)}L${p(-0.34, 0.48)}L${p(-0.34, -0.07)}L${p(-0.42, -0.02)}L${p(-0.5, -0.3)}Z`;
46
+ }
47
+ /** The formation a lineup uses: the one named, one of your own, or the best fit among those of the surface's sport (the softball one for a softball field). */
48
+ export function lineupFormation(layer, env) {
49
+ if (!Array.isArray(layer.players))
50
+ throw new TypeError("a lineup needs players: a list of { pos, name, number }");
51
+ if (layer.formation !== undefined)
52
+ return formationOf({ kind: "formation", formation: layer.formation });
53
+ const sports = Object.keys(SURFACES).filter((sport) => SURFACES[sport].includes(env.o.sport));
54
+ const softball = (() => { const league = chooseLeague(env.o).league; return league?.family === "baseball" && league.game === "softball"; })();
55
+ const among = formationsFor(sports).filter((f) => (f.id === "softball-standard") === softball);
56
+ const best = bestFormation(among, layer.players, layer.rotation);
57
+ if (best === undefined)
58
+ throw new RangeError(`There is no formation for ${env.o.sport}: give the formation of your own`);
59
+ return best;
60
+ }
61
+ /** A lineup's places on the surface and the players on them. */
62
+ export function computeLineup(layer, env) {
63
+ const formation = lineupFormation(layer, env);
64
+ const f = formationOf({ kind: "formation", formation });
65
+ const spotsList = spotsOf(f);
66
+ const made = assign(f, layer.players, layer.rotation);
67
+ const stands = new Map();
68
+ const unplaced = [];
69
+ const data = [];
70
+ layer.players.forEach((p, i) => {
71
+ const id = made.of[i];
72
+ if (id === undefined || typeof p !== "object" || p === null) {
73
+ if (typeof p === "object" && p !== null)
74
+ unplaced.push(p);
75
+ return;
76
+ }
77
+ stands.set(id, p);
78
+ data.push({ ...p, position: id, team: p.team ?? layer.side ?? "home" });
79
+ });
80
+ const side = layer.side === "away" ? "away" : "home";
81
+ // The visitors attack the other end, except where the sport does not turn: a football offence goes right unless the layer says, and a defence faces it.
82
+ const attacking = layer.attacking ?? (side === "away" && TURNS.has(env.o.sport) ? "left" : undefined);
83
+ const asLayer = { ...layer, kind: "formation", formation: { ...f, positions: spotsList, extra: undefined }, team: side, data, attacking, labels: undefined, names: undefined, size: layer.size };
84
+ const { spots } = computeFormation(asLayer, env);
85
+ return { formation, layer: asLayer, spots, unplaced, stands };
86
+ }
87
+ const NUMBER_INK = "ky-nm";
88
+ export function drawLineup(layer, env) {
89
+ const made = newMade();
90
+ const words = env.words.team;
91
+ const language = env.o.language === "ja" ? "ja" : "en";
92
+ const { formation, spots, unplaced, stands } = computeLineup(layer, env);
93
+ const k = FEET_PER[unitOfLayer(layer, env)];
94
+ const rel = new Map(spotsOf(formation).map((p) => [p.id, p.size ?? 1]));
95
+ // The marks are as big as is usual and no bigger than the first line's places allow; the second line's are smaller.
96
+ const first = spots.filter((s) => (rel.get(s.id) ?? 1) >= 1);
97
+ let nearest = Infinity;
98
+ first.forEach((a, i) => first.forEach((b, j) => (j > i ? (nearest = Math.min(nearest, Math.hypot(a.at[0] - b.at[0], a.at[1] - b.at[1]))) : undefined)));
99
+ // A football line stands shoulder to shoulder, so sizing by the nearest neighbour would shrink the offence and not the defence: both teams get one size.
100
+ const football = env.o.sport === "nfl" || env.o.sport === "ifaf";
101
+ const base = isNum(layer.size) && layer.size > 0 ? layer.size * k : football ? env.base * 2.4 : Math.min(env.base * 3.4, Math.max(env.base * 2, (Number.isFinite(nearest) ? nearest : env.base * 2) * 0.9));
102
+ const jersey = layer.shape === "jersey";
103
+ const mode = layer.labels === "position" || layer.labels === "number" || layer.labels === "name" || layer.labels === "none" ? layer.labels : "number";
104
+ const showNames = layer.names !== false && mode !== "name";
105
+ const side = layer.side === "away" ? "away" : "home";
106
+ const mine = paintOf({ team: side });
107
+ const marks = [];
108
+ const badges = [];
109
+ const text = [];
110
+ spots.forEach((s) => {
111
+ const player = stands.get(s.id);
112
+ const size = base * (rel.get(s.id) ?? 1);
113
+ const [cx, cy] = s.at;
114
+ const own = paintOf(player === undefined ? undefined : { color: player.color, team: player.team ?? side });
115
+ const cls = [own.cls !== "" ? own.cls : mine.cls, player === undefined ? "ky-e" : ""].filter((c) => c !== "").join(" ");
116
+ const tip = [s.name, player === undefined ? "" : [player.name, player.number].filter((t) => t !== undefined && t !== "").join(" ")].filter((t) => t !== "").join(env.words.said.colon);
117
+ const turn = env.turned ? ` transform="rotate(-90 ${num(cx)} ${num(cy)})"` : "";
118
+ marks.push(titled(jersey ? "path" : "circle", `${attrs(cls, own.css)} ${jersey ? `d="${jerseyPath(cx, cy, size * 1.08)}"` : `cx="${num(cx)}" cy="${num(cy)}" r="${num(size / 2)}"`}`, player?.title ?? tip));
119
+ // What is in the mark.
120
+ let label = "";
121
+ if (mode === "position")
122
+ label = s.id;
123
+ else if (mode === "number")
124
+ label = player === undefined ? s.id : player.number === undefined || player.number === "" ? s.id : String(player.number);
125
+ else if (mode === "name")
126
+ label = player === undefined ? s.id : labelOf(player);
127
+ if (label !== "") {
128
+ const room = jersey ? size * 0.6 : size * 0.86;
129
+ const fs = Math.min(size * 0.46, room / Math.max(0.9, textWidth(label)));
130
+ text.push(`<text class="${player === undefined ? "ky-lt" : NUMBER_INK}" style="font-size:${num(fs)}px" x="${num(cx)}" y="${num(cy + fs * 0.36 + (jersey ? size * 0.07 : 0))}"${turn}>${esc(label)}</text>`);
131
+ }
132
+ // The name, under the mark, as wide as the neighbours allow and no wider: it shrinks, and is squeezed if it must be.
133
+ if (showNames && player?.name !== undefined && player.name !== "") {
134
+ let neighbour = Infinity;
135
+ for (const other of spots)
136
+ if (other !== s)
137
+ neighbour = Math.min(neighbour, Math.hypot(other.at[0] - cx, other.at[1] - cy));
138
+ const room = Math.max(size * 1.05, Math.min(size * 3.4, (Number.isFinite(neighbour) ? neighbour : size * 3) * 0.92));
139
+ // The name shrinks to fit, down to a quarter of the mark's width; a name still too long is cut short with an ellipsis, and one that cannot keep three letters is left out
140
+ // (a football line stands shoulder to shoulder). The whole name is in the tooltip, the description and the table.
141
+ const fs = Math.min(size * 0.4, Math.max(size * 0.26, room / textWidth(player.name)));
142
+ const shown = fit(player.name, room / fs);
143
+ if (shown !== "")
144
+ text.push(`<text class="ky-lt" style="font-size:${num(fs)}px" x="${num(cx)}" y="${num(cy + size * (jersey ? 0.54 : 0.5) + fs * 1.05)}"${turn}>${esc(shown)}</text>`);
145
+ }
146
+ // The captain's armband and the flag.
147
+ if (player?.captain === true) {
148
+ const r = size * 0.2;
149
+ const bx = cx - size * 0.4;
150
+ const by = cy - size * 0.4;
151
+ badges.push(titled("circle", ` class="ky-lc" cx="${num(bx)}" cy="${num(by)}" r="${num(r)}"`, words.captainTip));
152
+ text.push(`<text class="ky-cl" style="font-size:${num(r * 1.25)}px" x="${num(bx)}" y="${num(by + r * 0.45)}"${env.turned ? ` transform="rotate(-90 ${num(bx)} ${num(by)})"` : ""}>${esc(words.captainMark)}</text>`);
153
+ }
154
+ const flag = safeLogo(player?.flag);
155
+ if (flag !== undefined) {
156
+ const h = size * 0.3;
157
+ const w = h * 1.5;
158
+ const fx = cx + size * 0.42 - w / 2;
159
+ const fy = cy + size * 0.4 - h / 2;
160
+ const spin = env.turned ? ` transform="rotate(-90 ${num(fx + w / 2)} ${num(fy + h / 2)})"` : "";
161
+ badges.push(`<g${spin}><image href="${esc(flag)}" x="${num(fx)}" y="${num(fy)}" width="${num(w)}" height="${num(h)}" preserveAspectRatio="xMidYMid slice"/><rect class="ky-lf" x="${num(fx)}" y="${num(fy)}" width="${num(w)}" height="${num(h)}"/></g>`);
162
+ }
163
+ });
164
+ made.drawn = spots.length;
165
+ made.skipped = unplaced.length;
166
+ made.cls = "ky-tm";
167
+ made.opacity = opacityOf(layer.opacity);
168
+ made.style = `--w:${num(base * 0.07)}`;
169
+ made.marks = `<g class="ky-fm${mine.cls === "" ? "" : ` ${mine.cls}`}">${marks.join("")}</g>${badges.length === 0 ? "" : `<g class="ky-lb">${badges.join("")}</g>`}<g text-anchor="middle">${text.join("")}</g>`;
170
+ const name = formation.name[language];
171
+ made.title = fill(words.lineupTitle, { name, n: stands.size });
172
+ const parts = [];
173
+ const list = spots.filter((s) => stands.has(s.id)).map((s) => fill(words.formationItem, { who: labelOf(stands.get(s.id)) || s.id, position: s.name }));
174
+ if (list.length > 0)
175
+ parts.push(fill(words.formationPlayers, { list: list.join(env.words.layers.listSep) }));
176
+ const captain = [...stands.values()].find((p) => p.captain === true);
177
+ if (captain !== undefined)
178
+ parts.push(fill(words.lineupCaptain, { who: labelOf(captain) }));
179
+ if (formation.sport === "volleyball" && isNum(layer.rotation) && layer.rotation >= 1)
180
+ parts.push(fill(words.lineupRotation, { n: Math.floor(layer.rotation) }));
181
+ if (unplaced.length > 0)
182
+ parts.push(fill(words.lineupUnplaced, { list: unplaced.map((p) => labelOf(p) || String(p.pos)).join(env.words.layers.listSep) }));
183
+ parts.push(formation.about[language]);
184
+ made.text = parts.join(env.words.said.gap);
185
+ return made;
186
+ }
187
+ export function lineupTables(layer, env) {
188
+ const w = env.words.team;
189
+ const l = env.words.layers;
190
+ const { formation, spots, unplaced, stands } = computeLineup(layer, env);
191
+ const language = env.o.language === "ja" ? "ja" : "en";
192
+ const frame = FRAMES[formation.frame];
193
+ const yes = w.captainMark;
194
+ const rows = spots.map((s, i) => {
195
+ const p = stands.get(s.id);
196
+ return [i + 1, s.id, s.name, p?.number === undefined ? null : String(p.number), p?.name ?? null, p === undefined ? null : p.captain === true ? yes : null, round(s.given[0], 2), round(s.given[1], 2)];
197
+ });
198
+ unplaced.forEach((p, i) => rows.push([spots.length + i + 1, String(p.pos), positionName(formation.sport, String(p.pos), language), p.number === undefined ? null : String(p.number), p.name ?? null, p.captain === true ? yes : null, null, null]));
199
+ return [
200
+ {
201
+ head: [l.tableNo, w.tablePosition, w.tableName, w.tableNumber, w.tablePlayer, w.tableCaptain, l.tableX, l.tableY],
202
+ rows,
203
+ title: typeof layer.title === "string" && layer.title !== "" ? layer.title : fill(w.lineupTitle, { name: formation.name[language], n: stands.size }),
204
+ note: fill(l.tableFrame, { frame: formation.frame, unit: frame.unit }),
205
+ },
206
+ ];
207
+ }
@@ -2,7 +2,7 @@
2
2
  // players sized and shaded by the passes between them. Written for this package from the plain arithmetic of counting and averaging; the idea
3
3
  // is that of mplsoccer's pass-network example (MIT), and no code of it is used.
4
4
  import { esc, num } from "./format.js";
5
- import { isNum, newMade, opacityOf, place, toArray, unitOfLayer } from "./layers.env.js";
5
+ import { isNum, markAttrs, newMade, opacityOf, place, toArray, unitOfLayer } from "./layers.env.js";
6
6
  import { curveBetween, curvePath, headPath } from "./layers.marks.js";
7
7
  import { fill } from "./strings.js";
8
8
  import { attrs, frameNote, round, teamClass, teamKey, titled } from "./team.common.js";
@@ -151,21 +151,22 @@ export function drawNetwork(layer, env) {
151
151
  const cls = teamClass(l.team);
152
152
  const o = between(alpha, linkT(l), false);
153
153
  const names = { a: nodeName(l.a), b: nodeName(l.b), n: l.n };
154
+ const link = !env.marks ? "" : ` data-mark-id="${esc(`${idText(l.a.id)}-${idText(l.b.id)}`)}" data-index="${links.length}" data-from="${esc(idText(l.a.id))}" data-to="${esc(idText(l.b.id))}"`;
154
155
  if (net.directed) {
155
156
  const curve = curveBetween(a, b, 0.14);
156
157
  const hl = Math.max(w * 3.4, base * 0.9);
157
158
  const stop = nodeWidth(l.b) / 2;
158
159
  const shaft = curve.trim(stop + hl * 0.9);
159
160
  const head = headPath(curve.at(Math.max(0, curve.length - stop)), curve.tangent(Math.max(0, curve.length - stop)), hl, hl * 0.5, false);
160
- links.push(`<g${attrs(cls, "", o)}><title>${esc(fill(words.linkTitleDirected, names))}</title><path class="ky-ln" stroke-width="${num(w)}" d="${curvePath(shaft)}"/><path class="ky-ah" d="${head}"/></g>`);
161
+ links.push(`<g${attrs(cls, "", o)}${link}><title>${esc(fill(words.linkTitleDirected, names))}</title><path class="ky-ln" stroke-width="${num(w)}" d="${curvePath(shaft)}"/><path class="ky-ah" d="${head}"/></g>`);
161
162
  }
162
163
  else
163
- links.push(titled("path", `${attrs(`ky-ln${cls === "" ? "" : ` ${cls}`}`, "", o)} stroke-width="${num(w)}" d="M${num(a[0])} ${num(a[1])}L${num(b[0])} ${num(b[1])}"`, fill(words.linkTitle, names)));
164
+ links.push(titled("path", `${attrs(`ky-ln${cls === "" ? "" : ` ${cls}`}`, "", o)}${link} stroke-width="${num(w)}" d="M${num(a[0])} ${num(a[1])}L${num(b[0])} ${num(b[1])}"`, fill(words.linkTitle, names)));
164
165
  }
165
- const nodes = net.nodes.map((n) => {
166
+ const nodes = net.nodes.map((n, index) => {
166
167
  const at = n.at;
167
168
  const cls = teamClass(n.team);
168
- return titled("circle", `${attrs(cls, "")} cx="${num(at[0])}" cy="${num(at[1])}" r="${num(nodeWidth(n) / 2)}"`, fill(words.nodeTitle, { name: nodeName(n), touches: n.touches, made: n.made, received: n.received }));
169
+ return titled("circle", `${attrs(cls, "")} cx="${num(at[0])}" cy="${num(at[1])}" r="${num(nodeWidth(n) / 2)}"${markAttrs(env, idText(n.id), index, [at[0], at[1]])}`, fill(words.nodeTitle, { name: nodeName(n), touches: n.touches, made: n.made, received: n.received }));
169
170
  });
170
171
  const mode = layer.labels === "name" || layer.labels === "id" || layer.labels === "none" || layer.labels === "number" ? layer.labels : "number";
171
172
  const text = [];
@@ -2,7 +2,7 @@
2
2
  // is near whom) and the convex hull (how much ground the team covers). Written for this package from the plain geometry of team.geometry.ts;
3
3
  // the ideas are those of mplsoccer's Voronoi, Delaunay and convex-hull examples (MIT), and no code of it is used.
4
4
  import { num } from "./format.js";
5
- import { opacityOf, newMade, toArray, unitOfLayer } from "./layers.env.js";
5
+ import { markAttrs, opacityOf, newMade, toArray, unitOfLayer } from "./layers.env.js";
6
6
  import { attrs, dots, frameNote, squareWord, unitWord, groupByTeam, paintOf, placeAll, polygonPath, round, teamClass, teamKey, teamLabel, titled, labelOf } from "./team.common.js";
7
7
  import { convexHull, delaunay, polygonArea, polygonCentroid, triangulationEdges, voronoiCells } from "./team.geometry.js";
8
8
  import { fill } from "./strings.js";
@@ -72,10 +72,10 @@ export function drawVoronoi(layer, env) {
72
72
  if (c.cells[i].length === 0)
73
73
  return;
74
74
  const { cls, css } = paintOf(p.d);
75
- out.push(titled("path", `${attrs(`ky-sp${cls === "" ? "" : ` ${cls}`}`, css)} d="${polygonPath(c.cells[i])}"`, `${[who(p), teamKey(p.d.team) === "" ? "" : teamLabel(teamKey(p.d.team), env)].filter((t) => t !== "").join(", ")}: ${round(100 * c.shares[i])}%`));
75
+ out.push(titled("path", `${attrs(`ky-sp${cls === "" ? "" : ` ${cls}`}`, css)} d="${polygonPath(c.cells[i])}"${markAttrs(env, p.d.id, p.index, [p.x, p.y])}`, `${[who(p), teamKey(p.d.team) === "" ? "" : teamLabel(teamKey(p.d.team), env)].filter((t) => t !== "").join(", ")}: ${round(100 * c.shares[i])}%`));
76
76
  });
77
77
  if (layer.points !== false)
78
- out.push(dots(c.players, env.base * 0.55, () => undefined));
78
+ out.push(dots(c.players, env.base * 0.55, () => undefined, env));
79
79
  made.marks = out.join("");
80
80
  made.title = fill(words.voronoiTitle, { n: c.players.length });
81
81
  const list = [...c.teams].sort((a, b) => b.share - a.share).map((t) => fill(words.shareItem, { name: teamLabel(t.team, env), pct: round(100 * t.share) })).join(env.words.layers.listSep);
@@ -140,7 +140,7 @@ export function drawDelaunay(layer, env) {
140
140
  out.push(titled("path", `${attrs(`ky-sp ky-sp-e${cls === "" ? "" : ` ${cls}`}`, "")} d="${g.edges.map(([a, b]) => `M${num(a[0])} ${num(a[1])}L${num(b[0])} ${num(b[1])}`).join("")}"`, tip));
141
141
  }
142
142
  if (layer.points !== false)
143
- out.push(dots(c.all, env.base * 0.55, () => undefined));
143
+ out.push(dots(c.all, env.base * 0.55, () => undefined, env));
144
144
  made.marks = out.join("");
145
145
  made.title = fill(words.delaunayTitle, { n: c.all.length });
146
146
  const triangles = c.groups.reduce((s, g) => s + g.triangles.length, 0);
@@ -193,7 +193,7 @@ export function drawHull(layer, env) {
193
193
  out.push(titled("path", `${attrs(`ky-sp ky-sp-e${cls === "" ? "" : ` ${cls}`}`, "")} d="M${num(h.corners[0][0])} ${num(h.corners[0][1])}L${num(h.corners[1][0])} ${num(h.corners[1][1])}"`, tip));
194
194
  }
195
195
  if (layer.points !== false)
196
- out.push(dots(c.all, env.base * 0.55, () => undefined));
196
+ out.push(dots(c.all, env.base * 0.55, () => undefined, env));
197
197
  made.marks = out.join("");
198
198
  made.title = fill(words.hullTitle, { n: c.all.length });
199
199
  const parts = [c.hulls.map((h) => fill(words.hullItem, { name: teamLabel(h.team, env), n: h.players.length, area: Math.round(h.area), unit: squareWord(c.unit, env) })).join(env.words.layers.listSep) + env.words.said.stop];
@@ -140,9 +140,11 @@ export interface FormationPosition {
140
140
  id: string;
141
141
  x: number;
142
142
  y: number;
143
+ /** How big the mark of a lineup is here, against the others: 1 by default. A second line's marks are smaller. */
144
+ size?: number;
143
145
  }
144
146
  /** The sports that have formations. */
145
- export type FormationSport = "soccer" | "nhl" | "nba" | "nfl" | "mlb";
147
+ export type FormationSport = "soccer" | "nhl" | "nba" | "nfl" | "mlb" | "rugby" | "afl" | "cricket" | "volleyball" | "handball" | "waterpolo" | "fieldhockey" | "netball";
146
148
  /** 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). */
147
149
  export interface Formation {
148
150
  id: string;
@@ -159,6 +161,8 @@ export interface Formation {
159
161
  ja: string;
160
162
  };
161
163
  positions: readonly FormationPosition[];
164
+ /** Places beside the surface's play that a lineup lists but the side does not field: baseball's designated hitter. A `formation` layer leaves them out; a `lineup` draws them. */
165
+ extra?: readonly FormationPosition[];
162
166
  }
163
167
  /** A player to stand on a position. */
164
168
  export interface FormationPlayer extends MarkDatum {
@@ -186,4 +190,39 @@ export interface FormationLayer extends LayerBase {
186
190
  los?: number;
187
191
  opacity?: number;
188
192
  }
193
+ /** A player of a lineup, placed by a position's code: nothing else about where he stands is needed. */
194
+ export interface LineupPlayer extends MarkDatum {
195
+ /** 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. */
196
+ pos: string | number;
197
+ name?: string;
198
+ number?: string | number;
199
+ /** A flag or crest for a small badge on the mark: an SVG as text, an image `data:` address or a path or address of an image. The package draws no flag of its own. */
200
+ flag?: string;
201
+ /** The captain wears an armband: a small "C" on the mark. */
202
+ captain?: boolean;
203
+ /** 1 by default. Hockey's second line is `line: 2` with the same codes. */
204
+ line?: number;
205
+ }
206
+ /** 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"`. */
207
+ export interface LineupLayer extends LayerBase {
208
+ kind: "lineup";
209
+ /** `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. */
210
+ side?: "home" | "away";
211
+ /** The id of a formation in `FORMATIONS` (`4-2-3-1`), or a formation of your own. Without it, the one of the surface's sport that places the most of the players, the first of its sport's table on a tie. */
212
+ formation?: string | Formation;
213
+ players: readonly LineupPlayer[];
214
+ /** `disc` (the default) or `jersey`. */
215
+ shape?: "disc" | "jersey";
216
+ /** What is written in a mark: the shirt `number` (the default), the `position`'s code, the `name` or `none`. */
217
+ labels?: "number" | "position" | "name" | "none";
218
+ /** Write each name under its mark. On by default. */
219
+ names?: boolean;
220
+ /** The width of a mark, in the layer's units. A share of the surface's longer side by default. */
221
+ size?: number;
222
+ /** A volleyball lineup is listed in the serving order, `P1` to `P6`; `rotation` (1 to 6) is how many rotations on: each player moves one place clockwise. 1 by default. */
223
+ rotation?: number;
224
+ /** The line of scrimmage of a football lineup, as for a `formation` layer. */
225
+ los?: number;
226
+ opacity?: number;
227
+ }
189
228
  export {};