@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
@@ -15,6 +15,7 @@ import { WATER_POLO } from "./pool.figures.js";
15
15
  import { swimPlan } from "./pool.js";
16
16
  import { athleticsLanes, athleticsLeague } from "./athletics/plan.js";
17
17
  import { skatingPlan } from "./skating/plan.js";
18
+ import { budoPlan } from "./budo/plan.js";
18
19
  import { laneEdges } from "./skating/track.js";
19
20
  import { iceRadius } from "./skating/long.js";
20
21
  import { LEAGUES, chooseLeague, cricketOf } from "./leagues.js";
@@ -36,6 +37,7 @@ export const ZONE_SETS = Object.freeze({
36
37
  "pool-lanes": { sports: ["swimming"], title: "Lanes: each lane of the pool, the whole length of it", source: "docs/sources.md#zone-sets" },
37
38
  "water-polo-areas": { sports: ["waterpolo"], title: "The goal area, the ground from 2 to 6 m, and the rest of the field, at each end", source: "docs/sources.md#zone-sets" },
38
39
  "pool-thirds": { sports: ["diving", "artisticswimming"], title: "Thirds along the pool, from the wall the boards or the judges stand at", source: "docs/sources.md#zone-sets" },
40
+ "budo-areas": { sports: ["judo", "karate", "kendo", "sumo", "kyudo", "dojo"], title: "The surface in its areas: the contest and the safety areas, the boundary band, the clay and the ring of a dohyō, the shajō, the yamichi and the azuchi, a dojo's floor", source: "docs/sources.md#zone-sets" },
39
41
  "field-thirds": { sports: ["lacrosse", "rugby", "afl", "gaelic", "fieldhockey", "handball", "netball"], title: "Thirds: defensive, middle and attacking", source: "docs/sources.md#zone-sets" },
40
42
  "skating-track": { sports: ["skating"], title: "The rink in parts: the finish straight, the first curve, the crossing straight, the second curve and the infield (a figure skating rink in thirds)", source: "docs/sources.md#zone-sets" },
41
43
  "athletics-track": { sports: ["athletics"], title: "The track in parts: the finish straight, the first bend, the back straight, the second bend, and the infield", source: "docs/sources.md#zone-sets" },
@@ -197,7 +199,7 @@ function nhlZones(id, ctx) {
197
199
  return out;
198
200
  }
199
201
  /** The distance from home plate along a ray at `angle` (degrees, 0 straight out to centre field, negative to left) to the edge of the infield: the arc 95 feet round the pitcher's plate. */
200
- function infieldReach(angle) {
202
+ export function infieldReach(angle) {
201
203
  const a = (angle * Math.PI) / 180;
202
204
  const along = MLB.pitcher * Math.cos(a);
203
205
  return along + Math.sqrt(along * along + MLB.skinRadius * MLB.skinRadius - MLB.pitcher * MLB.pitcher);
@@ -463,7 +465,7 @@ function poolLanes(ctx) {
463
465
  }
464
466
  /**
465
467
  * A water polo field at each end: the goal area (to the 2 m line), the ground from 2 to 6 m (where the penalty foul applies), and the rest, split at the half distance line. The marks are the rules' (Part Six,
466
- * Appendix 4); the zones are ours.
468
+ * Article 21.7, February 2026; Appendix 4, 20.4.1.5 in the June 2025 edition); the zones are ours.
467
469
  */
468
470
  function waterPoloAreas(ctx) {
469
471
  const { box } = ctx.outline;
@@ -556,6 +558,56 @@ function skatingZones(ctx) {
556
558
  { id: "second-curve", name: w.secondCurve, d: west, label: [cx - (S / 2 + (R + Ro) / 2) * k, cy], room: [(Ro - R) * k, R * k], has: (x, y) => { const [u, v] = m(x, y); return onIce(u, v) && u < -S / 2; } },
557
559
  ];
558
560
  }
561
+ /**
562
+ * The budō surface in its areas, each as the plan lays it out (budo/plan.ts): a judo or karate area's mats by the colour they are laid in (the safety area under, the contest area, and for karate the boundary band, over
563
+ * it); a dohyō's clay, the jano-me and the ring; a kyūdōjō's shajō, yamichi and azuchi; a kendo floor and a dojo's in two parts (a dojo's kamiza end and the rest). The zones are ours: no rule names them.
564
+ */
565
+ function budoZones(ctx) {
566
+ const plan = budoPlan({ sport: ctx.o.sport, league: ctx.o.league, budo: ctx.o.budo, dojo: ctx.o.dojo });
567
+ const k = FEET_PER_METRE;
568
+ const ox = plan.box.x0;
569
+ const oy = plan.box.y0;
570
+ const w = ctx.words;
571
+ const box = (b) => [(b.x0 - ox) * k, (b.y0 - oy) * k, (b.x1 - ox) * k, (b.y1 - oy) * k];
572
+ const one = (id, name, b) => {
573
+ const [x0, y0, x1, y1] = box(b);
574
+ return rect(id, name, x0, y0, x1, y1, true, true);
575
+ };
576
+ const whole = one("surface", w.budoSafety, plan.box);
577
+ // The zones do not overlap: a zone is what is in it and in none of the zones laid over it.
578
+ const inBox = (b, x, y) => x >= (b.x0 - ox) * k - 1e-9 && x <= (b.x1 - ox) * k + 1e-9 && y >= (b.y0 - oy) * k - 1e-9 && y <= (b.y1 - oy) * k + 1e-9;
579
+ const without = (z, over) => ({ ...z, has: (x, y) => z.has(x, y) && !over.some((b) => inBox(b, x, y)) });
580
+ if (plan.judo !== undefined)
581
+ return [without(one("safety-area", w.budoSafety, plan.judo.tatami), plan.judo.contests), ...plan.judo.contests.map((c, i) => one(`contest-area-${i + 1}`, w.budoContest, c))];
582
+ if (plan.karate !== undefined)
583
+ return [without(one("safety-area", w.budoSafety, plan.karate.mats), plan.karate.areas), ...plan.karate.areas.map((c, i) => without(one(`boundary-band-${i + 1}`, w.budoBand, c), plan.karate.inner)), ...plan.karate.inner.map((c, i) => one(`contest-area-${i + 1}`, w.budoContest, c))];
584
+ if (plan.sumo !== undefined) {
585
+ const L = plan.sumo;
586
+ const [cx, cy] = [(L.centre[0] - ox) * k, (L.centre[1] - oy) * k];
587
+ const ring = (r) => `M${num(cx - r * k)} ${num(cy)}A${num(r * k)} ${num(r * k)} 0 1 0 ${num(cx + r * k)} ${num(cy)}A${num(r * k)} ${num(r * k)} 0 1 0 ${num(cx - r * k)} ${num(cy)}Z`;
588
+ const jano = L.outer + plan.figures.jano;
589
+ const dist = (x, y) => Math.hypot(x - cx, y - cy) / k;
590
+ const base = one("clay", w.budoClay, L.platform);
591
+ const inner = L.ring + plan.figures.bale.width;
592
+ return [
593
+ { ...base, has: (x, y) => base.has(x, y) && dist(x, y) > jano },
594
+ { id: "jano-me", name: w.budoJano, d: ring(jano), label: [cx, cy - ((L.outer + jano) / 2) * k], room: [(jano - L.outer) * k, (jano - L.outer) * k], has: (x, y) => dist(x, y) <= jano && dist(x, y) > inner },
595
+ { id: "ring", name: w.budoRing, d: ring(L.ring), label: [cx, cy - L.ring * 0.5 * k], room: [L.ring * k, L.ring * k * 0.4], has: (x, y) => dist(x, y) <= inner },
596
+ ];
597
+ }
598
+ if (plan.kyudo !== undefined) {
599
+ const parts = [plan.kyudo.shajo, plan.kyudo.yamichi, plan.kyudo.azuchi];
600
+ return [without({ ...whole, id: "range", name: w.budoRest }, parts), one("shajo", w.budoShajo, plan.kyudo.shajo), without(one("yamichi", w.budoYamichi, plan.kyudo.yamichi), [plan.kyudo.shajo, plan.kyudo.azuchi]), without(one("azuchi", w.budoAzuchi, plan.kyudo.azuchi), [plan.kyudo.shajo, plan.kyudo.yamichi])];
601
+ }
602
+ const ranges = plan.ranges;
603
+ const kamiza = ranges.kamiza;
604
+ if (plan.dojo !== undefined && kamiza !== undefined && !("left" in kamiza))
605
+ return [without({ ...whole, id: "floor", name: w.budoRest }, [kamiza]), one("kamiza", w.budoKamiza, kamiza)];
606
+ const halves = ranges.half;
607
+ if (halves !== undefined && "left" in halves)
608
+ return [one("left", w.endLeft, halves.left), without(one("right", w.endRight, halves.right), [halves.left])];
609
+ return [whole];
610
+ }
559
611
  /** The zones of a set on the surface the context describes. Throws for a set that is not for the surface's sport. */
560
612
  export function buildZoneSet(id, ctx) {
561
613
  if (!Object.prototype.hasOwnProperty.call(ZONE_SETS, id))
@@ -567,6 +619,6 @@ export function buildZoneSet(id, ctx) {
567
619
  const { zones, which } = retrosheetZones(ctx);
568
620
  return { id, zones, which };
569
621
  }
570
- const zones = id === "skating-track" ? skatingZones(ctx) : id === "athletics-track" ? athleticsZones(ctx) : id === "field-thirds" ? fieldThirds(ctx) : id === "cricket-ground" ? cricketZones(ctx) : id.startsWith("soccer") ? soccerZones(id, ctx) : id === "nba-shot" ? nbaZones(ctx) : id.startsWith("nhl") ? nhlZones(id, ctx) : id === "baseball-spray" ? baseballSpray(ctx) : id === "curling-house" ? curlingZones(ctx) : id === "net-court" ? netZones(ctx) : id === "wall-court" ? wallZones(ctx) : id === "roundnet-rings" ? roundnetZones(ctx) : id === "pool-lanes" ? poolLanes(ctx) : id === "water-polo-areas" ? waterPoloAreas(ctx) : id === "pool-thirds" ? poolThirds(ctx) : footballPass(ctx);
622
+ const zones = id === "budo-areas" ? budoZones(ctx) : id === "skating-track" ? skatingZones(ctx) : id === "athletics-track" ? athleticsZones(ctx) : id === "field-thirds" ? fieldThirds(ctx) : id === "cricket-ground" ? cricketZones(ctx) : id.startsWith("soccer") ? soccerZones(id, ctx) : id === "nba-shot" ? nbaZones(ctx) : id.startsWith("nhl") ? nhlZones(id, ctx) : id === "baseball-spray" ? baseballSpray(ctx) : id === "curling-house" ? curlingZones(ctx) : id === "net-court" ? netZones(ctx) : id === "wall-court" ? wallZones(ctx) : id === "roundnet-rings" ? roundnetZones(ctx) : id === "pool-lanes" ? poolLanes(ctx) : id === "water-polo-areas" ? waterPoloAreas(ctx) : id === "pool-thirds" ? poolThirds(ctx) : footballPass(ctx);
571
623
  return { id, zones, which: lastWins(zones) };
572
624
  }
package/dist/homeside.js CHANGED
@@ -42,6 +42,13 @@ export const HOME_FIRST = Object.freeze({
42
42
  athletics: false,
43
43
  // A skating race has no home and away.
44
44
  skating: false,
45
+ // A budō contest is between the first contestant called and the second (judo: blue to the referee's right, white to his left; karate: aka and ao; kendo: aka and shiro; sumo: east and west): no home side.
46
+ judo: false,
47
+ karate: false,
48
+ kendo: false,
49
+ sumo: false,
50
+ kyudo: false,
51
+ dojo: false,
45
52
  });
46
53
  /** The leagues whose convention is not their sport's: the international federation's lacrosse lists the home team first. */
47
54
  export const HOME_FIRST_LEAGUES = Object.freeze({
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export { COLOR_PROPERTIES, SPORTS, crowdStands, describe, drawBadge, drawKyogijo, drawScore, drawSurface, isPortrait, statusLabel, statusText, winnerOf } from "./draw.ts";
2
2
  export { HOME_FIRST, HOME_FIRST_LEAGUES, homeSideFor } from "./homeside.ts";
3
- export { inkOn, luminance, rgbOf, safeColour, safeLogo } from "./colour.ts";
3
+ export { inkOn, luminance, rgbOf, safeColour, safePicture } from "./colour.ts";
4
+ export { safeLogo } from "./logo.ts";
5
+ export { useEmblems } from "./emblems-source.ts";
6
+ export type { EmblemDrawOptions, EmblemId, EmblemRef, EmblemRenderer, EmblemStyleName } from "./emblems-source.ts";
4
7
  export { OUTCOMES, drawLayers, layerTable } from "./layers.ts";
5
8
  export { RANGES } from "./ranges.names.ts";
6
9
  export { BACKGROUND_OPACITY, BACKGROUND_PATTERNS, KYOGIJO_BACKGROUND_STYLE } from "./background.ts";
@@ -14,6 +17,10 @@ export type { Area as FieldArea, FieldPlan, GoalPlan as FieldGoalPlan, Outline a
14
17
  export { KYOGIJO_FIELDS_STYLE } from "./fields.style.ts";
15
18
  export { KYOGIJO_ATHLETICS_STYLE, KYOGIJO_TRACK_STYLE } from "./athletics.style.ts";
16
19
  export { KYOGIJO_SKATING_STYLE } from "./skating.style.ts";
20
+ export { KYOGIJO_BUDO_STYLE } from "./budo.style.ts";
21
+ export { dojo, drawDojo } from "./budo/dojo.api.ts";
22
+ export type { Dojo, Tatami } from "./budo/dojo.api.ts";
23
+ export type { BudoScore, BudoStyle, DojoSpec } from "./budo/types.ts";
17
24
  export type { SkatingDiscipline, SkatingFurniture, SkatingInfield, SkatingRace, SkatingStyle } from "./skating/types.ts";
18
25
  export type { AthleticsFieldEvent, AthleticsInfield, AthleticsRace, AthleticsStyle, AthleticsTrackLook, WaterJump } from "./athletics/types.ts";
19
26
  export type { RangeInfo } from "./ranges.names.ts";
@@ -30,6 +37,9 @@ export { KYOGIJO_CROWD_STYLE } from "./crowd.draw.ts";
30
37
  export { GRIDIRON_GROUNDS, WALL_SEATS, outlinePath, outlinePoints } from "./crowd.ts";
31
38
  export type { CrowdStands, StandGap, StandRing, StandSector, Outline as StandOutline } from "./crowd.ts";
32
39
  export { KYOGIJO_MARK_STYLE } from "./mark.ts";
40
+ export { DRIVE_CODES, readSituation, situationText } from "./situation.ts";
41
+ export type { BaseballSituation, DriveLeg, DriveResult, FootballSituation, HockeySituation, ShootoutAttempt, ShootoutRound, Situation, SituationContext, SituationRead, SituationTeam, HockeyRead, BaseballRead, FootballRead, DriveRead } from "./situation.ts";
42
+ export { KYOGIJO_SITUATION_STYLE } from "./situation.style.ts";
33
43
  export { KYOGIJO_PAGEANT_MOTION, KYOGIJO_PAGEANT_STYLE } from "./pageant.style.ts";
34
44
  export { KYOGIJO_POOL_STYLE } from "./pool.style.ts";
35
45
  export { KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_WALLS_STYLE } from "./style.ts";
@@ -37,7 +47,7 @@ export { KYOGIJO_LOOK_STYLE, LOOKS, LOOK_NAMES, LOOK_PAIRS, contrastOf, groupOf,
37
47
  export type { Group as LookGroup, Look, Palette as LookPalette, PaletteKey as LookPaletteKey } from "./look.ts";
38
48
  export { FEATURES, FEATURE_INFO, KYOGIJO_FEATURE_STYLE } from "./features.ts";
39
49
  export type { Feature } from "./features.ts";
40
- export type { DelaunayLayer, FlowLayer, Formation, FormationLayer, FormationPlayer, FormationPosition, FormationSport, HullLayer, MoveDatum, NetworkLayer, NetworkNode, PassDatum, PlayerDatum, SonarLayer, VoronoiLayer } from "./team.types.ts";
50
+ export type { DelaunayLayer, FlowLayer, Formation, FormationLayer, FormationPlayer, FormationPosition, FormationSport, HullLayer, LineupLayer, LineupPlayer, MoveDatum, NetworkLayer, NetworkNode, PassDatum, PlayerDatum, SonarLayer, VoronoiLayer } from "./team.types.ts";
41
51
  export { COURT_THEMES } from "./themes.ts";
42
52
  export { abbreviate, capitals, fitLines, textWidth } from "./text.ts";
43
53
  export type { FittedLines } from "./text.ts";
@@ -47,6 +57,8 @@ export type { PlaceAlias, PlaceNameKind } from "./names.ts";
47
57
  export type { CoverQuirk, DugoutSide, HillQuirk, OverhangQuirk, Park, ParkData, ParkDugouts, ParkPeriod, ParkPeriodData, ParkQuirk, ParkSource, ParkWall, ParkWallData, PointQuirk, QuirkKind, WallPlace } from "./parks.ts";
48
58
  export { ERA_CURRENT, ERA_SOURCES, ERA_SPORTS, eraChanges, eraRange, eraStates, footballIn, rulesFor, seasonLabel } from "./eras.ts";
49
59
  export type { EraChange, EraRules, EraRulesOf, EraSource, EraSpan, EraSport, FootballRules, MlbRules, NbaRules, NhlRules } from "./eras.ts";
60
+ export { ARENA_IDS, ARENAS, arenaById, arenaByName, arenaByWikidata, arenaCourtIn, arenaNameIn, arenaOfficialName, arenaRinkIn, arenaSourceUrl, arenasByName } from "./arenas.ts";
61
+ export type { Arena, ArenaCapacity, ArenaData, ArenaName, ArenaSize, ArenaSource } from "./arenas.ts";
50
62
  export { GROUND_IDS, GROUNDS, groundById, groundByName, groundByWikidata, groundNameIn, groundOfficialName, groundSourceUrl, groundsByName, groundsHosting } from "./grounds.ts";
51
63
  export type { Ground, GroundCompetition, GroundData, GroundName, GroundSource, GroundTournament } from "./grounds.ts";
52
64
  export { AFL_GROUNDS, FAMILY_OF, LEAGUES, LEAGUE_SOURCES, VARIANTS, chooseLeague, hasHistory, leagueAt, leagueById, leagueChanges, leagueFigures, leagueIds, leagueSeason, leagueSize } from "./leagues.ts";
@@ -58,4 +70,4 @@ export type { SoccerPitchClass, SoccerPitchId, SoccerSize, SoccerSpec } from "./
58
70
  export { SOCCER_PROVIDER_IDS, SOCCER_PROVIDERS, frameMatrix, rescale, soccerFrame, soccerLandmarks } from "./surfaces/soccer-providers.ts";
59
71
  export type { Rescaler, SoccerFrame, SoccerProvider, SoccerProviderId } from "./surfaces/soccer-providers.ts";
60
72
  export { VERSION } from "./version.ts";
61
- export type { AxesOptions, Box, Colors, CourtStyle, CrowdOptions, CrowdShape, Detail, DirectionOptions, FieldSport, FieldStyle, Fence, GoalInfo, GoalStyle, HardwoodOptions, Language, NetStyle, LineDash, LineWidth, LookName, Orientation, PoolSport, PoolStyle, QuirkName, QuirkOptions, RangeBox, PageantOptions, PageantStyle, RangeName, RangeNamed, ScoreOptions, Side, Size, Sport, StatusWord, StripePattern, SurfaceOptions, Team, TeamMark, Theme, WallOptions, } from "./types.ts";
73
+ export type { AxesOptions, Box, Colors, CourtStyle, CrowdOptions, CrowdShape, Detail, DirectionOptions, BudoSport, FieldSport, FieldStyle, Fence, GoalInfo, GoalStyle, HardwoodOptions, Language, NetStyle, LineDash, LineWidth, LookName, Orientation, PoolSport, PoolStyle, QuirkName, QuirkOptions, RangeBox, PageantOptions, PageantStyle, RangeName, RangeNamed, ScoreOptions, Side, Size, Sport, StatusWord, StripePattern, SurfaceOptions, Team, TeamMark, Theme, WallOptions, } from "./types.ts";
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // Kyogijo Stadiums 競技場: sports surfaces as SVG, drawn to scale, with the teams and the score on them.
2
2
  export { COLOR_PROPERTIES, SPORTS, crowdStands, describe, drawBadge, drawKyogijo, drawScore, drawSurface, isPortrait, statusLabel, statusText, winnerOf } from "./draw.js";
3
3
  export { HOME_FIRST, HOME_FIRST_LEAGUES, homeSideFor } from "./homeside.js";
4
- export { inkOn, luminance, rgbOf, safeColour, safeLogo } from "./colour.js";
4
+ export { inkOn, luminance, rgbOf, safeColour, safePicture } from "./colour.js";
5
+ export { safeLogo } from "./logo.js";
6
+ export { useEmblems } from "./emblems-source.js";
5
7
  export { OUTCOMES, drawLayers, layerTable } from "./layers.js";
6
8
  export { RANGES } from "./ranges.names.js";
7
9
  export { BACKGROUND_OPACITY, BACKGROUND_PATTERNS, KYOGIJO_BACKGROUND_STYLE } from "./background.js";
@@ -11,6 +13,8 @@ export { formatTime, parseTime } from "./pool.race.js";
11
13
  export { KYOGIJO_FIELDS_STYLE } from "./fields.style.js";
12
14
  export { KYOGIJO_ATHLETICS_STYLE, KYOGIJO_TRACK_STYLE } from "./athletics.style.js";
13
15
  export { KYOGIJO_SKATING_STYLE } from "./skating.style.js";
16
+ export { KYOGIJO_BUDO_STYLE } from "./budo.style.js";
17
+ export { dojo, drawDojo } from "./budo/dojo.api.js";
14
18
  export { rampColour } from "./layers.marks.js";
15
19
  export { HEAT_RAMPS, HEAT_RAMP_NAMES } from "./heat.colour.js";
16
20
  export { ZONE_SETS, ZONE_SET_IDS } from "./heat.zones.js";
@@ -20,6 +24,8 @@ export { HARDWOOD_SMALL, KYOGIJO_HARDWOOD_STYLE } from "./hardwood.js";
20
24
  export { KYOGIJO_CROWD_STYLE } from "./crowd.draw.js";
21
25
  export { GRIDIRON_GROUNDS, WALL_SEATS, outlinePath, outlinePoints } from "./crowd.js";
22
26
  export { KYOGIJO_MARK_STYLE } from "./mark.js";
27
+ export { DRIVE_CODES, readSituation, situationText } from "./situation.js";
28
+ export { KYOGIJO_SITUATION_STYLE } from "./situation.style.js";
23
29
  export { KYOGIJO_PAGEANT_MOTION, KYOGIJO_PAGEANT_STYLE } from "./pageant.style.js";
24
30
  export { KYOGIJO_POOL_STYLE } from "./pool.style.js";
25
31
  export { KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_WALLS_STYLE } from "./style.js";
@@ -30,6 +36,7 @@ export { abbreviate, capitals, fitLines, textWidth } from "./text.js";
30
36
  export { PARKS, PARK_IDS, fenceFor, parkById, parkByName, parkByWikidata, parkNameIn, parkOfficialName, parkPointsIn, parkQuirksIn, parkSourceUrl, parkUnit, parkWallsIn, parksByName } from "./parks.js";
31
37
  export { officialName, plainName } from "./names.js";
32
38
  export { ERA_CURRENT, ERA_SOURCES, ERA_SPORTS, eraChanges, eraRange, eraStates, footballIn, rulesFor, seasonLabel } from "./eras.js";
39
+ export { ARENA_IDS, ARENAS, arenaById, arenaByName, arenaByWikidata, arenaCourtIn, arenaNameIn, arenaOfficialName, arenaRinkIn, arenaSourceUrl, arenasByName } from "./arenas.js";
33
40
  export { GROUND_IDS, GROUNDS, groundById, groundByName, groundByWikidata, groundNameIn, groundOfficialName, groundSourceUrl, groundsByName, groundsHosting } from "./grounds.js";
34
41
  export { AFL_GROUNDS, FAMILY_OF, LEAGUES, LEAGUE_SOURCES, VARIANTS, chooseLeague, hasHistory, leagueAt, leagueById, leagueChanges, leagueFigures, leagueIds, leagueSeason, leagueSize } from "./leagues.js";
35
42
  export { SURFACE_UNITS, UNITS, convertLength, isUnit } from "./units.js";
@@ -13,7 +13,8 @@ export { KYOGIJO_HEAT_STYLE } from "./style.ts";
13
13
  export type { ArrowsLayer, Comet, Layer, LayerBase, LayerKind, LayerTeam, LineDatum, LineStyle, LinesLayer, MarkDatum, MarkShape, OutcomeStyle, Paint, PointDatum, PointStyle, PointsLayer, Scale, TableOptions, TextDatum, TextLayer, TextStyle } from "./layers.types.ts";
14
14
  export { KYOGIJO_TEAM_STYLE } from "./style.ts";
15
15
  export { FORMATIONS, FORMATION_IDS, POSITION_NAMES, formationById, formationsOf, positionName } from "./formations.ts";
16
+ export { LINEUP_CODES } from "./lineup.codes.ts";
16
17
  export type { Named } from "./formations.ts";
17
18
  export { clipNearer, convexHull, delaunay, neighbours, polygonArea, polygonCentroid, triangulationEdges, voronoiCells } from "./team.geometry.ts";
18
19
  export type { Pt, Triangulation } from "./team.geometry.ts";
19
- export type { DelaunayLayer, FlowLayer, Formation, FormationLayer, FormationPlayer, FormationPosition, FormationSport, HullLayer, MoveDatum, NetworkLayer, NetworkNode, PassDatum, PlayerDatum, SonarLayer, VoronoiLayer } from "./team.types.ts";
20
+ export type { DelaunayLayer, FlowLayer, Formation, FormationLayer, FormationPlayer, FormationPosition, FormationSport, HullLayer, LineupLayer, LineupPlayer, MoveDatum, NetworkLayer, NetworkNode, PassDatum, PlayerDatum, SonarLayer, VoronoiLayer } from "./team.types.ts";
@@ -8,4 +8,5 @@ export { ZONE_SETS, ZONE_SET_IDS, buildZoneSet } from "./heat.zones.js";
8
8
  export { KYOGIJO_HEAT_STYLE } from "./style.js";
9
9
  export { KYOGIJO_TEAM_STYLE } from "./style.js";
10
10
  export { FORMATIONS, FORMATION_IDS, POSITION_NAMES, formationById, formationsOf, positionName } from "./formations.js";
11
+ export { LINEUP_CODES } from "./lineup.codes.js";
11
12
  export { clipNearer, convexHull, delaunay, neighbours, polygonArea, polygonCentroid, triangulationEdges, voronoiCells } from "./team.geometry.js";
package/dist/layers.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Env } from "./layers.env.ts";
1
2
  import type { Layer, OutcomeStyle, TableOptions } from "./layers.types.ts";
2
3
  import type { Box, ScoreOptions, SurfaceOptions } from "./types.ts";
3
4
  /**
@@ -11,7 +12,7 @@ export interface RenderedLayers {
11
12
  summaries: string[];
12
13
  }
13
14
  /** The layers of a drawing as markup, and a sentence for each. `turned` says whether the surface group is turned a quarter. */
14
- export declare function renderLayers(o: SurfaceOptions | ScoreOptions, layers: readonly Layer[] | undefined, turned?: boolean, range?: Box, upright?: number): RenderedLayers;
15
+ export declare function renderLayers(o: SurfaceOptions | ScoreOptions, layers: readonly Layer[] | undefined, turned?: boolean, range?: Box, upright?: number, zone?: Env["zone"]): RenderedLayers;
15
16
  /**
16
17
  * 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
17
18
  * 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
@@ -32,7 +32,16 @@ export interface Env {
32
32
  home?: string;
33
33
  away?: string;
34
34
  };
35
+ /** Whether the layer being drawn writes `data-mark-id`, `data-index` and `data-zone` on its marks: set for each layer by `renderLayers`. */
36
+ marks: boolean;
37
+ /** The finest zone of the surface a place (feet, the drawing's wide frame) lies in, by id: what a mark carries as `data-zone`. `undefined` where the sport has none there. */
38
+ zone: (x: number, y: number) => string | undefined;
35
39
  }
40
+ /**
41
+ * The data attributes every mark carries, so that one listener on the drawing can link a mark to a table: `data-mark-id` (the id the caller gave the datum, else its index),
42
+ * `data-index` (its place in the layer's data) and `data-zone` (the zone it lies in, from `zoneAt`'s rules). The place is in feet in the drawing's wide frame.
43
+ */
44
+ export declare function markAttrs(env: Env, id: unknown, index: number, at?: readonly [number, number], name?: string): string;
36
45
  /** What a layer made. */
37
46
  export interface Made {
38
47
  /** The elements of the layer, and its gradients as `defs`. */
@@ -71,4 +80,4 @@ export declare function place(layer: LayerBase, env: Env, points: readonly P[],
71
80
  /** The inverse of `place`: places on the surface, in feet, as the layer's own coordinates (its frame and unit). */
72
81
  export declare function placeBack(layer: LayerBase, env: Env, points: readonly P[], unit: Unit): P[];
73
82
  export declare function newMade(): Made;
74
- export declare function envFor(o: SurfaceOptions | ScoreOptions, turned: boolean | undefined, range?: Box, upright?: number): Env;
83
+ export declare function envFor(o: SurfaceOptions | ScoreOptions, turned: boolean | undefined, range?: Box, upright?: number, zone?: Env["zone"]): Env;
@@ -1,8 +1,10 @@
1
1
  // What every kind of data layer is drawn against: the drawing's id, its unit and outline, the words, and what a layer made. Kept apart
2
2
  // from layers.ts so that the heat maps, which are written in files of their own, can use it without importing the file that imports them.
3
- import { safeLogo } from "./colour.js";
3
+ import { safeLogo } from "./logo.js";
4
+ import { esc } from "./format.js";
4
5
  import { drawingId, isPortrait, lengthUnit } from "./common.js";
5
6
  import { fromSurface, toSurface } from "./frames.js";
7
+ import { zoneResolver } from "./rules.js";
6
8
  import "./frames.providers.js";
7
9
  import { chooseLeague } from "./leagues.js";
8
10
  import { frameSizeOf, outlineOf } from "./outline.js";
@@ -12,6 +14,32 @@ export const isNum = (value) => typeof value === "number" && Number.isFinite(val
12
14
  export const clamp01 = (value) => Math.max(0, Math.min(1, value));
13
15
  export const own = (table, key) => Object.prototype.hasOwnProperty.call(table, key);
14
16
  export const opacityOf = (value) => (isNum(value) ? clamp01(value) : undefined);
17
+ /** The zone of a drawing's marks, made when the first mark asks: a surface the rules cannot be made for (a special game) has none. */
18
+ function zonesOf(o) {
19
+ let resolver;
20
+ return (x, y) => {
21
+ if (resolver === undefined) {
22
+ try {
23
+ resolver = o.host !== undefined ? () => undefined : zoneResolver(o.sport, o);
24
+ }
25
+ catch {
26
+ resolver = () => undefined;
27
+ }
28
+ }
29
+ return resolver(x, y);
30
+ };
31
+ }
32
+ /**
33
+ * The data attributes every mark carries, so that one listener on the drawing can link a mark to a table: `data-mark-id` (the id the caller gave the datum, else its index),
34
+ * `data-index` (its place in the layer's data) and `data-zone` (the zone it lies in, from `zoneAt`'s rules). The place is in feet in the drawing's wide frame.
35
+ */
36
+ export function markAttrs(env, id, index, at, name = "data-zone") {
37
+ if (!env.marks)
38
+ return "";
39
+ const given = typeof id === "string" && id !== "" ? id : typeof id === "number" && Number.isFinite(id) ? String(id) : String(index);
40
+ const zone = at === undefined ? undefined : env.zone(at[0], at[1]);
41
+ return ` data-mark-id="${esc(given)}" data-index="${index}"${zone === undefined ? "" : ` ${name}="${esc(zone)}"`}`;
42
+ }
15
43
  export const toArray = (value, what) => {
16
44
  if (!Array.isArray(value))
17
45
  throw new TypeError(`${what} needs data: a list of marks`);
@@ -60,7 +88,7 @@ export function placeBack(layer, env, points, unit) {
60
88
  export function newMade() {
61
89
  return { marks: "", defs: "", style: "", attr: "", opacity: undefined, drawn: 0, skipped: 0, outcomes: new Map(), teams: new Map(), low: Infinity, high: -Infinity };
62
90
  }
63
- export function envFor(o, turned, range, upright) {
91
+ export function envFor(o, turned, range, upright, zone) {
64
92
  const language = o.language === "ja" ? "ja" : "en";
65
93
  const outline = outlineOf(o);
66
94
  const teams = o;
@@ -76,6 +104,8 @@ export function envFor(o, turned, range, upright) {
76
104
  cropped: range !== undefined,
77
105
  base: Math.max((range ?? outline.box).w, (range ?? outline.box).h) * 0.013,
78
106
  words: KYOGIJO_STRINGS[language],
79
- logos: { home: safeLogo(teams.home?.logo), away: safeLogo(teams.away?.logo) },
107
+ logos: { home: safeLogo(teams.home?.logo, { color: teams.home?.color, accent: teams.home?.textColor }), away: safeLogo(teams.away?.logo, { color: teams.away?.color, accent: teams.away?.textColor }) },
108
+ marks: o.marks === true || o.pointer === true,
109
+ zone: zone ?? zonesOf(o),
80
110
  };
81
111
  }
package/dist/layers.js CHANGED
@@ -3,12 +3,14 @@
3
3
  // table that stands for the drawing. Every mark is a plain SVG element in the drawing's own frame, so the same options always give
4
4
  // the same string. Written for this package; mplsoccer and hockey_rink were read for the list of charts to match, and nothing of
5
5
  // either is used.
6
- import { safeColour, safeLogo } from "./colour.js";
6
+ import { safeColour } from "./colour.js";
7
+ import { withEmblems } from "./emblems-source.js";
8
+ import { safeLogo } from "./logo.js";
7
9
  import { FRAMES } from "./frames.js";
8
10
  import { KYOGIJO_HEAT_STYLE, KYOGIJO_TEAM_STYLE } from "./style.js";
9
11
  import { drawTeam, isTeamLayer, teamTables } from "./team.layers.js";
10
12
  import { drawHeat, drawLegend, heatTable } from "./heat.js";
11
- import { clamp01, envFor, isNum, newMade, opacityOf, own, place, toArray, unitOfLayer } from "./layers.env.js";
13
+ import { clamp01, envFor, isNum, markAttrs, newMade, opacityOf, own, place, toArray, unitOfLayer } from "./layers.env.js";
12
14
  import { safeClass } from "./common.js";
13
15
  import { esc, num } from "./format.js";
14
16
  import { curveBetween, curvePath, headPath, isPolygon, polylinePath, rampColour, shapePath, taperPath, wavePath, } from "./layers.marks.js";
@@ -136,7 +138,7 @@ function drawPoints(layer, env) {
136
138
  const w = (isNum(d.size) && d.size > 0 ? d.size * k : sizeFn === undefined ? fixed : Math.max(0.05, sizeFn(d.value) * k)) * (outcome.scale ?? 1);
137
139
  const { cls, css } = colourAttrs(colourFor(style.fill, d, NEUTRAL, domain), layerExpr, NEUTRAL);
138
140
  const classes = `${cls}${outcome.hollow === true ? `${cls === "" ? "" : " "}ky-hol` : ""}`;
139
- const open = attrs(classes, css, opacityOf(d.opacity));
141
+ const open = attrs(classes, css, opacityOf(d.opacity)) + markAttrs(env, d.id, i, [x, y]);
140
142
  const role = d.team === "home" ? "home" : d.team === "away" ? "away" : undefined;
141
143
  const logo = shape === "logo" ? safeLogo(d.logo) ?? (role === undefined ? undefined : safeLogo(style.logos?.[role]) ?? env.logos[role]) : undefined;
142
144
  if (logo !== undefined) {
@@ -258,13 +260,15 @@ function drawLines(layer, env, index) {
258
260
  const headEl = head === "" ? "" : `<path class="${headKind === "open" ? "ky-ln" : "ky-ah"}" d="${head}"${headKind === "open" ? ` stroke-width="${num(w)}"` : ""}/>`;
259
261
  const title = typeof d.title === "string" && d.title !== "" ? `<title>${esc(d.title)}</title>` : "";
260
262
  const opacity = opacityOf(d.opacity);
263
+ // Where it starts is its zone; where it ends is `data-zone-end`.
264
+ const marks = env.marks ? markAttrs(env, d.id, i, first) + (env.zone(last[0], last[1]) === undefined ? "" : ` data-zone-end="${esc(env.zone(last[0], last[1]))}"`) : "";
261
265
  // A line with a head is a group (its colour and opacity on the group); a line alone carries them itself.
262
266
  const wrap = headEl !== "";
263
267
  const pathClass = [shaftClass, wrap ? "" : cls].filter((c) => c !== "").join(" ");
264
268
  const pathStyle = [wrap ? "" : css, gradient].filter((c) => c !== "").join(";");
265
- const pathOpen = `${attrs(pathClass, pathStyle, wrap ? undefined : opacity)} d="${shaft}"${tapered === undefined ? strokeWidth + dashes : ""}`;
269
+ const pathOpen = `${attrs(pathClass, pathStyle, wrap ? undefined : opacity)}${wrap ? "" : marks} d="${shaft}"${tapered === undefined ? strokeWidth + dashes : ""}`;
266
270
  if (wrap)
267
- out.push(`<g${attrs(cls, css, opacity)}>${title}<path${pathOpen}/>${headEl}</g>`);
271
+ out.push(`<g${attrs(cls, css, opacity)}${marks}>${title}<path${pathOpen}/>${headEl}</g>`);
268
272
  else
269
273
  out.push(title === "" ? `<path${pathOpen}/>` : `<path${pathOpen}>${title}</path>`);
270
274
  });
@@ -309,12 +313,17 @@ function drawText(layer, env) {
309
313
  // The letters are turned back by the quarter the surface was turned, so that they read upright, and by the caller's own turn on top.
310
314
  const angle = env.back + (isNum(d.rotate) ? d.rotate : 0);
311
315
  const classes = ["ky-lt", style.halo === false ? "ky-nh" : "", cls].filter((c) => c !== "").join(" ");
312
- const open = `${attrs(classes, `${s === size ? "" : `font-size:${num(s)}px`}${s !== size && css !== "" ? ";" : ""}${css}`, opacityOf(d.opacity))}${anchor === base ? "" : ` text-anchor="${anchor}"`} x="${num(x)}" y="${num(y + s * 0.36)}"${angle === 0 ? "" : ` transform="rotate(${num(angle)} ${num(x)} ${num(y)})"`}`;
316
+ const open = `${attrs(classes, `${s === size ? "" : `font-size:${num(s)}px`}${s !== size && css !== "" ? ";" : ""}${css}`, opacityOf(d.opacity))}${markAttrs(env, d.id, i, [x, y])}${anchor === base ? "" : ` text-anchor="${anchor}"`} x="${num(x)}" y="${num(y + s * 0.36)}"${angle === 0 ? "" : ` transform="rotate(${num(angle)} ${num(x)} ${num(y)})"`}`;
313
317
  out.push(`<text${open}>${esc(String(d.text))}${typeof d.title === "string" && d.title !== "" ? `<title>${esc(d.title)}</title>` : ""}</text>`);
314
318
  });
315
319
  made.marks = out.join("");
316
320
  return made;
317
321
  }
322
+ /**
323
+ * True when a datum of the layer has an `id`: the caller means to find its marks again. Not for the team layers, whose data have always had an `id` of their own (the player's, for a label and a table),
324
+ * so that a drawing made before the marks had ids is the same text now.
325
+ */
326
+ const hasIds = (layer) => !isTeamLayer(layer) && Array.isArray(layer.data) && (layer.data).some((d) => typeof d === "object" && d !== null && d.id !== undefined);
318
327
  const plural = (words, kind, n) => words[`${kind === "lines" ? "line" : kind === "arrows" ? "arrow" : kind === "text" ? "text" : "point"}${n === 1 ? "One" : "Many"}`];
319
328
  const tidy = (text) => text.replace(/[.。]\s*$/, "");
320
329
  /** A list of counts, `goal 3, shot 21`, the most first. */
@@ -348,12 +357,16 @@ function summarise(layer, made, words, stop, gap) {
348
357
  return { title, description: parts.join(gap) };
349
358
  }
350
359
  /** The layers of a drawing as markup, and a sentence for each. `turned` says whether the surface group is turned a quarter. */
351
- export function renderLayers(o, layers, turned, range, upright) {
360
+ export function renderLayers(o, layers, turned, range, upright, zone) {
361
+ return withEmblems(o.emblems, () => renderedLayers(o, layers, turned, range, upright, zone));
362
+ }
363
+ function renderedLayers(o, layers, turned, range, upright, zone) {
352
364
  if (layers === undefined || layers.length === 0)
353
365
  return { svg: "", summaries: [] };
354
366
  if (!Array.isArray(layers))
355
367
  throw new TypeError("layers is a list of layers");
356
- const env = envFor(o, turned, range, upright);
368
+ const env = envFor(o, turned, range, upright, zone);
369
+ const markedBy = env.marks;
357
370
  const clipId = `${env.id}-lc`;
358
371
  const { stop, gap } = env.words.said;
359
372
  const said = [];
@@ -366,6 +379,8 @@ export function renderLayers(o, layers, turned, range, upright) {
366
379
  layers.forEach((layer, index) => {
367
380
  if (typeof layer !== "object" || layer === null)
368
381
  throw new TypeError(`layer ${index} is not a layer`);
382
+ // The marks are written with their ids, indexes and zones when the layer or the drawing asks, or when the caller gave a datum an id of its own.
383
+ env.marks = typeof layer.marks === "boolean" ? layer.marks : markedBy || hasIds(layer);
369
384
  const name = safeClass(layer.id).replace(/ /g, "-");
370
385
  const gid = name === "" ? `${env.id}-l${index}` : `${env.id}-${name}`;
371
386
  const finish = (made) => {
@@ -375,7 +390,8 @@ export function renderLayers(o, layers, turned, range, upright) {
375
390
  return;
376
391
  const { title, description } = summarise(layer, made, env.words.layers, stop, gap);
377
392
  said[index] = description === "" ? `${tidy(title)}${stop}` : `${tidy(title)}${stop}${gap}${description}`;
378
- const clip = layer.clip !== "none" && layer.kind !== "legend";
393
+ // A lineup is not cut to the outline unless it is asked to be: a fielder at the foul line or a catcher behind the plate would lose half a jersey.
394
+ const clip = layer.kind !== "legend" && (layer.clip === "surface" || (layer.clip !== "none" && layer.kind !== "lineup"));
379
395
  clipped = clipped || clip;
380
396
  const cls = made.cls ?? (layer.kind === "points" ? "ky-pts" : layer.kind === "text" ? "ky-txt" : "ky-lns");
381
397
  // Cropped to a range, a clipped layer is also cut to the range: its group is held by a group with the range's clip.
@@ -403,6 +419,7 @@ export function renderLayers(o, layers, turned, range, upright) {
403
419
  return;
404
420
  case "network":
405
421
  case "formation":
422
+ case "lineup":
406
423
  case "voronoi":
407
424
  case "delaunay":
408
425
  case "hull":
@@ -415,7 +432,7 @@ export function renderLayers(o, layers, turned, range, upright) {
415
432
  slots[index] = () => finish(drawLegend(layer, env, gid, known));
416
433
  return;
417
434
  default:
418
- throw new TypeError(`layer ${index} has the kind "${String(layer.kind)}": use points, lines, arrows, text, network, formation, voronoi, delaunay, hull, flow, sonar, heatmap, hexbin, zones, contour or legend`);
435
+ throw new TypeError(`layer ${index} has the kind "${String(layer.kind)}": use points, lines, arrows, text, network, formation, lineup, voronoi, delaunay, hull, flow, sonar, heatmap, hexbin, zones, contour or legend`);
419
436
  }
420
437
  });
421
438
  slots.forEach((later) => later?.());
@@ -491,7 +508,7 @@ export function layerTable(layers, options = {}) {
491
508
  return render(heatTable(layer, surfaceEnv), layer.kind);
492
509
  if (surfaceEnv !== undefined && isTeamLayer(layer))
493
510
  return teamTables(layer, surfaceEnv).map((t) => render(t, layer.kind)).join("");
494
- const data = layer.kind === "formation" ? (layer.data ?? []) : toArray(layer.data, `layer ${index}`);
511
+ const data = layer.kind === "formation" ? (layer.data ?? []) : layer.kind === "lineup" ? (layer.players ?? []) : toArray(layer.data, `layer ${index}`);
495
512
  const columns = [];
496
513
  const has = (key) => data.some((d) => typeof d === "object" && d !== null && d[key] !== undefined && d[key] !== null);
497
514
  const t = words.team;
@@ -501,6 +518,8 @@ export function layerTable(layers, options = {}) {
501
518
  columns.push([t.tableFrom, "from", (d) => d.from], [t.tableTo, "to", (d) => d.to]);
502
519
  if (layer.kind === "formation")
503
520
  columns.push([t.tablePosition, "position", (d) => d.position], [t.tableNumber, "number", (d) => d.number], [t.tableName, "name", (d) => d.name]);
521
+ else if (layer.kind === "lineup")
522
+ columns.push([t.tablePosition, "pos", (d) => d.pos], [t.tableNumber, "number", (d) => d.number], [t.tableName, "name", (d) => d.name]);
504
523
  else {
505
524
  columns.push([w.tableX, "x", (d) => d.x]);
506
525
  columns.push([w.tableY, "y", (d) => d.y]);
@@ -1,7 +1,7 @@
1
1
  import type { ScoreOptions, SurfaceOptions } from "./types.ts";
2
2
  import type { Frame, Attacking, Landmark, SurfaceSize } from "./frames.ts";
3
3
  import type { ContourLayer, HeatmapLayer, HexbinLayer, LegendLayer, ZonesLayer } from "./heat.types.ts";
4
- import type { DelaunayLayer, FlowLayer, FormationLayer, HullLayer, NetworkLayer, SonarLayer, VoronoiLayer } from "./team.types.ts";
4
+ import type { DelaunayLayer, FlowLayer, FormationLayer, HullLayer, LineupLayer, NetworkLayer, SonarLayer, VoronoiLayer } from "./team.types.ts";
5
5
  import type { Unit } from "./units.ts";
6
6
  /** The shapes a point can be drawn as. A team's logo is `logo`. */
7
7
  export type MarkShape = "circle" | "square" | "triangle" | "diamond" | "cross" | "x" | "star";
@@ -45,6 +45,8 @@ export interface OutcomeStyle {
45
45
  }
46
46
  /** What every mark can carry. */
47
47
  export interface MarkDatum {
48
+ /** 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. */
49
+ id?: string | number;
48
50
  /** The tooltip, and what a screen reader reads for the mark. */
49
51
  title?: string;
50
52
  /** `home`, `away` or another team. */
@@ -189,6 +191,11 @@ export interface LayerBase {
189
191
  clip?: "surface" | "none";
190
192
  /** Hide the layer. It is still in `layerTable`. */
191
193
  hidden?: boolean;
194
+ /**
195
+ * Write `data-mark-id`, `data-index` and `data-zone` on every mark of the layer (and draw each cell of a heat map or hexbin as a path of its own), so that one listener can link a mark to a table.
196
+ * Off by default, to keep a drawing compact; on by itself when a datum has an `id`, and when the drawing is made with `marks: true` or `pointer: true`. `false` turns it off for this layer.
197
+ */
198
+ marks?: boolean;
192
199
  }
193
200
  export interface PointsLayer extends LayerBase {
194
201
  kind: "points";
@@ -211,7 +218,7 @@ export interface TextLayer extends LayerBase {
211
218
  style?: TextStyle;
212
219
  }
213
220
  /** A layer of data on a surface. */
214
- export type Layer = PointsLayer | LinesLayer | ArrowsLayer | TextLayer | HeatmapLayer | HexbinLayer | ZonesLayer | ContourLayer | LegendLayer | NetworkLayer | FormationLayer | VoronoiLayer | DelaunayLayer | HullLayer | FlowLayer | SonarLayer;
221
+ export type Layer = PointsLayer | LinesLayer | ArrowsLayer | TextLayer | HeatmapLayer | HexbinLayer | ZonesLayer | ContourLayer | LegendLayer | NetworkLayer | FormationLayer | LineupLayer | VoronoiLayer | DelaunayLayer | HullLayer | FlowLayer | SonarLayer;
215
222
  /** The kinds of layer. */
216
223
  export type LayerKind = Layer["kind"];
217
224
  /** What `layerTable` takes. */