@johnmorrisdotca/kyogijo-stadiums 0.0.0-stage → 0.4.0
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.
- package/CHANGELOG.md +129 -0
- package/LICENSE +21 -0
- package/NOTICE.md +293 -0
- package/README.md +600 -2
- package/dist/annotate.d.ts +47 -0
- package/dist/annotate.js +349 -0
- package/dist/atlas-entry.d.ts +4 -0
- package/dist/atlas-entry.js +3 -0
- package/dist/atlas.d.ts +131 -0
- package/dist/atlas.js +149 -0
- package/dist/background.d.ts +56 -0
- package/dist/background.js +127 -0
- package/dist/ballpark.d.ts +48 -0
- package/dist/ballpark.js +129 -0
- package/dist/canvas-entry.d.ts +2 -0
- package/dist/canvas-entry.js +2 -0
- package/dist/canvas.d.ts +85 -0
- package/dist/canvas.js +279 -0
- package/dist/charts-entry.d.ts +10 -0
- package/dist/charts-entry.js +8 -0
- package/dist/charts.baseball.d.ts +30 -0
- package/dist/charts.baseball.js +152 -0
- package/dist/charts.curling.d.ts +52 -0
- package/dist/charts.curling.js +196 -0
- package/dist/charts.d.ts +172 -0
- package/dist/charts.js +508 -0
- package/dist/charts.network.d.ts +66 -0
- package/dist/charts.network.js +64 -0
- package/dist/charts.soccer.d.ts +43 -0
- package/dist/charts.soccer.js +191 -0
- package/dist/charts.types.d.ts +396 -0
- package/dist/charts.types.js +1 -0
- package/dist/colour.d.ts +29 -0
- package/dist/colour.js +97 -0
- package/dist/common.d.ts +14 -0
- package/dist/common.js +32 -0
- package/dist/court.d.ts +89 -0
- package/dist/court.js +248 -0
- package/dist/courts.style.d.ts +20 -0
- package/dist/courts.style.js +54 -0
- package/dist/cricket.board.d.ts +18 -0
- package/dist/cricket.board.js +67 -0
- package/dist/curling.board.d.ts +24 -0
- package/dist/curling.board.js +147 -0
- package/dist/d3-entry.d.ts +3 -0
- package/dist/d3-entry.js +2 -0
- package/dist/d3.d.ts +45 -0
- package/dist/d3.js +56 -0
- package/dist/draw.d.ts +52 -0
- package/dist/draw.js +705 -0
- package/dist/element-define.d.ts +2 -0
- package/dist/element-define.js +4 -0
- package/dist/element-entry.d.ts +1 -0
- package/dist/element-entry.js +2 -0
- package/dist/element.d.ts +39 -0
- package/dist/element.js +321 -0
- package/dist/eras-entry.d.ts +2 -0
- package/dist/eras-entry.js +2 -0
- package/dist/eras.d.ts +204 -0
- package/dist/eras.js +396 -0
- package/dist/events-entry.d.ts +3 -0
- package/dist/events-entry.js +3 -0
- package/dist/events.d.ts +54 -0
- package/dist/events.data.d.ts +59 -0
- package/dist/events.data.js +297 -0
- package/dist/events.js +127 -0
- package/dist/features.d.ts +31 -0
- package/dist/features.js +284 -0
- package/dist/field.score.d.ts +5 -0
- package/dist/field.score.js +33 -0
- package/dist/fields/afl.d.ts +11 -0
- package/dist/fields/afl.js +103 -0
- package/dist/fields/cricket.d.ts +9 -0
- package/dist/fields/cricket.js +81 -0
- package/dist/fields/fieldhockey.d.ts +6 -0
- package/dist/fields/fieldhockey.js +81 -0
- package/dist/fields/gaelic.d.ts +10 -0
- package/dist/fields/gaelic.js +86 -0
- package/dist/fields/handball.d.ts +6 -0
- package/dist/fields/handball.js +85 -0
- package/dist/fields/lacrosse.d.ts +17 -0
- package/dist/fields/lacrosse.js +169 -0
- package/dist/fields/rugby.d.ts +10 -0
- package/dist/fields/rugby.js +130 -0
- package/dist/fields.d.ts +12 -0
- package/dist/fields.js +44 -0
- package/dist/fields.kit.d.ts +63 -0
- package/dist/fields.kit.js +108 -0
- package/dist/fields.style.d.ts +4 -0
- package/dist/fields.style.js +19 -0
- package/dist/fields.types.d.ts +155 -0
- package/dist/fields.types.js +1 -0
- package/dist/format.d.ts +10 -0
- package/dist/format.js +28 -0
- package/dist/formations.d.ts +18 -0
- package/dist/formations.js +211 -0
- package/dist/frames-entry.d.ts +8 -0
- package/dist/frames-entry.js +7 -0
- package/dist/frames.d.ts +199 -0
- package/dist/frames.js +271 -0
- package/dist/frames.providers.d.ts +16 -0
- package/dist/frames.providers.js +302 -0
- package/dist/geojson-entry.d.ts +2 -0
- package/dist/geojson-entry.js +2 -0
- package/dist/geojson.d.ts +79 -0
- package/dist/geojson.js +147 -0
- package/dist/geometry-entry.d.ts +7 -0
- package/dist/geometry-entry.js +5 -0
- package/dist/geometry.d.ts +295 -0
- package/dist/geometry.js +361 -0
- package/dist/grounds-entry.d.ts +4 -0
- package/dist/grounds-entry.js +3 -0
- package/dist/grounds.d.ts +134 -0
- package/dist/grounds.data.d.ts +2 -0
- package/dist/grounds.data.js +1849 -0
- package/dist/grounds.js +74 -0
- package/dist/heat.colour.d.ts +37 -0
- package/dist/heat.colour.js +54 -0
- package/dist/heat.d.ts +43 -0
- package/dist/heat.js +614 -0
- package/dist/heat.stats.d.ts +123 -0
- package/dist/heat.stats.js +413 -0
- package/dist/heat.types.d.ts +145 -0
- package/dist/heat.types.js +1 -0
- package/dist/heat.zones.d.ts +54 -0
- package/dist/heat.zones.js +424 -0
- package/dist/homeside.d.ts +16 -0
- package/dist/homeside.js +43 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +29 -0
- package/dist/layers-entry.d.ts +19 -0
- package/dist/layers-entry.js +11 -0
- package/dist/layers.d.ts +41 -0
- package/dist/layers.env.d.ts +72 -0
- package/dist/layers.env.js +79 -0
- package/dist/layers.js +533 -0
- package/dist/layers.marks.d.ts +45 -0
- package/dist/layers.marks.js +168 -0
- package/dist/layers.types.d.ts +226 -0
- package/dist/layers.types.js +1 -0
- package/dist/layout.d.ts +97 -0
- package/dist/layout.js +381 -0
- package/dist/layout.style.d.ts +6 -0
- package/dist/layout.style.js +17 -0
- package/dist/layouts-entry.d.ts +5 -0
- package/dist/layouts-entry.js +4 -0
- package/dist/leagues/aussie.d.ts +57 -0
- package/dist/leagues/aussie.js +36 -0
- package/dist/leagues/baseball.d.ts +58 -0
- package/dist/leagues/baseball.js +171 -0
- package/dist/leagues/basketball.d.ts +67 -0
- package/dist/leagues/basketball.js +128 -0
- package/dist/leagues/courts.d.ts +212 -0
- package/dist/leagues/courts.js +114 -0
- package/dist/leagues/cricket.d.ts +45 -0
- package/dist/leagues/cricket.js +34 -0
- package/dist/leagues/curling.d.ts +64 -0
- package/dist/leagues/curling.js +53 -0
- package/dist/leagues/fieldhockey.d.ts +48 -0
- package/dist/leagues/fieldhockey.js +29 -0
- package/dist/leagues/fields.d.ts +10 -0
- package/dist/leagues/fields.js +6 -0
- package/dist/leagues/football.d.ts +8 -0
- package/dist/leagues/football.js +82 -0
- package/dist/leagues/gaelic.d.ts +50 -0
- package/dist/leagues/gaelic.js +29 -0
- package/dist/leagues/handball.d.ts +45 -0
- package/dist/leagues/handball.js +32 -0
- package/dist/leagues/hockey.d.ts +63 -0
- package/dist/leagues/hockey.js +166 -0
- package/dist/leagues/lacrosse.d.ts +103 -0
- package/dist/leagues/lacrosse.js +175 -0
- package/dist/leagues/rugby.d.ts +59 -0
- package/dist/leagues/rugby.js +72 -0
- package/dist/leagues/sources.d.ts +18 -0
- package/dist/leagues/sources.js +95 -0
- package/dist/leagues/types.d.ts +43 -0
- package/dist/leagues/types.js +5 -0
- package/dist/leagues-entry.d.ts +23 -0
- package/dist/leagues-entry.js +15 -0
- package/dist/leagues.d.ts +172 -0
- package/dist/leagues.js +462 -0
- package/dist/look.d.ts +50 -0
- package/dist/look.js +186 -0
- package/dist/mark.d.ts +35 -0
- package/dist/mark.js +59 -0
- package/dist/mount-entry.d.ts +2 -0
- package/dist/mount-entry.js +2 -0
- package/dist/mount.d.ts +63 -0
- package/dist/mount.js +167 -0
- package/dist/mow.d.ts +36 -0
- package/dist/mow.js +111 -0
- package/dist/names.d.ts +77 -0
- package/dist/names.js +97 -0
- package/dist/outline.d.ts +25 -0
- package/dist/outline.js +112 -0
- package/dist/parks-entry.d.ts +4 -0
- package/dist/parks-entry.js +3 -0
- package/dist/parks.d.ts +200 -0
- package/dist/parks.data.d.ts +2 -0
- package/dist/parks.data.js +4489 -0
- package/dist/parks.js +92 -0
- package/dist/plot-entry.d.ts +2 -0
- package/dist/plot-entry.js +2 -0
- package/dist/plot.d.ts +74 -0
- package/dist/plot.js +68 -0
- package/dist/png-entry.d.ts +2 -0
- package/dist/png-entry.js +2 -0
- package/dist/png.d.ts +26 -0
- package/dist/png.js +46 -0
- package/dist/ranges.d.ts +33 -0
- package/dist/ranges.js +178 -0
- package/dist/ranges.names.d.ts +31 -0
- package/dist/ranges.names.js +65 -0
- package/dist/react-entry.d.ts +2 -0
- package/dist/react-entry.js +2 -0
- package/dist/react.d.ts +53 -0
- package/dist/react.js +58 -0
- package/dist/replay-entry.d.ts +5 -0
- package/dist/replay-entry.js +4 -0
- package/dist/replay.control.d.ts +47 -0
- package/dist/replay.control.js +184 -0
- package/dist/replay.d.ts +170 -0
- package/dist/replay.dom.d.ts +21 -0
- package/dist/replay.dom.js +388 -0
- package/dist/replay.js +579 -0
- package/dist/replay.types.d.ts +152 -0
- package/dist/replay.types.js +1 -0
- package/dist/restyle.d.ts +21 -0
- package/dist/restyle.js +70 -0
- package/dist/road.d.ts +26 -0
- package/dist/road.draw.d.ts +6 -0
- package/dist/road.draw.js +269 -0
- package/dist/road.js +59 -0
- package/dist/road.model.d.ts +63 -0
- package/dist/road.model.js +215 -0
- package/dist/road.style.d.ts +7 -0
- package/dist/road.style.js +26 -0
- package/dist/road.types.d.ts +122 -0
- package/dist/road.types.js +1 -0
- package/dist/scene-entry.d.ts +7 -0
- package/dist/scene-entry.js +5 -0
- package/dist/scene.css.d.ts +44 -0
- package/dist/scene.css.js +296 -0
- package/dist/scene.d.ts +27 -0
- package/dist/scene.fit.d.ts +12 -0
- package/dist/scene.fit.js +18 -0
- package/dist/scene.flat.d.ts +38 -0
- package/dist/scene.flat.js +167 -0
- package/dist/scene.js +420 -0
- package/dist/scene.path.d.ts +40 -0
- package/dist/scene.path.js +357 -0
- package/dist/scene.types.d.ts +172 -0
- package/dist/scene.types.js +1 -0
- package/dist/scene.xml.d.ts +31 -0
- package/dist/scene.xml.js +81 -0
- package/dist/score.d.ts +70 -0
- package/dist/score.js +212 -0
- package/dist/sets.board.d.ts +22 -0
- package/dist/sets.board.js +89 -0
- package/dist/sheet.d.ts +18 -0
- package/dist/sheet.js +38 -0
- package/dist/special.d.ts +44 -0
- package/dist/special.js +204 -0
- package/dist/special.style.d.ts +8 -0
- package/dist/special.style.js +36 -0
- package/dist/strings.d.ts +581 -0
- package/dist/strings.js +411 -0
- package/dist/style.d.ts +35 -0
- package/dist/style.js +177 -0
- package/dist/surfaces/baseball.d.ts +33 -0
- package/dist/surfaces/baseball.js +208 -0
- package/dist/surfaces/baseball.walls.d.ts +16 -0
- package/dist/surfaces/baseball.walls.js +150 -0
- package/dist/surfaces/basketball.d.ts +8 -0
- package/dist/surfaces/basketball.js +199 -0
- package/dist/surfaces/curling.d.ts +23 -0
- package/dist/surfaces/curling.js +128 -0
- package/dist/surfaces/field.d.ts +12 -0
- package/dist/surfaces/field.js +163 -0
- package/dist/surfaces/football.d.ts +12 -0
- package/dist/surfaces/football.js +208 -0
- package/dist/surfaces/hockey.d.ts +10 -0
- package/dist/surfaces/hockey.js +166 -0
- package/dist/surfaces/net.d.ts +7 -0
- package/dist/surfaces/net.js +151 -0
- package/dist/surfaces/soccer-providers.d.ts +124 -0
- package/dist/surfaces/soccer-providers.js +334 -0
- package/dist/surfaces/soccer-spec.d.ts +138 -0
- package/dist/surfaces/soccer-spec.js +175 -0
- package/dist/surfaces/soccer.d.ts +25 -0
- package/dist/surfaces/soccer.js +270 -0
- package/dist/surfaces/surface.d.ts +180 -0
- package/dist/surfaces/surface.js +49 -0
- package/dist/team.common.d.ts +75 -0
- package/dist/team.common.js +97 -0
- package/dist/team.formation.d.ts +24 -0
- package/dist/team.formation.js +121 -0
- package/dist/team.geometry.d.ts +66 -0
- package/dist/team.geometry.js +304 -0
- package/dist/team.layers.d.ts +16 -0
- package/dist/team.layers.js +32 -0
- package/dist/team.motion.d.ts +9 -0
- package/dist/team.motion.js +325 -0
- package/dist/team.network.d.ts +43 -0
- package/dist/team.network.js +215 -0
- package/dist/team.shape.d.ts +12 -0
- package/dist/team.shape.js +217 -0
- package/dist/team.types.d.ts +189 -0
- package/dist/team.types.js +1 -0
- package/dist/text.d.ts +31 -0
- package/dist/text.js +88 -0
- package/dist/themes.d.ts +14 -0
- package/dist/themes.js +24 -0
- package/dist/types.d.ts +565 -0
- package/dist/types.js +1 -0
- package/dist/units.d.ts +45 -0
- package/dist/units.js +90 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +234 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { GoalMouth, GoalMouthOptions, SoccerChart, SoccerShot, SoccerShotOptions } from "./charts.types.ts";
|
|
2
|
+
import { keyItems } from "./charts.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A shot map for soccer: each shot on the pitch (all of it, or the half the shots attack) in a data provider's own numbers, sized by its expected
|
|
5
|
+
* goals, a goal a filled star and the rest open marks (a save a circle, a miss an x, a block a triangle, the post a diamond), with the expected-goals
|
|
6
|
+
* total, a key and a table. `goalMouth()` of the result is the goal-mouth view of the same shots.
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { soccerShotMap } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
10
|
+
* const chart = soccerShotMap(
|
|
11
|
+
* [
|
|
12
|
+
* { x: 104, y: 36, outcome: "goal", xg: 0.31, goalY: -1.2, goalZ: 0.6 },
|
|
13
|
+
* { x: 98, y: 50, outcome: "saved", xg: 0.08, goalY: 2.4, goalZ: 1.1 },
|
|
14
|
+
* { x: 90, y: 30, outcome: "missed", xg: 0.04, goalY: 5, goalZ: 0.5 },
|
|
15
|
+
* ],
|
|
16
|
+
* { provider: "statsbomb", half: true, goalUnit: "yd" },
|
|
17
|
+
* );
|
|
18
|
+
* console.log(chart.totals.shots, chart.totals.goals, chart.totals.xg, chart.options.half);
|
|
19
|
+
* console.log(chart.goalMouth().svg.startsWith("<svg"), chart.goalMouth().placed);
|
|
20
|
+
* // 3 1 0.43 right
|
|
21
|
+
* // true 3
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function soccerShotMap(shots: readonly SoccerShot[], options?: SoccerShotOptions): SoccerChart & {
|
|
25
|
+
key: ReturnType<typeof keyItems>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The goal-mouth view of soccer shots: a small drawing of the goal frame seen from the front (the shooter's left on the left), with where each shot
|
|
29
|
+
* crossed the goal line, across from the centre of the goal (`goalY`, positive to the shooter's right) and up from the ground (`goalZ`). A shot wide
|
|
30
|
+
* or high of the frame is drawn beside or above it; a block has no place across the goal and is left out, and the count says how many were.
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { soccerGoalMouth } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
34
|
+
* const view = soccerGoalMouth([
|
|
35
|
+
* { x: 100, y: 40, outcome: "goal", goalY: 0.8, goalZ: 1.9, xg: 0.2 },
|
|
36
|
+
* { x: 90, y: 40, outcome: "missed", goalY: -4.6, goalZ: 1.2 },
|
|
37
|
+
* { x: 85, y: 30, outcome: "blocked" },
|
|
38
|
+
* ]);
|
|
39
|
+
* console.log(view.placed, view.skipped, view.svg.includes("Goal mouth"));
|
|
40
|
+
* // 2 1 true
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function soccerGoalMouth(shots: readonly SoccerShot[], options?: GoalMouthOptions): GoalMouth;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// The soccer shot map and the goal-mouth view. The map is shots on a pitch (all of it, or the half the shots attack) in a data provider's own
|
|
2
|
+
// numbers, sized by expected goals; the goal mouth is a small drawing of the goal frame seen from the front, with where each shot crossed the goal line.
|
|
3
|
+
// The ideas are those of mplsoccer's shot maps and goal-mouth plots (MIT); no code of it, nor of sportypy or sportyR (GPL), is used.
|
|
4
|
+
import { drawingId } from "./common.js";
|
|
5
|
+
import { esc, el, num } from "./format.js";
|
|
6
|
+
import { layerTable, renderLayers } from "./layers.js";
|
|
7
|
+
import { outlineOf } from "./outline.js";
|
|
8
|
+
import { KYOGIJO_BASE_STYLE } from "./style.js";
|
|
9
|
+
import { fill } from "./strings.js";
|
|
10
|
+
import { FEET_PER_METRE } from "./geometry.js";
|
|
11
|
+
import { SOCCER } from "./surfaces/soccer-spec.js";
|
|
12
|
+
import { VERSION } from "./version.js";
|
|
13
|
+
import { assemble, clean, countOutcomes, describeCounts, drawingOptions, frameOf, fromOf, hasPlace, isNum, keyItems, keyLayers, languageOf, round, teamTotals, wordsOf } from "./charts.js";
|
|
14
|
+
const LOOKS = {
|
|
15
|
+
goal: { shape: "star", scale: 1.35 },
|
|
16
|
+
saved: { hollow: true },
|
|
17
|
+
missed: { shape: "x", hollow: true },
|
|
18
|
+
blocked: { shape: "triangle", hollow: true },
|
|
19
|
+
post: { shape: "diamond", hollow: true },
|
|
20
|
+
};
|
|
21
|
+
const ORDER = ["goal", "saved", "post", "missed", "blocked"];
|
|
22
|
+
const METRES = { m: 1, yd: 0.9144, ft: 0.3048 };
|
|
23
|
+
/** A shot's outcome word: its own, `goal` for a goal, else `shot`. */
|
|
24
|
+
const outcomeOf = (s) => (s.goal === true ? "goal" : typeof s.outcome === "string" && s.outcome !== "" ? s.outcome.toLowerCase() : "shot");
|
|
25
|
+
/** The key's entries for the outcomes present, goals first. */
|
|
26
|
+
function entriesOf(counts, labels) {
|
|
27
|
+
const present = [...counts.keys()].filter((k) => k !== "");
|
|
28
|
+
return [...ORDER.filter((k) => present.includes(k)), ...present.filter((k) => !ORDER.includes(k))].map((k) => ({ label: labels[k] ?? k, outcome: k }));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A shot map for soccer: each shot on the pitch (all of it, or the half the shots attack) in a data provider's own numbers, sized by its expected
|
|
32
|
+
* goals, a goal a filled star and the rest open marks (a save a circle, a miss an x, a block a triangle, the post a diamond), with the expected-goals
|
|
33
|
+
* total, a key and a table. `goalMouth()` of the result is the goal-mouth view of the same shots.
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { soccerShotMap } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
37
|
+
* const chart = soccerShotMap(
|
|
38
|
+
* [
|
|
39
|
+
* { x: 104, y: 36, outcome: "goal", xg: 0.31, goalY: -1.2, goalZ: 0.6 },
|
|
40
|
+
* { x: 98, y: 50, outcome: "saved", xg: 0.08, goalY: 2.4, goalZ: 1.1 },
|
|
41
|
+
* { x: 90, y: 30, outcome: "missed", xg: 0.04, goalY: 5, goalZ: 0.5 },
|
|
42
|
+
* ],
|
|
43
|
+
* { provider: "statsbomb", half: true, goalUnit: "yd" },
|
|
44
|
+
* );
|
|
45
|
+
* console.log(chart.totals.shots, chart.totals.goals, chart.totals.xg, chart.options.half);
|
|
46
|
+
* console.log(chart.goalMouth().svg.startsWith("<svg"), chart.goalMouth().placed);
|
|
47
|
+
* // 3 1 0.43 right
|
|
48
|
+
* // true 3
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function soccerShotMap(shots, options = {}) {
|
|
52
|
+
if (!Array.isArray(shots))
|
|
53
|
+
throw new TypeError("shots is a list of shots");
|
|
54
|
+
const language = languageOf(options);
|
|
55
|
+
const all = wordsOf(language);
|
|
56
|
+
const w = all.charts;
|
|
57
|
+
const provider = options.provider ?? "international";
|
|
58
|
+
const frame = frameOf(provider, "international");
|
|
59
|
+
const half = options.half === true ? "right" : options.half === "left" || options.half === "right" ? options.half : undefined;
|
|
60
|
+
const o = drawingOptions("soccer", options, w.titleSoccer, {
|
|
61
|
+
...(typeof provider === "string" ? { pitchType: provider } : {}),
|
|
62
|
+
...(half === undefined ? {} : { half }),
|
|
63
|
+
...(options.pitch === undefined ? {} : { pitch: options.pitch }),
|
|
64
|
+
...(options.ground === undefined ? {} : { ground: options.ground }),
|
|
65
|
+
});
|
|
66
|
+
const attacking = options.attacking === "left" ? "left" : undefined;
|
|
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 }));
|
|
69
|
+
const { counts, placed: count, skipped } = countOutcomes(data);
|
|
70
|
+
const hasXg = data.some((d) => isNum(d.value));
|
|
71
|
+
const sized = options.xg === false ? false : hasXg;
|
|
72
|
+
const xgMax = isNum(options.xgMax) && options.xgMax > 0 ? options.xgMax : 0.8;
|
|
73
|
+
const labels = { goal: w.goal, saved: w.saved, missed: w.missedNet, blocked: w.blocked, shot: w.shot, post: w.post };
|
|
74
|
+
const entries = entriesOf(counts, labels);
|
|
75
|
+
const style = { outcomes: LOOKS, opacity: 0.92, ...(sized ? { size: { domain: [0, xgMax], range: [3.2, 13] } } : { scale: 1.5 }) };
|
|
76
|
+
const xg = hasXg ? round(data.reduce((sum, d) => sum + (hasPlace(d) && isNum(d.value) ? d.value : 0), 0), 2) : null;
|
|
77
|
+
const layers = [];
|
|
78
|
+
const description = [describeCounts(entries, counts, all), sized ? w.sizeNote : ""].filter(Boolean).join(all.said.gap);
|
|
79
|
+
layers.push(clean({ kind: "points", id: "shots", name: w.shots, description: description === "" ? undefined : description, ...where, data, style }));
|
|
80
|
+
const extras = [...(sized ? [w.sizeXg] : []), ...(xg === null ? [] : [fill(w.xgTotal, { n: xg })])];
|
|
81
|
+
if (options.key !== false) {
|
|
82
|
+
const { box } = outlineOf(o);
|
|
83
|
+
const place = half === undefined ? {} : { x: half === "right" ? box.x + box.w / 2 : box.x, w: box.w / 2 };
|
|
84
|
+
layers.push(...keyLayers(o, entries, LOOKS, all, extras, place));
|
|
85
|
+
}
|
|
86
|
+
const goals = counts.get("goal") ?? 0;
|
|
87
|
+
const totals = { shots: count, goals, xg, skipped, teams: teamTotals(data) };
|
|
88
|
+
const summary = xg === null ? fill(w.sumShots, { shots: count, goals }) : fill(w.sumShotsXg, { shots: count, goals, xg });
|
|
89
|
+
const base = assemble(o, layers, { title: w.titleSoccer, summary, totals });
|
|
90
|
+
const chart = {
|
|
91
|
+
...base,
|
|
92
|
+
key: keyItems(entries, LOOKS),
|
|
93
|
+
goalMouth: (mouth = {}) => soccerGoalMouth(shots, { language, unit: options.goalUnit, xg: sized, xgMax, theme: options.surface?.theme, style: options.surface?.style, id: options.id === undefined ? undefined : `${options.id}-goal`, ...mouth }),
|
|
94
|
+
};
|
|
95
|
+
return chart;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The goal-mouth view of soccer shots: a small drawing of the goal frame seen from the front (the shooter's left on the left), with where each shot
|
|
99
|
+
* crossed the goal line, across from the centre of the goal (`goalY`, positive to the shooter's right) and up from the ground (`goalZ`). A shot wide
|
|
100
|
+
* or high of the frame is drawn beside or above it; a block has no place across the goal and is left out, and the count says how many were.
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { soccerGoalMouth } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
104
|
+
* const view = soccerGoalMouth([
|
|
105
|
+
* { x: 100, y: 40, outcome: "goal", goalY: 0.8, goalZ: 1.9, xg: 0.2 },
|
|
106
|
+
* { x: 90, y: 40, outcome: "missed", goalY: -4.6, goalZ: 1.2 },
|
|
107
|
+
* { x: 85, y: 30, outcome: "blocked" },
|
|
108
|
+
* ]);
|
|
109
|
+
* console.log(view.placed, view.skipped, view.svg.includes("Goal mouth"));
|
|
110
|
+
* // 2 1 true
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function soccerGoalMouth(shots, options = {}) {
|
|
114
|
+
if (!Array.isArray(shots))
|
|
115
|
+
throw new TypeError("shots is a list of shots");
|
|
116
|
+
const language = languageOf(options);
|
|
117
|
+
const all = wordsOf(language);
|
|
118
|
+
const w = all.charts;
|
|
119
|
+
const unit = options.unit === "yd" || options.unit === "ft" ? options.unit : "m";
|
|
120
|
+
const k = METRES[unit];
|
|
121
|
+
const goalW = options.goal !== undefined && options.goal.width > 0 ? options.goal.width : SOCCER.goalWidth;
|
|
122
|
+
const goalH = options.goal !== undefined && options.goal.height > 0 ? options.goal.height : SOCCER.goalHeight;
|
|
123
|
+
// The view, in metres: the goal frame with room beside it, above it and a strip of ground beneath.
|
|
124
|
+
const viewW = goalW * 1.7;
|
|
125
|
+
const ground = goalH * 0.22;
|
|
126
|
+
const viewH = goalH * 2.1;
|
|
127
|
+
const frame = {
|
|
128
|
+
name: "goal-mouth",
|
|
129
|
+
title: "Goal mouth, seen from the front: across from the centre (positive to the shooter's right) and the height",
|
|
130
|
+
sport: "soccer",
|
|
131
|
+
unit: "m",
|
|
132
|
+
x: [-viewW / 2, viewW / 2],
|
|
133
|
+
y: [0, viewH - ground],
|
|
134
|
+
origin: "the centre of the goal on the ground",
|
|
135
|
+
yDirection: "up",
|
|
136
|
+
size: "variable",
|
|
137
|
+
ends: "none",
|
|
138
|
+
landmarks: ({ length, width }) => ({ x: [[-viewW / 2, 0], [viewW / 2, length]], y: [[0, width - ground * FEET_PER_METRE], [viewH - ground, 0]] }),
|
|
139
|
+
source: "Kyogijo",
|
|
140
|
+
};
|
|
141
|
+
const id = options.id === undefined ? undefined : options.id;
|
|
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 }));
|
|
144
|
+
const { counts, placed: placedCount, skipped } = countOutcomes(rows);
|
|
145
|
+
const hasXg = rows.some((r) => isNum(r.value));
|
|
146
|
+
const sized = options.xg === false ? false : options.xg === true || hasXg;
|
|
147
|
+
const xgMax = isNum(options.xgMax) && options.xgMax > 0 ? options.xgMax : 0.8;
|
|
148
|
+
const labels = { goal: w.goal, saved: w.saved, missed: w.missedNet, blocked: w.blocked, shot: w.shot, post: w.post };
|
|
149
|
+
const entries = entriesOf(counts, labels);
|
|
150
|
+
const style = { outcomes: LOOKS, opacity: 0.92, ...(sized ? { size: { domain: [0, xgMax], range: [0.25, 0.8] } } : { size: 0.45 }) };
|
|
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
|
+
const layers = [layer];
|
|
153
|
+
const rendered = renderLayers(surface, layers, false);
|
|
154
|
+
const F = FEET_PER_METRE;
|
|
155
|
+
const x0 = ((viewW - goalW) / 2) * F;
|
|
156
|
+
const x1 = x0 + goalW * F;
|
|
157
|
+
const yb = (viewH - ground) * F;
|
|
158
|
+
const yt = yb - goalH * F;
|
|
159
|
+
const net = [];
|
|
160
|
+
const step = 0.45 * F;
|
|
161
|
+
for (let x = x0 + step; x < x1 - 0.01; x += step)
|
|
162
|
+
net.push(`M${num(x)} ${num(yt)}V${num(yb)}`);
|
|
163
|
+
for (let y = yt + step; y < yb - 0.01; y += step)
|
|
164
|
+
net.push(`M${num(x0)} ${num(y)}H${num(x1)}`);
|
|
165
|
+
const art = el("rect", { class: "ky-surface", x: 0, y: 0, width: viewW * F, height: viewH * F }) +
|
|
166
|
+
el("rect", { class: "ky-zone", x: x0, y: yt, width: goalW * F, height: goalH * F }) +
|
|
167
|
+
el("path", { class: "ky-goal-net", d: net.join(""), "stroke-width": 0.12 }) +
|
|
168
|
+
el("path", { class: "ky-line", d: `M0 ${num(yb)}H${num(viewW * F)}`, "stroke-width": 0.5 }) +
|
|
169
|
+
el("path", { class: "ky-goal", d: `M${num(x0)} ${num(yb)}V${num(yt)}H${num(x1)}V${num(yb)}`, "stroke-width": 0.75, "stroke-linejoin": "round" });
|
|
170
|
+
const gid = drawingId(surface);
|
|
171
|
+
const title = options.title ?? w.titleGoal;
|
|
172
|
+
const description = [fill(w.goalDesc, { w: round(goalW, 2), h: round(goalH, 2) }), rendered.summaries.length === 0 ? "" : `${all.layers.data}${all.said.colon}${rendered.summaries.join(all.said.gap)}`].filter(Boolean).join(all.said.gap);
|
|
173
|
+
const theme = options.theme === "light" || options.theme === "dark" ? options.theme : undefined;
|
|
174
|
+
const svg = el("svg", {
|
|
175
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
176
|
+
class: `kyogijo ky-soccer ky-goal-mouth${options.className === undefined || options.className.replace(/[^\w -]/g, "").trim() === "" ? "" : ` ${options.className.replace(/[^\w -]/g, "").trim()}`}`,
|
|
177
|
+
viewBox: `0 0 ${num(viewW * F)} ${num(viewH * F)}`,
|
|
178
|
+
role: "img",
|
|
179
|
+
"aria-labelledby": `${gid}-t ${gid}-d`,
|
|
180
|
+
"data-sport": "soccer",
|
|
181
|
+
"data-theme": theme,
|
|
182
|
+
"data-version": VERSION,
|
|
183
|
+
}, `<title id="${gid}-t">${esc(title)}</title><desc id="${gid}-d">${esc(description)}</desc>${options.style === "none" ? "" : `<style>${KYOGIJO_BASE_STYLE}</style>`}${art}${rendered.svg}`);
|
|
184
|
+
return {
|
|
185
|
+
svg,
|
|
186
|
+
layers,
|
|
187
|
+
placed: placedCount,
|
|
188
|
+
skipped,
|
|
189
|
+
table: (tableOptions = {}) => layerTable(layers, { ...tableOptions, language, surface }),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import type { Frame } from "./frames.ts";
|
|
2
|
+
import type { HeatRamp } from "./heat.types.ts";
|
|
3
|
+
import type { Layer, LayerTeam, MarkShape, TableOptions } from "./layers.types.ts";
|
|
4
|
+
import type { SoccerProviderId } from "./surfaces/soccer-providers.ts";
|
|
5
|
+
import type { Language, SurfaceOptions } from "./types.ts";
|
|
6
|
+
import type { Unit } from "./units.ts";
|
|
7
|
+
/** What every chart gives back: the layers and the drawing options to put them on, a sentence, the totals, and the ways to draw them. */
|
|
8
|
+
export interface Chart<Totals = Record<string, never>> {
|
|
9
|
+
/** The drawing's options: the sport, the layers, the language, a title and what the chart needs of the surface (the park and the season of a spray chart, the half of a pitch). Pass them to `drawKyogijo`, `drawSurface`, `mountKyogijo` or `kyogijoProps`; change any of them first if you like. */
|
|
10
|
+
options: SurfaceOptions;
|
|
11
|
+
/** The layers, the same list as `options.layers`: the zones under the marks, the marks, and a key. They are plain data, so a layer can be changed, taken out or followed by layers of your own. */
|
|
12
|
+
layers: Layer[];
|
|
13
|
+
/** The chart's accessible name, in the chart's language. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** What the chart shows, in a sentence or two, for a caption. */
|
|
16
|
+
summary: string;
|
|
17
|
+
/** The numbers behind it: counts, percentages and expected goals, and the zones with theirs. */
|
|
18
|
+
totals: Totals;
|
|
19
|
+
/** The drawing as an SVG string: `drawKyogijo({ ...options, ...extra })`. */
|
|
20
|
+
draw(extra?: Partial<SurfaceOptions>): string;
|
|
21
|
+
/** One HTML table for each layer that holds data (not the key), with the zones as rows. The table that stands for the drawing for a screen reader. */
|
|
22
|
+
table(options?: Omit<TableOptions, "surface" | "language">): string;
|
|
23
|
+
}
|
|
24
|
+
/** One entry of a chart's key: a mark and what it means. A chart lists its own in `key`. */
|
|
25
|
+
export interface KeyItem {
|
|
26
|
+
/** The outcome the mark stands for (`made`, `goal`). */
|
|
27
|
+
outcome: string;
|
|
28
|
+
/** What the mark is called, in the chart's language. */
|
|
29
|
+
label: string;
|
|
30
|
+
shape: MarkShape;
|
|
31
|
+
/** Drawn open (a miss, a save) and not filled. */
|
|
32
|
+
hollow: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** The options every chart takes. */
|
|
35
|
+
export interface ChartOptions {
|
|
36
|
+
/** The language of the chart's words: its title, its key, the labels of its zones and its table. English by default. */
|
|
37
|
+
language?: Language;
|
|
38
|
+
/** A prefix for the ids inside the drawing. Made from the chart when missing, so that two charts on a page do not share ids. */
|
|
39
|
+
id?: string;
|
|
40
|
+
/** Draw the key (what each mark is) in the margin above the surface. On by default. */
|
|
41
|
+
key?: boolean;
|
|
42
|
+
/** Draw the colour bar of the zones, hexagons or cells. On by default. */
|
|
43
|
+
legend?: boolean;
|
|
44
|
+
/** The colour ramp of the zones, hexagons or cells: a built-in name, a list of stops or `{ light, dark }`. */
|
|
45
|
+
ramp?: HeatRamp;
|
|
46
|
+
/** Options of the drawing laid over the chart's own: the theme, the orientation, `colors`, `style: "none"`, `className`. */
|
|
47
|
+
surface?: Partial<SurfaceOptions>;
|
|
48
|
+
}
|
|
49
|
+
/** A shot as stats.nba.com's shot chart gives it: `LOC_X` and `LOC_Y` and whether it went in. */
|
|
50
|
+
export interface NbaShot {
|
|
51
|
+
x: number | null;
|
|
52
|
+
y: number | null;
|
|
53
|
+
/** True for a shot that went in. Or give `outcome`. */
|
|
54
|
+
made?: boolean;
|
|
55
|
+
/** `made` or `missed`, in place of `made`. */
|
|
56
|
+
outcome?: "made" | "missed" | (string & {});
|
|
57
|
+
/** A number to carry with the shot (a distance, a value over average). */
|
|
58
|
+
value?: number | null;
|
|
59
|
+
team?: LayerTeam;
|
|
60
|
+
/** The tooltip of the mark and its line in the table. */
|
|
61
|
+
title?: string;
|
|
62
|
+
}
|
|
63
|
+
/** What `nbaShotChart` takes. */
|
|
64
|
+
export interface NbaShotOptions extends ChartOptions {
|
|
65
|
+
/** The frame the shots are in: `nba-stats` (the default), or another frame of the court. */
|
|
66
|
+
from?: Frame | string;
|
|
67
|
+
/** The basket the shots were taken at: `right` (the default; the shot chart puts every shot at the right basket) or `left`. */
|
|
68
|
+
end?: "left" | "right";
|
|
69
|
+
/** Count the shots into the six shot zones (restricted area, in the paint, mid-range, both corner threes, above the break) and colour each by its field-goal percentage. On by default. */
|
|
70
|
+
zones?: boolean;
|
|
71
|
+
/** Draw each shot as a mark: filled when made, an open x when missed. On by default. */
|
|
72
|
+
marks?: boolean;
|
|
73
|
+
/** The league's field-goal percentage (46 for 46 percent). With it the zones are coloured by how far they are above or below it, on a diverging ramp; without it, by the percentage alone. */
|
|
74
|
+
league?: number;
|
|
75
|
+
/** A zone with fewer shots than this is left uncoloured (a percentage from two shots says little). 3 by default. */
|
|
76
|
+
minShots?: number;
|
|
77
|
+
/** Write each zone's percentage and its made over taken (`13/31`). On by default. */
|
|
78
|
+
labels?: boolean;
|
|
79
|
+
}
|
|
80
|
+
/** One zone of a chart: its name, the shots in it and the share that went in. */
|
|
81
|
+
export interface ZoneTotal {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
taken: number;
|
|
85
|
+
made: number;
|
|
86
|
+
/** The percent that went in, one decimal, or null for a zone with no shots. */
|
|
87
|
+
percent: number | null;
|
|
88
|
+
}
|
|
89
|
+
/** The numbers behind an NBA shot chart. */
|
|
90
|
+
export interface NbaTotals {
|
|
91
|
+
shots: number;
|
|
92
|
+
made: number;
|
|
93
|
+
/** The percent that went in, one decimal, or null with no shots. */
|
|
94
|
+
percent: number | null;
|
|
95
|
+
/** Rows left out for want of a place. */
|
|
96
|
+
skipped: number;
|
|
97
|
+
zones: ZoneTotal[];
|
|
98
|
+
}
|
|
99
|
+
/** A shot as the NHL's play-by-play gives it. */
|
|
100
|
+
export interface NhlShot {
|
|
101
|
+
x: number | null;
|
|
102
|
+
y: number | null;
|
|
103
|
+
/** `goal`, `saved` (a shot on goal), `missed`, `blocked`, or `shot` for an attempt of no known result. */
|
|
104
|
+
outcome?: "goal" | "saved" | "missed" | "blocked" | "shot" | (string & {});
|
|
105
|
+
/** True for a goal, in place of `outcome`. */
|
|
106
|
+
goal?: boolean;
|
|
107
|
+
/** The chance the shot had of scoring, 0 to 1. */
|
|
108
|
+
xg?: number | null;
|
|
109
|
+
team?: LayerTeam;
|
|
110
|
+
title?: string;
|
|
111
|
+
}
|
|
112
|
+
/** What `nhlShotMap` takes. */
|
|
113
|
+
export interface NhlShotOptions extends ChartOptions {
|
|
114
|
+
/** The frame the shots are in: `nhl-api` (the default), `hockeytech`, or another frame of the rink. */
|
|
115
|
+
from?: Frame | string;
|
|
116
|
+
/** Turn the shots at the left end a half turn so that all of them are at the right end: one half of the rink, every shot. Off by default. */
|
|
117
|
+
fold?: boolean;
|
|
118
|
+
/** `xg` sizes each mark by its chance of scoring (the area of the mark follows `xg`); `none` draws them all alike. `xg` by default when any shot has an `xg`. */
|
|
119
|
+
sizeBy?: "xg" | "none";
|
|
120
|
+
/** The `xg` that is the largest mark. 0.5 by default; a larger one is drawn as large. */
|
|
121
|
+
xgMax?: number;
|
|
122
|
+
/** Count the shots into the rink's slot zones (`nhl-slot`), or into home plate (`nhl-home-plate`). `true` is the slot. Off by default. */
|
|
123
|
+
zones?: boolean | "nhl-slot" | "nhl-home-plate";
|
|
124
|
+
/** Count the shots into hexagons (`true`, or their size in feet). Off by default; with `zones` as well, the zones win. */
|
|
125
|
+
hexbin?: boolean | {
|
|
126
|
+
size?: number;
|
|
127
|
+
};
|
|
128
|
+
/** What the zones or hexagons show: `shots` (the count, the default), `xg` (the sum of the chances) or `goals` (the percent that were goals). */
|
|
129
|
+
statistic?: "shots" | "xg" | "goals";
|
|
130
|
+
/** The end the shots attack, in the frame's numbers, when they are not folded: leave out for shots at both ends. */
|
|
131
|
+
attacking?: "left" | "right";
|
|
132
|
+
/** Draw the marks. On by default. */
|
|
133
|
+
marks?: boolean;
|
|
134
|
+
/** Write the numbers on the zones. On by default for zones. */
|
|
135
|
+
labels?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/** The numbers behind a hockey shot map. */
|
|
138
|
+
export interface NhlTotals {
|
|
139
|
+
shots: number;
|
|
140
|
+
goals: number;
|
|
141
|
+
/** The sum of the shots' chances, two decimals, or null when no shot has one. */
|
|
142
|
+
xg: number | null;
|
|
143
|
+
skipped: number;
|
|
144
|
+
/** The zones, when the chart has them. */
|
|
145
|
+
zones: ZoneTotal[];
|
|
146
|
+
/** Shots and goals by team, when the shots name one. */
|
|
147
|
+
teams: Record<string, {
|
|
148
|
+
shots: number;
|
|
149
|
+
goals: number;
|
|
150
|
+
xg: number | null;
|
|
151
|
+
}>;
|
|
152
|
+
}
|
|
153
|
+
/** A ball in play. Give `x` and `y` in the frame, or for Retrosheet a `zone` (the location code, `8D`). */
|
|
154
|
+
export interface Ball {
|
|
155
|
+
x?: number | null;
|
|
156
|
+
y?: number | null;
|
|
157
|
+
/** A Retrosheet hit-location code in place of `x` and `y`. */
|
|
158
|
+
zone?: string;
|
|
159
|
+
/** What happened: `single`, `double`, `triple`, `home run`, `out`, or the words of Statcast's `events` (`home_run`, `field_out`) or the usual short forms (`1B`, `HR`). Any other word is drawn as it is. */
|
|
160
|
+
type?: string;
|
|
161
|
+
/** A number to carry with the ball (exit velocity, distance). */
|
|
162
|
+
value?: number | null;
|
|
163
|
+
team?: LayerTeam;
|
|
164
|
+
title?: string;
|
|
165
|
+
}
|
|
166
|
+
/** What `mlbSprayChart` takes. */
|
|
167
|
+
export interface MlbSprayOptions extends ChartOptions {
|
|
168
|
+
/** The frame the balls are in: `mlbam` (the default; Statcast's `hc_x` and `hc_y`), `retrosheet` (feet from home plate, or zone codes), or another frame of the field. */
|
|
169
|
+
from?: Frame | string;
|
|
170
|
+
/** The ballpark: a key of `PARKS`, past parks included. The generic field when missing. */
|
|
171
|
+
park?: string;
|
|
172
|
+
/** The season: a past park is drawn with the fence it had in it (`polo-grounds` in 1915). */
|
|
173
|
+
year?: number;
|
|
174
|
+
/** The side the batter bats from, `R` (the default) or `L`: it says which side of the field is pull. `none` names the thirds left, centre and right field. */
|
|
175
|
+
bats?: "L" | "R" | "none";
|
|
176
|
+
/** Count the balls into the spray zones (`baseball-spray`, pull, centre and opposite by infield and outfield; `true` is this) or Retrosheet's sectors (`baseball-retrosheet`). Off by default. */
|
|
177
|
+
zones?: boolean | "baseball-spray" | "baseball-retrosheet";
|
|
178
|
+
/** What the zones show: `share` of all the balls (the default), `count`, or the `hit-rate` (the percent that were hits). */
|
|
179
|
+
statistic?: "share" | "count" | "hit-rate";
|
|
180
|
+
/** The ground round the fence, in feet. A ball beyond the fence is drawn there, over the ground round it; 40 by default so that there is room. A ball farther than the margin is drawn at its edge. */
|
|
181
|
+
margin?: number;
|
|
182
|
+
/** Draw the marks. On by default. */
|
|
183
|
+
marks?: boolean;
|
|
184
|
+
/** Write the numbers on the zones. On by default for zones. */
|
|
185
|
+
labels?: boolean;
|
|
186
|
+
}
|
|
187
|
+
/** The numbers behind a spray chart. */
|
|
188
|
+
export interface MlbTotals {
|
|
189
|
+
balls: number;
|
|
190
|
+
/** Balls by type: `single`, `double`, `triple`, `home run`, `out`, and any other word given. */
|
|
191
|
+
types: Record<string, number>;
|
|
192
|
+
/** Balls that came down beyond the fence of the park in that season. */
|
|
193
|
+
beyondFence: number;
|
|
194
|
+
skipped: number;
|
|
195
|
+
/** The zones, when the chart has them: the balls in each and, for a hit rate, the hits. */
|
|
196
|
+
zones: ZoneTotal[];
|
|
197
|
+
}
|
|
198
|
+
/** A pass. The destination is `x` and `y`; the passer's place is `fromX` and `fromY` when you have it. */
|
|
199
|
+
export interface NflPass {
|
|
200
|
+
/** Where the ball came down or was caught, in the frame. */
|
|
201
|
+
x: number | null;
|
|
202
|
+
y: number | null;
|
|
203
|
+
/** Where it was thrown from. With it a pass can be drawn as an arrow. */
|
|
204
|
+
fromX?: number | null;
|
|
205
|
+
fromY?: number | null;
|
|
206
|
+
/** `complete`, `incomplete`, `touchdown` or `interception`. */
|
|
207
|
+
outcome?: "complete" | "incomplete" | "touchdown" | "interception" | (string & {});
|
|
208
|
+
/** The yards the ball travelled past the line of scrimmage. Worked out from the destination and `los` when missing. */
|
|
209
|
+
airYards?: number | null;
|
|
210
|
+
team?: LayerTeam;
|
|
211
|
+
title?: string;
|
|
212
|
+
}
|
|
213
|
+
/** What `nflPassChart` takes. */
|
|
214
|
+
export interface NflPassOptions extends ChartOptions {
|
|
215
|
+
/** The frame the passes are in: `nfl-tracking` (the default; yards, the end zones counted) or another frame of the field. */
|
|
216
|
+
from?: Frame | string;
|
|
217
|
+
/** The line of scrimmage, in the frame's numbers along the field (60 for midfield in `nfl-tracking`). Midfield by default. */
|
|
218
|
+
los?: number;
|
|
219
|
+
/** The way the offence goes in the frame's numbers: `right` (the default) or `left`. The chart is drawn with the offence going right. */
|
|
220
|
+
direction?: "left" | "right";
|
|
221
|
+
/** Count the passes into the pass grid (short and deep by left, middle and right) and colour each by its completion percentage. On by default. */
|
|
222
|
+
zones?: boolean;
|
|
223
|
+
/** Draw each pass from its passer to its destination as an arrow (dashed when incomplete), for passes that have `fromX` and `fromY`. Off by default. */
|
|
224
|
+
arrows?: boolean;
|
|
225
|
+
/** Draw the destinations as marks. On by default. */
|
|
226
|
+
marks?: boolean;
|
|
227
|
+
/** Draw the line of scrimmage. On by default. */
|
|
228
|
+
line?: boolean;
|
|
229
|
+
/** Write each zone's percentage and its completions over attempts. On by default. */
|
|
230
|
+
labels?: boolean;
|
|
231
|
+
/** A zone with fewer passes than this is left uncoloured. 3 by default. */
|
|
232
|
+
minPasses?: number;
|
|
233
|
+
}
|
|
234
|
+
/** The numbers behind a pass chart. */
|
|
235
|
+
export interface NflTotals {
|
|
236
|
+
passes: number;
|
|
237
|
+
complete: number;
|
|
238
|
+
touchdowns: number;
|
|
239
|
+
interceptions: number;
|
|
240
|
+
/** The percent that were caught (touchdowns included), one decimal, or null with no passes. */
|
|
241
|
+
percent: number | null;
|
|
242
|
+
/** The mean air yards of the passes that have a place, or null. */
|
|
243
|
+
airYards: number | null;
|
|
244
|
+
skipped: number;
|
|
245
|
+
zones: ZoneTotal[];
|
|
246
|
+
}
|
|
247
|
+
/** A shot. The place is in the provider's frame; `goalY` and `goalZ` say where it crossed the goal line. */
|
|
248
|
+
export interface SoccerShot {
|
|
249
|
+
x: number | null;
|
|
250
|
+
y: number | null;
|
|
251
|
+
/** `goal`, `saved`, `blocked`, `missed` or `post`. */
|
|
252
|
+
outcome?: "goal" | "saved" | "blocked" | "missed" | "post" | (string & {});
|
|
253
|
+
/** True for a goal, in place of `outcome`. */
|
|
254
|
+
goal?: boolean;
|
|
255
|
+
/** Expected goals, 0 to 1. */
|
|
256
|
+
xg?: number | null;
|
|
257
|
+
/** Where the shot crossed the goal line, for the goal-mouth view: across the goal from its centre, positive to the shooter's right, in `goalUnit` (metres by default). */
|
|
258
|
+
goalY?: number | null;
|
|
259
|
+
/** How high above the ground it crossed the goal line, in `goalUnit`. */
|
|
260
|
+
goalZ?: number | null;
|
|
261
|
+
team?: LayerTeam;
|
|
262
|
+
title?: string;
|
|
263
|
+
}
|
|
264
|
+
/** What `soccerShotMap` takes. */
|
|
265
|
+
export interface SoccerShotOptions extends ChartOptions {
|
|
266
|
+
/** The provider the shots are in (`opta`, `statsbomb`, `wyscout`, ...): the pitch is drawn in its numbers and the marks use them. Or a `Frame` of your own, for a pitch drawn as usual. `international` (metres) by default. */
|
|
267
|
+
provider?: SoccerProviderId | Frame;
|
|
268
|
+
/** Show only the half the shots attack: `true` for the right half (the default for `attacking: "right"`), `"left"` or `"right"` to choose. All of the pitch when false or missing. */
|
|
269
|
+
half?: boolean | "left" | "right";
|
|
270
|
+
/** The end the shots attack, in the provider's numbers: `right` (the default; each team attacks left to right) or `left`. */
|
|
271
|
+
attacking?: "left" | "right";
|
|
272
|
+
/** Size each mark by its expected goals (`xg`). On by default when any shot has one. */
|
|
273
|
+
xg?: boolean;
|
|
274
|
+
/** The `xg` that is the largest mark. 0.8 by default. */
|
|
275
|
+
xgMax?: number;
|
|
276
|
+
/** The pitch: a preset or a size, as `pitch` of a drawing. */
|
|
277
|
+
pitch?: SurfaceOptions["pitch"];
|
|
278
|
+
/** The ground, as `ground` of a drawing. */
|
|
279
|
+
ground?: string;
|
|
280
|
+
/** The unit of `goalY` and `goalZ`: `m` (the default), `yd` or `ft`. */
|
|
281
|
+
goalUnit?: "m" | "yd" | "ft";
|
|
282
|
+
}
|
|
283
|
+
/** The numbers behind a soccer shot map. */
|
|
284
|
+
export interface SoccerTotals {
|
|
285
|
+
shots: number;
|
|
286
|
+
goals: number;
|
|
287
|
+
/** The sum of the shots' expected goals, two decimals, or null when none has one. */
|
|
288
|
+
xg: number | null;
|
|
289
|
+
skipped: number;
|
|
290
|
+
teams: Record<string, {
|
|
291
|
+
shots: number;
|
|
292
|
+
goals: number;
|
|
293
|
+
xg: number | null;
|
|
294
|
+
}>;
|
|
295
|
+
}
|
|
296
|
+
/** The options of the goal-mouth view. */
|
|
297
|
+
export interface GoalMouthOptions {
|
|
298
|
+
language?: Language;
|
|
299
|
+
id?: string;
|
|
300
|
+
/** The unit of `goalY` and `goalZ`: `m` (the default), `yd` or `ft`. */
|
|
301
|
+
unit?: "m" | "yd" | "ft";
|
|
302
|
+
/** The size of the goal in metres: the Laws' 7.32 by 2.44 by default. */
|
|
303
|
+
goal?: {
|
|
304
|
+
width: number;
|
|
305
|
+
height: number;
|
|
306
|
+
};
|
|
307
|
+
/** Size the marks by expected goals. On when any shot has one. */
|
|
308
|
+
xg?: boolean;
|
|
309
|
+
xgMax?: number;
|
|
310
|
+
theme?: SurfaceOptions["theme"];
|
|
311
|
+
style?: SurfaceOptions["style"];
|
|
312
|
+
className?: string;
|
|
313
|
+
title?: string;
|
|
314
|
+
}
|
|
315
|
+
/** The goal-mouth view: a small drawing of the goal frame seen from the front, with where each shot crossed the goal line. */
|
|
316
|
+
export interface GoalMouth {
|
|
317
|
+
/** The drawing as an SVG string. */
|
|
318
|
+
svg: string;
|
|
319
|
+
/** The layers of the marks, in the frame of the goal (`x` across from the centre, `y` the height, in metres), if you want to draw them another way. */
|
|
320
|
+
layers: Layer[];
|
|
321
|
+
/** The shots that had a place across the goal and the ones that did not. */
|
|
322
|
+
placed: number;
|
|
323
|
+
skipped: number;
|
|
324
|
+
/** The table of the shots with their place across the goal (`x` across from the centre, `y` the height, in metres). */
|
|
325
|
+
table(options?: Omit<TableOptions, "surface" | "language">): string;
|
|
326
|
+
}
|
|
327
|
+
/** A soccer shot map: the chart, and the goal-mouth view of the same shots. */
|
|
328
|
+
export interface SoccerChart extends Chart<SoccerTotals> {
|
|
329
|
+
/** The goal-mouth view of the shots that have `goalY` and `goalZ`: a small drawing of the goal frame, seen from the front, with where each shot crossed the goal line. */
|
|
330
|
+
goalMouth(options?: GoalMouthOptions): GoalMouth;
|
|
331
|
+
}
|
|
332
|
+
/** A stone at rest, as its place on the sheet: feet from the button of the house it was played to (the `curling` frame), x across the sheet (positive to the thrower's right) and y along it (positive towards the back line). */
|
|
333
|
+
export interface CurlingStone {
|
|
334
|
+
x: number | null;
|
|
335
|
+
y: number | null;
|
|
336
|
+
/** The team that threw it: `home` or `away` take the two stone colours (red and yellow by default). */
|
|
337
|
+
team?: LayerTeam;
|
|
338
|
+
/** Its number among its team's eight, 1 to 8, written on the stone. */
|
|
339
|
+
number?: number;
|
|
340
|
+
/** The tooltip of the stone and its line in the table. Made from the team, the number and the distance when missing. */
|
|
341
|
+
title?: string;
|
|
342
|
+
}
|
|
343
|
+
/** What `curlingStones` takes. */
|
|
344
|
+
export interface CurlingOptions extends ChartOptions {
|
|
345
|
+
/** The number of the end, for the title: `6` gives "End 6". */
|
|
346
|
+
end?: number;
|
|
347
|
+
/** The team that has the hammer, the last stone of the end. It is named in the summary. */
|
|
348
|
+
hammer?: "home" | "away";
|
|
349
|
+
/** The teams' names, for the summary and the stones' tooltips. */
|
|
350
|
+
names?: {
|
|
351
|
+
home?: string;
|
|
352
|
+
away?: string;
|
|
353
|
+
};
|
|
354
|
+
/** The house the stones are at: the `left` one (the top house of a tall drawing, the default) or the `right`. */
|
|
355
|
+
house?: "left" | "right";
|
|
356
|
+
/** The unit of the stones' places: feet by default. */
|
|
357
|
+
units?: Unit;
|
|
358
|
+
/** Write each stone's number on it. On by default, for the stones that have one. */
|
|
359
|
+
numbers?: boolean;
|
|
360
|
+
}
|
|
361
|
+
/** One stone as the chart judged it. */
|
|
362
|
+
export interface CurlingStoneResult {
|
|
363
|
+
/** The place of the stone in the list given. */
|
|
364
|
+
index: number;
|
|
365
|
+
/** Its centre's distance from the button, in feet. */
|
|
366
|
+
distance: number;
|
|
367
|
+
/** In play: it did not touch a sideline, and it stands wholly beyond the hog line and not wholly beyond the back line. */
|
|
368
|
+
inPlay: boolean;
|
|
369
|
+
/** Any part of it is within 6 feet of the button (in or touching the house), so that it can score. */
|
|
370
|
+
inHouse: boolean;
|
|
371
|
+
/** It scores. */
|
|
372
|
+
counting: boolean;
|
|
373
|
+
/** It is the stone nearest the button of those that can score. */
|
|
374
|
+
shot: boolean;
|
|
375
|
+
}
|
|
376
|
+
/** The numbers behind a curling chart. */
|
|
377
|
+
export interface CurlingTotals {
|
|
378
|
+
/** Stones with a place. */
|
|
379
|
+
stones: number;
|
|
380
|
+
/** In play, and out of play (removed, so not drawn). */
|
|
381
|
+
inPlay: number;
|
|
382
|
+
out: number;
|
|
383
|
+
/** The stones that score, and the points. */
|
|
384
|
+
counting: number;
|
|
385
|
+
points: number;
|
|
386
|
+
/** The team that scores, or `null` for a blank end. */
|
|
387
|
+
scoring: string | null;
|
|
388
|
+
/** The shot rock's place in the list given, or `null`. */
|
|
389
|
+
shot: number | null;
|
|
390
|
+
/** No one scores: no stone in the house, or the two nearest are as near. */
|
|
391
|
+
blank: boolean;
|
|
392
|
+
/** Rows left out for want of a place. */
|
|
393
|
+
skipped: number;
|
|
394
|
+
/** Every stone, as judged. */
|
|
395
|
+
results: CurlingStoneResult[];
|
|
396
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/colour.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The colour as it may be written into a style attribute, or `undefined`. A hex colour, `rgb()`, `rgba()`, `hsl()`,
|
|
3
|
+
* `hsla()`, a colour name or `var(--name)` is drawn; anything else is ignored, so nothing from a form or an address
|
|
4
|
+
* can end the attribute or add a rule.
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { safeColour } from "@johnmorrisdotca/kyogijo-stadiums";
|
|
8
|
+
* console.log(safeColour("#1f5fa8"), safeColour("red;fill:url(x)"));
|
|
9
|
+
* // #1f5fa8 undefined
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function safeColour(value: unknown): string | undefined;
|
|
13
|
+
/** The red, green and blue of a colour, when it can be read without a browser (a hex colour, `rgb()` or a common name). */
|
|
14
|
+
export declare function rgbOf(value: string | undefined): [number, number, number] | undefined;
|
|
15
|
+
/** The relative luminance of a colour, 0 (black) to 1 (white). */
|
|
16
|
+
export declare function luminance(rgb: [number, number, number]): number;
|
|
17
|
+
/** White or near-black, whichever reads better on the colour. White when the colour cannot be read. */
|
|
18
|
+
export declare function inkOn(color: string | undefined): string;
|
|
19
|
+
/**
|
|
20
|
+
* A logo's address as it may be written into an image's `href`, or `undefined`. An `https:` or `http:` address, a
|
|
21
|
+
* relative path, an image `data:` address, or an SVG document as text (written as an image, which cannot run scripts).
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { safeLogo } from "@johnmorrisdotca/kyogijo-stadiums";
|
|
25
|
+
* console.log(safeLogo("https://example.com/gulls.svg"), safeLogo("javascript:alert(1)"));
|
|
26
|
+
* // https://example.com/gulls.svg undefined
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function safeLogo(value: unknown): string | undefined;
|