@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
@@ -76,7 +76,7 @@ export function mlbSprayChart(balls, options = {}) {
76
76
  if (at !== undefined)
77
77
  [x, y] = toFrame === undefined ? at : toFrame([at])[0];
78
78
  }
79
- return clean({ x: x ?? null, y: y ?? null, outcome: ballType(b.type), value: b.value, team: b.team, title: b.title });
79
+ return clean({ id: b.id, x: x ?? null, y: y ?? null, outcome: ballType(b.type), value: b.value, team: b.team, title: b.title });
80
80
  });
81
81
  const { counts, placed: count, skipped } = countOutcomes(rows);
82
82
  // The fence of the park in that season, and the balls that came down beyond it: each ball's distance and its angle from dead centre, from the surface's own feet.
package/dist/charts.js CHANGED
@@ -218,7 +218,7 @@ export function nbaShotChart(shots, options = {}) {
218
218
  const end = options.end === "left" ? "left" : "right";
219
219
  // A shot chart puts every shot at the basket it was taken at; a frame fixed to the court says where each one is.
220
220
  const shooter = frame.ends === "shooter";
221
- const data = shots.map((s) => clean({ x: s.x, y: s.y, outcome: s.made === undefined ? s.outcome : s.made ? "made" : "missed", value: s.value, team: s.team, title: s.title }));
221
+ const data = shots.map((s) => clean({ id: s.id, x: s.x, y: s.y, outcome: s.made === undefined ? s.outcome : s.made ? "made" : "missed", value: s.value, team: s.team, title: s.title }));
222
222
  const { counts, placed: count, skipped } = countOutcomes(data);
223
223
  const entries = [{ label: w.made, outcome: "made" }, { label: w.missed, outcome: "missed" }];
224
224
  const o = drawingOptions("nba", options, w.titleNba);
@@ -330,7 +330,7 @@ export function nhlShotMap(shots, options = {}) {
330
330
  const frame = frameOf(options.from, "nhl-api");
331
331
  const o = drawingOptions("nhl", options, w.titleNhl);
332
332
  const folded = options.fold === true;
333
- const given = shots.map((s) => clean({ x: s.x, y: s.y, outcome: hockeyOutcome(s), value: s.xg, team: s.team, title: s.title }));
333
+ const given = shots.map((s) => clean({ id: s.id, x: s.x, y: s.y, outcome: hockeyOutcome(s), value: s.xg, team: s.team, title: s.title }));
334
334
  const data = folded ? foldToRight(given, frame, o) : given;
335
335
  const { counts, placed: count, skipped } = countOutcomes(data);
336
336
  const hasXg = data.some((d) => isNum(d.value));
@@ -441,7 +441,7 @@ export function nflPassChart(passes, options = {}) {
441
441
  const data = rows.map(({ p, outcome }, i) => {
442
442
  const place_ = at[i];
443
443
  const air = isNum(p.airYards) ? p.airYards : place_ === undefined ? undefined : round(inYards(place_[0] - losFeet), 1);
444
- return clean({ x: p.x, y: p.y, outcome, value: air, team: p.team, title: p.title });
444
+ return clean({ id: p.id, x: p.x, y: p.y, outcome, value: air, team: p.team, title: p.title });
445
445
  });
446
446
  const { counts, placed: count, skipped } = countOutcomes(data);
447
447
  const entries = [["complete", w.complete], ["incomplete", w.incomplete], ["touchdown", w.touchdown], ["interception", w.interception]].filter(([k]) => counts.has(k) || counts.size === 0 || ["complete", "incomplete"].includes(k)).map(([outcome, label]) => ({ label, outcome }));
@@ -480,7 +480,7 @@ export function nflPassChart(passes, options = {}) {
480
480
  layers.push(line);
481
481
  }
482
482
  if (options.arrows === true) {
483
- const arrows = rows.filter(({ p }) => isNum(p.fromX) && isNum(p.fromY) && isNum(p.x) && isNum(p.y)).map(({ p, outcome }) => clean({ x: p.fromX, y: p.fromY, x2: p.x, y2: p.y, outcome, team: p.team, title: p.title }));
483
+ const arrows = rows.filter(({ p }) => isNum(p.fromX) && isNum(p.fromY) && isNum(p.x) && isNum(p.y)).map(({ p, outcome }) => clean({ id: p.id, x: p.fromX, y: p.fromY, x2: p.x, y2: p.y, outcome, team: p.team, title: p.title }));
484
484
  layers.push(clean({ kind: "arrows", id: "arrows", name: w.passes, ...where, data: arrows, style: { width: 0.7, opacity: 0.7, curve: 0.08, outcomes: { touchdown: {}, interception: {}, complete: {}, incomplete: { hollow: true } } } }));
485
485
  }
486
486
  const values = data.map((d) => d.value).filter(isNum);
@@ -65,7 +65,7 @@ export function soccerShotMap(shots, options = {}) {
65
65
  });
66
66
  const attacking = options.attacking === "left" ? "left" : undefined;
67
67
  const where = clean({ from: fromOf(typeof provider === "object" ? provider : undefined, frame), attacking });
68
- const data = shots.map((s) => clean({ x: s.x, y: s.y, outcome: outcomeOf(s), value: s.xg, team: s.team, title: s.title }));
68
+ const data = shots.map((s) => clean({ id: s.id, x: s.x, y: s.y, outcome: outcomeOf(s), value: s.xg, team: s.team, title: s.title }));
69
69
  const { counts, placed: count, skipped } = countOutcomes(data);
70
70
  const hasXg = data.some((d) => isNum(d.value));
71
71
  const sized = options.xg === false ? false : hasXg;
@@ -140,7 +140,7 @@ export function soccerGoalMouth(shots, options = {}) {
140
140
  };
141
141
  const id = options.id === undefined ? undefined : options.id;
142
142
  const surface = { sport: "soccer", pitch: { length: viewW, width: viewH, unit: "m" }, units: "m", language, ...(id === undefined ? {} : { id }), margin: 0 };
143
- const rows = shots.map((s) => clean({ x: isNum(s.goalY) ? round(s.goalY * k, 3) : null, y: isNum(s.goalZ) ? round(s.goalZ * k, 3) : null, outcome: outcomeOf(s), value: s.xg, team: s.team, title: s.title }));
143
+ const rows = shots.map((s) => clean({ id: s.id, x: isNum(s.goalY) ? round(s.goalY * k, 3) : null, y: isNum(s.goalZ) ? round(s.goalZ * k, 3) : null, outcome: outcomeOf(s), value: s.xg, team: s.team, title: s.title }));
144
144
  const { counts, placed: placedCount, skipped } = countOutcomes(rows);
145
145
  const hasXg = rows.some((r) => isNum(r.value));
146
146
  const sized = options.xg === false ? false : options.xg === true || hasXg;
@@ -150,7 +150,7 @@ export function soccerGoalMouth(shots, options = {}) {
150
150
  const style = { outcomes: LOOKS, opacity: 0.92, ...(sized ? { size: { domain: [0, xgMax], range: [0.25, 0.8] } } : { size: 0.45 }) };
151
151
  const layer = clean({ kind: "points", id: "goal-shots", name: w.shots, description: describeCounts(entries, counts, all), from: frame, units: "m", data: rows, style, clip: "none" });
152
152
  const layers = [layer];
153
- const rendered = renderLayers(surface, layers, false);
153
+ const rendered = renderLayers(surface, layers, false, undefined, undefined, () => undefined);
154
154
  const F = FEET_PER_METRE;
155
155
  const x0 = ((viewW - goalW) / 2) * F;
156
156
  const x1 = x0 + goalW * F;
@@ -48,6 +48,8 @@ export interface ChartOptions {
48
48
  }
49
49
  /** A shot as stats.nba.com's shot chart gives it: `LOC_X` and `LOC_Y` and whether it went in. */
50
50
  export interface NbaShot {
51
+ /** Your id for the shot, written on its mark as `data-mark-id`. */
52
+ id?: string | number;
51
53
  x: number | null;
52
54
  y: number | null;
53
55
  /** True for a shot that went in. Or give `outcome`. */
@@ -98,6 +100,8 @@ export interface NbaTotals {
98
100
  }
99
101
  /** A shot as the NHL's play-by-play gives it. */
100
102
  export interface NhlShot {
103
+ /** Your id for the shot, written on its mark as `data-mark-id`. */
104
+ id?: string | number;
101
105
  x: number | null;
102
106
  y: number | null;
103
107
  /** `goal`, `saved` (a shot on goal), `missed`, `blocked`, or `shot` for an attempt of no known result. */
@@ -152,6 +156,8 @@ export interface NhlTotals {
152
156
  }
153
157
  /** A ball in play. Give `x` and `y` in the frame, or for Retrosheet a `zone` (the location code, `8D`). */
154
158
  export interface Ball {
159
+ /** Your id for the shot, written on its mark as `data-mark-id`. */
160
+ id?: string | number;
155
161
  x?: number | null;
156
162
  y?: number | null;
157
163
  /** A Retrosheet hit-location code in place of `x` and `y`. */
@@ -197,6 +203,8 @@ export interface MlbTotals {
197
203
  }
198
204
  /** A pass. The destination is `x` and `y`; the passer's place is `fromX` and `fromY` when you have it. */
199
205
  export interface NflPass {
206
+ /** Your id for the shot, written on its mark as `data-mark-id`. */
207
+ id?: string | number;
200
208
  /** Where the ball came down or was caught, in the frame. */
201
209
  x: number | null;
202
210
  y: number | null;
@@ -246,6 +254,8 @@ export interface NflTotals {
246
254
  }
247
255
  /** A shot. The place is in the provider's frame; `goalY` and `goalZ` say where it crossed the goal line. */
248
256
  export interface SoccerShot {
257
+ /** Your id for the shot, written on its mark as `data-mark-id`. */
258
+ id?: string | number;
249
259
  x: number | null;
250
260
  y: number | null;
251
261
  /** `goal`, `saved`, `blocked`, `missed` or `post`. */
package/dist/colour.d.ts CHANGED
@@ -17,13 +17,13 @@ export declare function luminance(rgb: [number, number, number]): number;
17
17
  /** White or near-black, whichever reads better on the colour. White when the colour cannot be read. */
18
18
  export declare function inkOn(color: string | undefined): string;
19
19
  /**
20
- * A logo's address as it may be written into an image's `href`, or `undefined`. An `https:` or `http:` address, a
20
+ * 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
21
21
  * relative path, an image `data:` address, or an SVG document as text (written as an image, which cannot run scripts).
22
22
  * @example
23
23
  * ```ts
24
- * import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
25
- * console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
24
+ * import { safePicture } from "@johnmorrisdotca/kyogijo-stadiums";
25
+ * console.log(safePicture("https://example.com/gulls.svg"), safePicture("javascript:alert(1)"));
26
26
  * // https://example.com/gulls.svg undefined
27
27
  * ```
28
28
  */
29
- export declare function safeLogo(value: unknown): string | undefined;
29
+ export declare function safePicture(value: unknown): string | undefined;
package/dist/colour.js CHANGED
@@ -64,16 +64,16 @@ export function inkOn(color) {
64
64
  return luminance(rgb) > 0.4 ? "#161a18" : "#ffffff";
65
65
  }
66
66
  /**
67
- * A logo's address as it may be written into an image's `href`, or `undefined`. An `https:` or `http:` address, a
67
+ * 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
68
68
  * relative path, an image `data:` address, or an SVG document as text (written as an image, which cannot run scripts).
69
69
  * @example
70
70
  * ```ts
71
- * import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
72
- * console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
71
+ * import { safePicture } from "@johnmorrisdotca/kyogijo-stadiums";
72
+ * console.log(safePicture("https://example.com/gulls.svg"), safePicture("javascript:alert(1)"));
73
73
  * // https://example.com/gulls.svg undefined
74
74
  * ```
75
75
  */
76
- export function safeLogo(value) {
76
+ export function safePicture(value) {
77
77
  if (typeof value !== "string")
78
78
  return undefined;
79
79
  const text = value.trim();
package/dist/crowd.d.ts CHANGED
@@ -28,7 +28,7 @@ export type Outline = {
28
28
  export interface StandRing {
29
29
  /** `lower`, `upper`, `courtside`, `grandstand` or `bleachers`, with the tier's number where a ring has parts. */
30
30
  id: string;
31
- tier: "courtside" | "lower" | "upper" | "grandstand" | "bleachers" | "wall";
31
+ tier: "courtside" | "gallery" | "lower" | "upper" | "grandstand" | "bleachers" | "wall";
32
32
  /** The band is `region` less `hole` (nothing for a polygon that is the band itself). */
33
33
  region: Outline;
34
34
  /** Further pieces of the band that are apart from `region` (a ballpark's bleachers either side of the batter's eye). */
@@ -90,6 +90,8 @@ export interface CrowdStands {
90
90
  };
91
91
  /** The seats of all the stands, from the model: what a density is a share of when no capacity is given. */
92
92
  seats: number;
93
+ /** The stands are a hall's gallery (a dojo's balcony): a thin band close to the walls, plain seats, a few neutral people and no sides. */
94
+ gallery?: boolean;
93
95
  }
94
96
  /** The crowd options read: every part has its value. */
95
97
  export interface Crowd {
@@ -145,6 +147,14 @@ export interface LayoutInput {
145
147
  awaySide: Side;
146
148
  /** A ground's key, for the stadium a soccer pitch stands in. */
147
149
  ground?: string;
150
+ /** The surface lies on an arena floor that fills most of the hall: the sport's closer stands, where it has them. */
151
+ arena?: boolean;
152
+ /** A hall's gallery instead of the sport's stands (a dojo's balcony, from `dojoRoom`): the distance of its first row from the surface, its depth and the heights of its first and last rows, in feet. */
153
+ gallery?: {
154
+ offset: number;
155
+ depth: number;
156
+ height: [number, number];
157
+ };
148
158
  /** The ballpark's fence as points on the field, for a ballpark (see `parkLayout`). */
149
159
  fence?: readonly Pt[];
150
160
  /** The foul lines' length at the poles and the backstop's distance, for a ballpark. */
@@ -14,7 +14,7 @@ const n1 = (v) => {
14
14
  return t === "-0" ? "0" : t.replace(/^0\./, ".");
15
15
  };
16
16
  /** One tile of people: the seats of a block 18 feet square, each taken with the probability `density`, in the colour of a team with the probability `share` and else in a neutral. */
17
- function tile(seed, density, share, team) {
17
+ function tile(seed, density, share, team, neutral = false) {
18
18
  const next = generator(seed);
19
19
  const shirts = new Map();
20
20
  const heads = new Map();
@@ -42,16 +42,16 @@ function tile(seed, density, share, team) {
42
42
  }
43
43
  let out = "";
44
44
  if (empty.length > 0)
45
- out += el("path", { class: "ky-cr-seat", d: empty.join("") });
45
+ out += el("path", { class: neutral ? "ky-cr-seat ky-bud-gseat" : "ky-cr-seat", d: empty.join("") });
46
46
  for (const [key, list] of shirts)
47
47
  out += key.startsWith("ky-") ? el("path", { class: key, d: list.join("") }) : el("path", { stroke: key, d: list.join("") });
48
48
  const headPaths = [...heads].map(([colour, list]) => el("path", { stroke: colour, "stroke-width": 1.15, d: list.join("") })).join("");
49
49
  return el("g", { fill: "none", "stroke-linecap": "round", "stroke-width": 2.3 }, out + headPaths);
50
50
  }
51
51
  /** The tiles a crowd needs: the home tile, the home tile of the upper tiers (emptier) and the visitors', and the display tiles. */
52
- function patterns(id, crowd, density, tifo) {
52
+ function patterns(id, crowd, density, tifo, neutral = false) {
53
53
  const make = (suffix, body, size = TILE) => el("pattern", { id: `${id}-cr-${suffix}`, width: size, height: size, patternUnits: "userSpaceOnUse" }, body);
54
- let out = make("h", tile(crowd.seed * 7 + 1, density, 0.88, "ky-cr-h")) + make("a", tile(crowd.seed * 7 + 3, Math.min(1, density * 1.04), 0.94, "ky-cr-a"));
54
+ let out = make("h", tile(crowd.seed * 7 + 1, density, neutral ? 0 : 0.88, "ky-cr-h", neutral)) + make("a", tile(crowd.seed * 7 + 3, Math.min(1, density * 1.04), 0.94, "ky-cr-a"));
55
55
  if (tifo !== undefined) {
56
56
  const stripes = (cls) => el("rect", { class: cls, width: 3, height: 6 }) + el("rect", { class: "ky-cr-tw", x: 3, width: 3, height: 6 });
57
57
  const checks = (cls) => el("rect", { class: cls, width: 3, height: 3 }) + el("rect", { class: cls, x: 3, y: 3, width: 3, height: 3 }) + el("rect", { class: "ky-cr-tw", x: 3, width: 3, height: 3 }) + el("rect", { class: "ky-cr-tw", y: 3, width: 3, height: 3 });
@@ -133,10 +133,11 @@ export function drawCrowd(stands, o) {
133
133
  for (const ring of stands.rings) {
134
134
  // The upper tiers are a little emptier: their paint is the same tile with the empty seats' bed showing through a veil.
135
135
  // The bed of seats under the tile, so that no seam of the pattern shows light.
136
+ const bed = stands.gallery === true ? "ky-cr-bed ky-bud-gbed" : "ky-cr-bed";
136
137
  if (fine)
137
- body += el("path", { class: "ky-cr-bed", d: path(ring) }) + el("path", { class: "ky-cr-people", d: path(ring), fill: `url(#${id}-cr-h)` });
138
+ body += el("path", { class: bed, d: path(ring) }) + el("path", { class: "ky-cr-people", d: path(ring), fill: `url(#${id}-cr-h)` });
138
139
  else
139
- body += el("path", { class: "ky-cr-bed", d: path(ring) }) + el("path", { class: "ky-cr-blk", d: path(ring), "fill-opacity": num(0.28 + 0.6 * density * ring.fill) });
140
+ body += el("path", { class: bed, d: path(ring) }) + el("path", { class: stands.gallery === true ? "ky-cr-blk ky-bud-gblk" : "ky-cr-blk", d: path(ring), "fill-opacity": num(0.28 + 0.6 * density * ring.fill) });
140
141
  if (fine && ring.fill < 0.95)
141
142
  body += el("path", { class: "ky-cr-bed", d: path(ring), "fill-opacity": num(Math.min(0.5, (1 - ring.fill) * 1.6)) });
142
143
  }
@@ -144,9 +145,14 @@ export function drawCrowd(stands, o) {
144
145
  body += sector(s, id, fine, density);
145
146
  if (fine)
146
147
  for (const ring of stands.rings)
147
- body += rows(ring) + aisles(ring);
148
+ body += rows(ring) + (stands.gallery === true ? "" : aisles(ring));
148
149
  // Depth: the front wall of a tier throws a shade on its first rows (and the ground in front of it), the roof's edge on the last rows of the upper tier.
149
- if (fine) {
150
+ // A gallery's railing: a line along its front edge, over the seats.
151
+ if (stands.gallery === true)
152
+ for (const ring of stands.rings)
153
+ if (ring.hole !== null)
154
+ body += el("path", { class: "ky-bud-rail", d: outlinePath(ring.hole), fill: "none" });
155
+ if (fine && stands.gallery !== true) {
150
156
  for (const ring of stands.rings) {
151
157
  if (ring.hole !== null)
152
158
  body += el("path", { d: outlinePath(ring.hole), fill: "none", stroke: "#000", "stroke-opacity": 0.2, "stroke-width": 7 });
@@ -160,7 +166,7 @@ export function drawCrowd(stands, o) {
160
166
  const [x0, y0, x1, y1] = g.box;
161
167
  body += el("rect", { class: `ky-cr-${g.kind}`, x: n1(x0), y: n1(y0), width: n1(x1 - x0), height: n1(y1 - y0), rx: g.kind === "bench" ? 1 : undefined });
162
168
  }
163
- return { defs: (fine ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined) : ""), markup: el("g", { class: "ky-crowd" }, body), over };
169
+ return { defs: (fine ? patterns(id, crowd, density, tifo.home || tifo.away ? tifo : undefined, stands.gallery === true) : ""), markup: el("g", { class: "ky-crowd" }, body), over };
164
170
  }
165
171
  /** A block of visitors or a card display, a polygon over the band it is in. */
166
172
  function sector(s, id, fine, density) {
package/dist/crowd.js CHANGED
@@ -91,6 +91,15 @@ const PROFILES = {
91
91
  cricket: { shape: "bowl", side: 30, end: 30, bench: 0, corner: 0, tiers: [lower(66), upper(73, 125)], oval: true, away: "end" },
92
92
  fieldhockey: { shape: "bowl", side: 20, end: 26, bench: 5, corner: 30, tiers: [lower(46, 3, 24), upper(53, 87, 32, 54)], away: "end" },
93
93
  handball: { shape: "bowl", side: 14, end: 18, bench: 4, corner: 16, tiers: [courtside(6), { id: "lower", from: 10, to: 46, height: [3, 22], fill: 1 }, upper(53, 97, 30, 56)], away: "corner" },
94
+ // The budō (ours: no rule gives the stands). The competition areas of judo, karate and kendo stand in an arena's bowl, as the Nippon Budokan's does round its octagonal floor (the Budokan's leaflet: 2,946 temporary
95
+ // seats on the arena, 2,959 to 3,199 on the first floor, 7,876 on the second and third); sumo's dohyō stands in the middle of a hall of box seats and a gallery (the Ryōgoku Kokugikan's); a kyūdōjō's
96
+ // spectators stand along the yamichi; a dojo's gallery looks down on the mats from above, as the Kodokan's does from the eighth floor.
97
+ judo: { shape: "bowl", side: 36, end: 36, bench: 6, corner: 50, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner", arena: { side: 8, end: 8, bench: 0, corner: 56, tiers: [lower(34, 3, 24), upper(40, 74, 32, 62)] } },
98
+ karate: { shape: "bowl", side: 36, end: 36, bench: 6, corner: 50, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner", arena: { side: 8, end: 8, bench: 0, corner: 56, tiers: [lower(34, 3, 24), upper(40, 74, 32, 62)] } },
99
+ kendo: { shape: "bowl", side: 36, end: 36, bench: 6, corner: 50, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner", arena: { side: 8, end: 8, bench: 0, corner: 56, tiers: [lower(34, 3, 24), upper(40, 74, 32, 62)] } },
100
+ sumo: { shape: "bowl", side: 58, end: 58, bench: 0, corner: 56, tiers: [{ id: "lower", from: 0, to: 54, height: [1, 8], fill: 1 }, upper(62, 128, 26, 62)], away: "corner" },
101
+ kyudo: { shape: "sides", side: 10, end: 8, bench: 0, corner: 6, tiers: [lower(24, 2, 8), upper(30, 52, 12, 22)], away: "corner" },
102
+ dojo: { shape: "bowl", side: 5, end: 5, bench: 0, corner: 8, tiers: [lower(26, 2, 14), upper(32, 58, 20, 36)], away: "corner" },
94
103
  };
95
104
  /** 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. */
96
105
  export const GRIDIRON_GROUNDS = Object.freeze({
@@ -297,8 +306,12 @@ function offsets(p, box, stadium) {
297
306
  }
298
307
  /** The layout of a sport's stands round its surface: rings, covers, sectors and gaps. For a ballpark see `parkLayout`. */
299
308
  export function layoutOf(input) {
300
- const { sport, box, crowd } = input;
301
- const base = PROFILES[sport];
309
+ const { sport, box } = input;
310
+ const gal = input.gallery;
311
+ // A gallery has no home and no visitors, no card display and no tunnel, camera or benches.
312
+ const crowd = gal === undefined ? input.crowd : { ...input.crowd, awayEnd: "none", tifo: undefined, gaps: false, shape: input.crowd.shape === "sides" ? "sides" : "bowl" };
313
+ const base0 = gal === undefined ? PROFILES[sport] : { shape: "bowl", side: gal.offset, end: gal.offset, bench: 0, corner: 0, tiers: [{ id: "gallery", from: 0, to: gal.depth, height: gal.height, fill: 1 }], away: "corner" };
314
+ const base = input.arena === true && base0.arena !== undefined ? { ...base0, ...base0.arena, tiers: base0.arena.tiers ?? base0.tiers } : base0;
302
315
  const host = sport === "soccer" ? (crowd.stadium === "nfl" || crowd.stadium === "cfl" ? crowd.stadium : crowd.stadium === "auto" && input.ground !== undefined ? GRIDIRON_GROUNDS[input.ground] : undefined) : undefined;
303
316
  const stadium = sport === "soccer" ? host ?? "purpose" : undefined;
304
317
  const p = stadium === "nfl" || stadium === "cfl" ? { ...PROFILES[stadium], shape: base.shape, away: base.away } : base;
@@ -360,6 +373,8 @@ export function layoutOf(input) {
360
373
  const stands = { sport, shape, box, rings, covers, concourses, sectors: [], gaps: [], reach: reachOf, seats: rings.reduce((n, r) => n + r.seats, 0) };
361
374
  if (stadium !== undefined)
362
375
  stands.stadium = stadium;
376
+ if (gal !== undefined)
377
+ stands.gallery = true;
363
378
  if (stadium === "nfl" || stadium === "cfl") {
364
379
  const f = gridiron(stadium);
365
380
  const field = { x: cx - f.length / 2, y: cy - f.width / 2, w: f.length, h: f.width };