@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
package/dist/canvas.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// A scene drawn onto a canvas context: a browser's CanvasRenderingContext2D, an OffscreenCanvas's, or a Node canvas's (@napi-rs/canvas, skia-canvas, node-canvas).
|
|
2
|
+
// Nothing is imported from any of them: the context is typed by the few members this file calls, so any object that has them will do.
|
|
3
|
+
import { rgbOf } from "./colour.js";
|
|
4
|
+
import { apply, boundsOf, IDENTITY, multiply, shapeCommands } from "./scene.path.js";
|
|
5
|
+
import { sceneOf } from "./scene.js";
|
|
6
|
+
/** The colour with an alpha: `rgb(...)` of a hex or rgb colour, or the colour itself when it cannot be read (and the alpha is 1). */
|
|
7
|
+
export function withAlpha(color, alpha) {
|
|
8
|
+
if (alpha >= 1)
|
|
9
|
+
return color;
|
|
10
|
+
const rgba = /^rgba\(\s*(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)[\s,/]+(\d*\.?\d+)\s*\)$/i.exec(color.trim());
|
|
11
|
+
if (rgba !== null)
|
|
12
|
+
return `rgba(${rgba[1]},${rgba[2]},${rgba[3]},${Math.round(Number(rgba[4]) * alpha * 1000) / 1000})`;
|
|
13
|
+
const hex8 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color.trim());
|
|
14
|
+
if (hex8 !== null)
|
|
15
|
+
return `rgba(${parseInt(hex8[1], 16)},${parseInt(hex8[2], 16)},${parseInt(hex8[3], 16)},${Math.round((parseInt(hex8[4], 16) / 255) * alpha * 1000) / 1000})`;
|
|
16
|
+
const rgb = rgbOf(color);
|
|
17
|
+
return rgb === undefined ? color : `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;
|
|
18
|
+
}
|
|
19
|
+
const number = (v) => Math.round(v * 1e6) / 1e6;
|
|
20
|
+
function trace(ctx, commands, matrix = IDENTITY) {
|
|
21
|
+
const at = (x, y) => (matrix === IDENTITY ? [x, y] : apply(matrix, x, y));
|
|
22
|
+
for (const c of commands) {
|
|
23
|
+
if (c[0] === "M")
|
|
24
|
+
ctx.moveTo(...at(c[1], c[2]));
|
|
25
|
+
else if (c[0] === "L")
|
|
26
|
+
ctx.lineTo(...at(c[1], c[2]));
|
|
27
|
+
else if (c[0] === "C")
|
|
28
|
+
ctx.bezierCurveTo(...at(c[1], c[2]), ...at(c[3], c[4]), ...at(c[5], c[6]));
|
|
29
|
+
else if (c[0] === "Q")
|
|
30
|
+
ctx.quadraticCurveTo(...at(c[1], c[2]), ...at(c[3], c[4]));
|
|
31
|
+
else
|
|
32
|
+
ctx.closePath();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const frame = (ctx, m) => ctx.setTransform(number(m[0]), number(m[1]), number(m[2]), number(m[3]), number(m[4]), number(m[5]));
|
|
36
|
+
/** The width and height the options ask for, and the scale and offset that fit the scene in them. */
|
|
37
|
+
function placement(scene, o) {
|
|
38
|
+
const { x: vx, y: vy, w, h } = scene.viewBox;
|
|
39
|
+
let scale = 1;
|
|
40
|
+
if (o.scale !== undefined)
|
|
41
|
+
scale = o.scale;
|
|
42
|
+
else if (o.width !== undefined && o.height !== undefined)
|
|
43
|
+
scale = Math.min(o.width / w, o.height / h);
|
|
44
|
+
else if (o.width !== undefined)
|
|
45
|
+
scale = o.width / w;
|
|
46
|
+
else if (o.height !== undefined)
|
|
47
|
+
scale = o.height / h;
|
|
48
|
+
if (!(scale > 0) || !Number.isFinite(scale))
|
|
49
|
+
throw new RangeError("The size of a canvas drawing must be more than nothing");
|
|
50
|
+
const width = o.width !== undefined && o.height !== undefined ? o.width : w * scale;
|
|
51
|
+
const height = o.width !== undefined && o.height !== undefined ? o.height : h * scale;
|
|
52
|
+
const ox = (o.x ?? 0) + (width - w * scale) / 2;
|
|
53
|
+
const oy = (o.y ?? 0) + (height - h * scale) / 2;
|
|
54
|
+
return { scale, width, height, matrix: [scale, 0, 0, scale, ox - vx * scale, oy - vy * scale] };
|
|
55
|
+
}
|
|
56
|
+
/** Set a fill or stroke style. Returns false when there is nothing to paint. An alpha is written into the colour when it can be, else set on the context. */
|
|
57
|
+
function setPaint(ctx, scene, target, paint, alpha) {
|
|
58
|
+
ctx.globalAlpha = 1;
|
|
59
|
+
if (typeof paint === "string") {
|
|
60
|
+
if (paint === "none")
|
|
61
|
+
return false;
|
|
62
|
+
const colour = withAlpha(paint, alpha);
|
|
63
|
+
ctx[target] = colour;
|
|
64
|
+
if (alpha < 1 && colour === paint)
|
|
65
|
+
ctx.globalAlpha = alpha;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if ("gradient" in paint) {
|
|
69
|
+
const g = scene.gradients.find((one) => one.id === paint.gradient);
|
|
70
|
+
if (g === undefined || ctx.createLinearGradient === undefined)
|
|
71
|
+
return false;
|
|
72
|
+
const gradient = ctx.createLinearGradient(g.x1, g.y1, g.x2, g.y2);
|
|
73
|
+
for (const stop of g.stops)
|
|
74
|
+
gradient.addColorStop(stop.offset, withAlpha(stop.color, stop.opacity * alpha));
|
|
75
|
+
ctx[target] = gradient;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function paintShape(ctx, run, shape, matrix) {
|
|
81
|
+
const s = shape.style;
|
|
82
|
+
frame(ctx, matrix);
|
|
83
|
+
const commands = shapeCommands(shape);
|
|
84
|
+
const filling = s.fill !== "none";
|
|
85
|
+
const stroking = s.stroke !== "none" && s.strokeWidth > 0;
|
|
86
|
+
const fill = () => {
|
|
87
|
+
if (!filling)
|
|
88
|
+
return;
|
|
89
|
+
if (typeof s.fill !== "string" && "pattern" in s.fill) {
|
|
90
|
+
patternFill(ctx, run, shape, matrix, s.fill.pattern);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!setPaint(ctx, run.scene, "fillStyle", s.fill, s.opacity * s.fillOpacity))
|
|
94
|
+
return;
|
|
95
|
+
ctx.beginPath();
|
|
96
|
+
trace(ctx, commands);
|
|
97
|
+
ctx.fill(s.fillRule);
|
|
98
|
+
};
|
|
99
|
+
const stroke = () => {
|
|
100
|
+
if (!stroking || !setPaint(ctx, run.scene, "strokeStyle", s.stroke, s.opacity * s.strokeOpacity))
|
|
101
|
+
return;
|
|
102
|
+
lineStyle(ctx, s);
|
|
103
|
+
ctx.beginPath();
|
|
104
|
+
trace(ctx, commands);
|
|
105
|
+
ctx.stroke();
|
|
106
|
+
};
|
|
107
|
+
if (s.paintOrder === "stroke") {
|
|
108
|
+
stroke();
|
|
109
|
+
fill();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
fill();
|
|
113
|
+
stroke();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function lineStyle(ctx, s) {
|
|
117
|
+
ctx.lineWidth = s.strokeWidth;
|
|
118
|
+
ctx.lineJoin = s.lineJoin;
|
|
119
|
+
ctx.lineCap = s.lineCap;
|
|
120
|
+
ctx.setLineDash(s.dash === undefined ? [] : [...s.dash]);
|
|
121
|
+
}
|
|
122
|
+
/** A pattern fill: the shape's outline is the clip and the tile's shapes are drawn over its box, tile after tile. */
|
|
123
|
+
function patternFill(ctx, run, shape, matrix, id) {
|
|
124
|
+
const pattern = run.scene.patterns.find((one) => one.id === id);
|
|
125
|
+
const box = boundsOf(shape);
|
|
126
|
+
if (pattern === undefined || box === undefined || !(pattern.width > 0) || !(pattern.height > 0))
|
|
127
|
+
return;
|
|
128
|
+
ctx.save();
|
|
129
|
+
frame(ctx, matrix);
|
|
130
|
+
ctx.beginPath();
|
|
131
|
+
trace(ctx, shapeCommands(shape));
|
|
132
|
+
ctx.clip(shape.style.fillRule);
|
|
133
|
+
const [i0, i1] = [Math.floor(box.x / pattern.width), Math.ceil((box.x + box.w) / pattern.width)];
|
|
134
|
+
const [j0, j1] = [Math.floor(box.y / pattern.height), Math.ceil((box.y + box.h) / pattern.height)];
|
|
135
|
+
for (let j = j0; j < j1; j += 1) {
|
|
136
|
+
for (let i = i0; i < i1; i += 1) {
|
|
137
|
+
const tile = multiply(matrix, [1, 0, 0, 1, i * pattern.width, j * pattern.height]);
|
|
138
|
+
for (const part of pattern.shapes)
|
|
139
|
+
paintShape(ctx, run, { ...part, style: { ...part.style, opacity: part.style.opacity * shape.style.opacity } }, multiply(tile, part.matrix));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
ctx.restore();
|
|
143
|
+
}
|
|
144
|
+
function paintText(ctx, run, t, matrix) {
|
|
145
|
+
const s = t.style;
|
|
146
|
+
// A small size is drawn larger and the drawing taken down by the same amount, so that the engine rasterises the letters at a size it renders well.
|
|
147
|
+
const k = t.fontSize < 10 ? 10 / t.fontSize : 1;
|
|
148
|
+
frame(ctx, multiply(matrix, [1 / k, 0, 0, 1 / k, 0, 0]));
|
|
149
|
+
ctx.font = `${t.fontWeight} ${number(t.fontSize * k)}px ${t.fontFamily}`;
|
|
150
|
+
ctx.textAlign = t.anchor === "middle" ? "center" : t.anchor === "end" ? "right" : "left";
|
|
151
|
+
ctx.textBaseline = "alphabetic";
|
|
152
|
+
if (t.letterSpacing !== 0 || ctx.letterSpacing !== undefined)
|
|
153
|
+
ctx.letterSpacing = `${number(t.letterSpacing * k)}px`;
|
|
154
|
+
const x = t.x * k;
|
|
155
|
+
const y = t.y * k;
|
|
156
|
+
const fill = () => {
|
|
157
|
+
if (s.fill !== "none" && setPaint(ctx, run.scene, "fillStyle", s.fill, s.opacity * s.fillOpacity))
|
|
158
|
+
ctx.fillText(t.text, x, y);
|
|
159
|
+
};
|
|
160
|
+
const stroke = () => {
|
|
161
|
+
if (s.stroke === "none" || !(s.strokeWidth > 0) || !setPaint(ctx, run.scene, "strokeStyle", s.stroke, s.opacity * s.strokeOpacity))
|
|
162
|
+
return;
|
|
163
|
+
lineStyle(ctx, { ...s, strokeWidth: s.strokeWidth * k });
|
|
164
|
+
ctx.strokeText(t.text, x, y);
|
|
165
|
+
};
|
|
166
|
+
if (s.paintOrder === "stroke") {
|
|
167
|
+
stroke();
|
|
168
|
+
fill();
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
fill();
|
|
172
|
+
stroke();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function paintImage(ctx, run, image, matrix) {
|
|
176
|
+
if (run.images === undefined || ctx.drawImage === undefined)
|
|
177
|
+
return;
|
|
178
|
+
const picture = run.images(image.href);
|
|
179
|
+
if (picture === undefined || picture === null)
|
|
180
|
+
return;
|
|
181
|
+
frame(ctx, matrix);
|
|
182
|
+
ctx.globalAlpha = image.style.opacity;
|
|
183
|
+
let { x, y, width, height } = image;
|
|
184
|
+
const pw = picture.naturalWidth ?? picture.width;
|
|
185
|
+
const ph = picture.naturalHeight ?? picture.height;
|
|
186
|
+
// `slice` (a photograph that fills a ground): as small as fills the box, in its middle, cut at the box's edges.
|
|
187
|
+
if (/slice/.test(image.preserveAspectRatio ?? "") && typeof pw === "number" && typeof ph === "number" && pw > 0 && ph > 0) {
|
|
188
|
+
const fit = Math.max(width / pw, height / ph);
|
|
189
|
+
ctx.save();
|
|
190
|
+
ctx.beginPath();
|
|
191
|
+
ctx.moveTo(x, y);
|
|
192
|
+
ctx.lineTo(x + width, y);
|
|
193
|
+
ctx.lineTo(x + width, y + height);
|
|
194
|
+
ctx.lineTo(x, y + height);
|
|
195
|
+
ctx.closePath();
|
|
196
|
+
ctx.clip();
|
|
197
|
+
ctx.drawImage(picture, x + (width - pw * fit) / 2, y + (height - ph * fit) / 2, pw * fit, ph * fit);
|
|
198
|
+
ctx.restore();
|
|
199
|
+
ctx.globalAlpha = 1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// The default `xMidYMid meet`: the whole picture, as large as the box allows, in its middle. `none` stretches it.
|
|
203
|
+
if (image.preserveAspectRatio !== "none" && typeof pw === "number" && typeof ph === "number" && pw > 0 && ph > 0) {
|
|
204
|
+
const fit = Math.min(width / pw, height / ph);
|
|
205
|
+
x += (width - pw * fit) / 2;
|
|
206
|
+
y += (height - ph * fit) / 2;
|
|
207
|
+
width = pw * fit;
|
|
208
|
+
height = ph * fit;
|
|
209
|
+
}
|
|
210
|
+
ctx.drawImage(picture, x, y, width, height);
|
|
211
|
+
ctx.globalAlpha = 1;
|
|
212
|
+
}
|
|
213
|
+
function paintItem(ctx, run, item) {
|
|
214
|
+
const matrix = multiply(run.base, item.matrix);
|
|
215
|
+
if (item.style.filter !== undefined && ctx.filter !== undefined)
|
|
216
|
+
ctx.filter = item.style.filter;
|
|
217
|
+
if (item.kind === "text")
|
|
218
|
+
paintText(ctx, run, item, matrix);
|
|
219
|
+
else if (item.kind === "image")
|
|
220
|
+
paintImage(ctx, run, item, matrix);
|
|
221
|
+
else
|
|
222
|
+
paintShape(ctx, run, item, matrix);
|
|
223
|
+
if (item.style.filter !== undefined && ctx.filter !== undefined)
|
|
224
|
+
ctx.filter = "none";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Draw a scene (or the options of `drawKyogijo`, or an SVG the package wrote) onto a canvas context. Give a `width` or a `height` in pixels, or a `scale`; the
|
|
228
|
+
* drawing keeps its shape. Light or dark by `theme`. A team's logo is drawn only if `images` returns a picture for its address.
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* import { drawToCanvas, type CanvasContext } from "@johnmorrisdotca/kyogijo-stadiums/canvas";
|
|
232
|
+
* // Any object with the context's members will do: this one only counts what is drawn.
|
|
233
|
+
* const counts: Record<string, number> = {};
|
|
234
|
+
* const ctx = new Proxy({} as CanvasContext, { get: (_, name: string) => () => void (counts[name] = (counts[name] ?? 0) + 1), set: () => true });
|
|
235
|
+
* const drawn = drawToCanvas(ctx, { sport: "nhl", id: "rink" }, { width: 800 });
|
|
236
|
+
* console.log(drawn.width, Math.round(drawn.height), counts.fill > 10);
|
|
237
|
+
* // 800 374 true
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function drawToCanvas(ctx, source, options = {}) {
|
|
241
|
+
const scene = sceneOf(source, options);
|
|
242
|
+
const { scale, width, height, matrix } = placement(scene, options);
|
|
243
|
+
const box = { x: options.x ?? 0, y: options.y ?? 0 };
|
|
244
|
+
ctx.save();
|
|
245
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
246
|
+
if (options.clear === true && ctx.clearRect !== undefined)
|
|
247
|
+
ctx.clearRect(box.x, box.y, width, height);
|
|
248
|
+
if (options.background !== undefined && ctx.fillRect !== undefined) {
|
|
249
|
+
ctx.fillStyle = options.background;
|
|
250
|
+
ctx.globalAlpha = 1;
|
|
251
|
+
ctx.fillRect(box.x, box.y, width, height);
|
|
252
|
+
}
|
|
253
|
+
const run = { scene, base: matrix, images: options.images };
|
|
254
|
+
let open = "";
|
|
255
|
+
for (const item of scene.items) {
|
|
256
|
+
const key = item.clips.join(",");
|
|
257
|
+
if (key !== open) {
|
|
258
|
+
if (open !== "")
|
|
259
|
+
ctx.restore();
|
|
260
|
+
open = key;
|
|
261
|
+
if (key !== "") {
|
|
262
|
+
ctx.save();
|
|
263
|
+
for (const index of item.clips) {
|
|
264
|
+
const clip = scene.clips[index];
|
|
265
|
+
frame(ctx, multiply(matrix, clip.matrix));
|
|
266
|
+
ctx.beginPath();
|
|
267
|
+
for (const shape of clip.shapes)
|
|
268
|
+
trace(ctx, shapeCommands(shape), shape.matrix);
|
|
269
|
+
ctx.clip();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
paintItem(ctx, run, item);
|
|
274
|
+
}
|
|
275
|
+
if (open !== "")
|
|
276
|
+
ctx.restore();
|
|
277
|
+
ctx.restore();
|
|
278
|
+
return { width, height, scale };
|
|
279
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { nbaShotChart, nflPassChart, nhlShotMap } from "./charts.ts";
|
|
2
|
+
export { mlbSprayChart } from "./charts.baseball.ts";
|
|
3
|
+
export { soccerGoalMouth, soccerShotMap } from "./charts.soccer.ts";
|
|
4
|
+
export { CURLING_COLORS, curlingScore, curlingStones } from "./charts.curling.ts";
|
|
5
|
+
export { passNetwork } from "./charts.network.ts";
|
|
6
|
+
export type { PassNetworkOptions, PassNetworkPass, PassNetworkTotals } from "./charts.network.ts";
|
|
7
|
+
export type { Ball, Chart, ChartOptions, CurlingOptions, CurlingStone, CurlingStoneResult, CurlingTotals, GoalMouth, GoalMouthOptions, KeyItem, MlbSprayOptions, MlbTotals, NbaShot, NbaShotOptions, NbaTotals, NflPass, NflPassOptions, NflTotals, NhlShot, NhlShotOptions, NhlTotals, SoccerChart, SoccerShot, SoccerShotOptions, SoccerTotals, ZoneTotal } from "./charts.types.ts";
|
|
8
|
+
export { ROAD_SIZES, roadToFinal } from "./road.ts";
|
|
9
|
+
export { KYOGIJO_ROAD_STYLE } from "./road.style.ts";
|
|
10
|
+
export type { RoadChart, RoadDecided, RoadFinal, RoadFinalist, RoadGroupMatch, RoadMatch, RoadOptions, RoadRecord, RoadScore, RoadSize, RoadSizeInfo, RoadTotals } from "./road.types.ts";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The ready-made charts: shot charts, shot maps, spray charts and pass charts built on the data layers, and the road to a final.
|
|
2
|
+
export { nbaShotChart, nflPassChart, nhlShotMap } from "./charts.js";
|
|
3
|
+
export { mlbSprayChart } from "./charts.baseball.js";
|
|
4
|
+
export { soccerGoalMouth, soccerShotMap } from "./charts.soccer.js";
|
|
5
|
+
export { CURLING_COLORS, curlingScore, curlingStones } from "./charts.curling.js";
|
|
6
|
+
export { passNetwork } from "./charts.network.js";
|
|
7
|
+
export { ROAD_SIZES, roadToFinal } from "./road.js";
|
|
8
|
+
export { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Ball, Chart, KeyItem, MlbSprayOptions, MlbTotals } from "./charts.types.ts";
|
|
2
|
+
/** A ball's type as one of the five words of the chart, from the words of Statcast's `events` (`home_run`, `field_out`) and the usual short forms; any other word is kept, in lower case. */
|
|
3
|
+
export declare function ballType(type: string | undefined): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* A spray chart for baseball from batted-ball data: where each ball came down, on the ballpark of that season (`park` and `year`, past parks
|
|
6
|
+
* included, each with the fence it had then), each hit type in its own mark (a single a small circle, a double a diamond, a triple a triangle,
|
|
7
|
+
* a home run a star, an out an open circle), the balls that came down beyond the fence drawn beyond it, and if asked the spray zones (pull,
|
|
8
|
+
* centre and opposite by infield and outfield, by the side the batter bats from). Balls come as Statcast's `hc_x` and `hc_y` (`mlbam`), as feet
|
|
9
|
+
* from home plate (`retrosheet`) or as Retrosheet's hit-location codes.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { mlbSprayChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
13
|
+
* const chart = mlbSprayChart(
|
|
14
|
+
* [
|
|
15
|
+
* { x: 125.4, y: 90, type: "single" },
|
|
16
|
+
* { x: 60, y: 60, type: "home_run" },
|
|
17
|
+
* { x: 150, y: 120, type: "field_out" },
|
|
18
|
+
* { zone: "8D", type: "double" },
|
|
19
|
+
* ],
|
|
20
|
+
* { park: "polo-grounds", year: 1915, bats: "R", zones: true },
|
|
21
|
+
* );
|
|
22
|
+
* console.log(chart.totals.balls, chart.totals.types["home run"], chart.options.park, chart.options.year);
|
|
23
|
+
* console.log(chart.draw().includes("Polo Grounds"));
|
|
24
|
+
* // 4 1 polo-grounds 1915
|
|
25
|
+
* // true
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function mlbSprayChart(balls: readonly Ball[], options?: MlbSprayOptions): Chart<MlbTotals> & {
|
|
29
|
+
key: KeyItem[];
|
|
30
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// The spray chart: balls in play on the ballpark of their season, each hit type in its own mark, with the balls that cleared the fence drawn
|
|
2
|
+
// beyond it and, if asked, the spray zones. Written for this package: the spray angle and the Gameday pixels are documented in
|
|
3
|
+
// docs/sources.md (the `mlbam` frame); the code of GeomMLBStadiums (GPL), which draws the same kind of chart, was not read.
|
|
4
|
+
import { fenceAt } from "./geometry.js";
|
|
5
|
+
import { convert } from "./frames.js";
|
|
6
|
+
import { zoneToPoint } from "./frames.providers.js";
|
|
7
|
+
import { outlineOf } from "./outline.js";
|
|
8
|
+
import { fenceFor, parkById } from "./parks.js";
|
|
9
|
+
import { MLB } from "./geometry.js";
|
|
10
|
+
import { fill } from "./strings.js";
|
|
11
|
+
import { assemble, clean, countOutcomes, describeCounts, drawingOptions, frameOf, fromOf, hasPlace, keyItems, keyLayers, languageOf, placed, wordsOf, zoneTotals } from "./charts.js";
|
|
12
|
+
/** The look of each hit type: a shape and a size as well as a fill, so that a type is never told by colour alone. */
|
|
13
|
+
const LOOKS = {
|
|
14
|
+
single: { scale: 0.9 },
|
|
15
|
+
double: { shape: "diamond", scale: 1.15 },
|
|
16
|
+
triple: { shape: "triangle", scale: 1.25 },
|
|
17
|
+
"home run": { shape: "star", scale: 1.55 },
|
|
18
|
+
out: { hollow: true, scale: 0.9 },
|
|
19
|
+
};
|
|
20
|
+
const ORDER = ["single", "double", "triple", "home run", "out"];
|
|
21
|
+
const HITS = ["single", "double", "triple", "home run"];
|
|
22
|
+
/** A ball's type as one of the five words of the chart, from the words of Statcast's `events` (`home_run`, `field_out`) and the usual short forms; any other word is kept, in lower case. */
|
|
23
|
+
export function ballType(type) {
|
|
24
|
+
if (typeof type !== "string" || type.trim() === "")
|
|
25
|
+
return undefined;
|
|
26
|
+
const key = type.trim().toLowerCase().replace(/[_-]+/g, " ");
|
|
27
|
+
const exact = { single: "single", "1b": "single", double: "double", "2b": "double", triple: "triple", "3b": "triple", "home run": "home run", homer: "home run", hr: "home run", out: "out" };
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(exact, key))
|
|
29
|
+
return exact[key];
|
|
30
|
+
// Every way of being put out (a flyout, a groundout, a double play, a fielder's choice, a sacrifice) is an out.
|
|
31
|
+
return /out|play|sac|fielder|choice/.test(key) ? "out" : key;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A spray chart for baseball from batted-ball data: where each ball came down, on the ballpark of that season (`park` and `year`, past parks
|
|
35
|
+
* included, each with the fence it had then), each hit type in its own mark (a single a small circle, a double a diamond, a triple a triangle,
|
|
36
|
+
* a home run a star, an out an open circle), the balls that came down beyond the fence drawn beyond it, and if asked the spray zones (pull,
|
|
37
|
+
* centre and opposite by infield and outfield, by the side the batter bats from). Balls come as Statcast's `hc_x` and `hc_y` (`mlbam`), as feet
|
|
38
|
+
* from home plate (`retrosheet`) or as Retrosheet's hit-location codes.
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { mlbSprayChart } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
42
|
+
* const chart = mlbSprayChart(
|
|
43
|
+
* [
|
|
44
|
+
* { x: 125.4, y: 90, type: "single" },
|
|
45
|
+
* { x: 60, y: 60, type: "home_run" },
|
|
46
|
+
* { x: 150, y: 120, type: "field_out" },
|
|
47
|
+
* { zone: "8D", type: "double" },
|
|
48
|
+
* ],
|
|
49
|
+
* { park: "polo-grounds", year: 1915, bats: "R", zones: true },
|
|
50
|
+
* );
|
|
51
|
+
* console.log(chart.totals.balls, chart.totals.types["home run"], chart.options.park, chart.options.year);
|
|
52
|
+
* console.log(chart.draw().includes("Polo Grounds"));
|
|
53
|
+
* // 4 1 polo-grounds 1915
|
|
54
|
+
* // true
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function mlbSprayChart(balls, options = {}) {
|
|
58
|
+
if (!Array.isArray(balls))
|
|
59
|
+
throw new TypeError("balls is a list of balls");
|
|
60
|
+
const language = languageOf(options);
|
|
61
|
+
const all = wordsOf(language);
|
|
62
|
+
const w = all.charts;
|
|
63
|
+
const frame = frameOf(options.from, "mlbam");
|
|
64
|
+
const park = options.park === undefined ? undefined : parkById(options.park);
|
|
65
|
+
if (options.park !== undefined && options.park !== "generic" && park === undefined)
|
|
66
|
+
throw new RangeError(`Unknown park "${options.park}": use a key of PARKS, such as "fenway-park"`);
|
|
67
|
+
const margin = typeof options.margin === "number" && options.margin >= 0 ? options.margin : 40;
|
|
68
|
+
const o = drawingOptions("mlb", options, w.titleMlb, { margin, ...(options.park === undefined ? {} : { park: options.park }), ...(options.year === undefined ? {} : { year: options.year }) });
|
|
69
|
+
const where = { from: fromOf(options.from, frame) };
|
|
70
|
+
// A ball given by its Retrosheet zone is put at the zone's centre, in the frame of the chart.
|
|
71
|
+
const toFrame = frame.name === "retrosheet" ? undefined : convert("retrosheet", frame);
|
|
72
|
+
const rows = balls.map((b) => {
|
|
73
|
+
let { x, y } = b;
|
|
74
|
+
if ((x === undefined || x === null || y === undefined || y === null) && typeof b.zone === "string") {
|
|
75
|
+
const at = zoneToPoint(b.zone);
|
|
76
|
+
if (at !== undefined)
|
|
77
|
+
[x, y] = toFrame === undefined ? at : toFrame([at])[0];
|
|
78
|
+
}
|
|
79
|
+
return clean({ x: x ?? null, y: y ?? null, outcome: ballType(b.type), value: b.value, team: b.team, title: b.title });
|
|
80
|
+
});
|
|
81
|
+
const { counts, placed: count, skipped } = countOutcomes(rows);
|
|
82
|
+
// The fence of the park in that season, and the balls that came down beyond it: each ball's distance and its angle from dead centre, from the surface's own feet.
|
|
83
|
+
const fence = fenceFor(park, undefined, options.year);
|
|
84
|
+
const at = placed(o, where, rows);
|
|
85
|
+
const beyond = rows.map((row, i) => {
|
|
86
|
+
const p = at[i];
|
|
87
|
+
if (p === undefined)
|
|
88
|
+
return false;
|
|
89
|
+
const angle = (Math.atan2(p[0], -p[1]) * 180) / Math.PI;
|
|
90
|
+
return Math.abs(angle) <= 45 && p[1] < 0 && Math.hypot(p[0], p[1]) > fenceAt(fence, angle);
|
|
91
|
+
});
|
|
92
|
+
const inside = rows.filter((row, i) => !beyond[i]);
|
|
93
|
+
const outside = rows.filter((row, i) => beyond[i]);
|
|
94
|
+
const known = ORDER.filter((k) => counts.has(k));
|
|
95
|
+
const other = [...counts.keys()].filter((k) => k !== "" && !ORDER.includes(k));
|
|
96
|
+
const labels = { single: w.single, double: w.double, triple: w.triple, "home run": w.homeRun, out: w.out };
|
|
97
|
+
const entries = [...known, ...other].map((k) => ({ label: labels[k] ?? k, outcome: k }));
|
|
98
|
+
const layers = [];
|
|
99
|
+
let zonesLayer;
|
|
100
|
+
if (options.zones !== undefined && options.zones !== false) {
|
|
101
|
+
const statistic = options.statistic ?? "share";
|
|
102
|
+
const stat = statistic === "count" ? { statistic: "count" } : statistic === "hit-rate" ? { statistic: "rate", statisticName: w.hitRate, success: HITS } : { statistic: "percent" };
|
|
103
|
+
zonesLayer = clean({
|
|
104
|
+
kind: "zones",
|
|
105
|
+
id: "zones",
|
|
106
|
+
name: w.balls,
|
|
107
|
+
zones: (options.zones === true ? "baseball-spray" : options.zones),
|
|
108
|
+
...where,
|
|
109
|
+
bats: options.bats,
|
|
110
|
+
data: rows,
|
|
111
|
+
...stat,
|
|
112
|
+
success: HITS,
|
|
113
|
+
labels: options.labels === false ? undefined : options.zones === "baseball-retrosheet" ? undefined : { show: ["value"] },
|
|
114
|
+
ramp: options.ramp ?? "ocean",
|
|
115
|
+
legend: options.legend === false ? undefined : true,
|
|
116
|
+
});
|
|
117
|
+
layers.push(zonesLayer);
|
|
118
|
+
}
|
|
119
|
+
const style = { outcomes: LOOKS, scale: 0.8, opacity: 0.9 };
|
|
120
|
+
if (options.marks !== false) {
|
|
121
|
+
const description = describeCounts(entries, counts, all);
|
|
122
|
+
layers.push(clean({ kind: "points", id: "balls", name: w.balls, description: description === "" ? undefined : description, ...where, data: inside, style }));
|
|
123
|
+
if (outside.length > 0) {
|
|
124
|
+
// Over the ground round the fence, held on the drawing, with a note of how many.
|
|
125
|
+
const { box } = outlineOf(o);
|
|
126
|
+
const hold = margin - 6;
|
|
127
|
+
layers.push({
|
|
128
|
+
kind: "points",
|
|
129
|
+
id: "beyond",
|
|
130
|
+
name: w.ballsBeyond,
|
|
131
|
+
description: fill(w.beyondNote, { n: outside.length }),
|
|
132
|
+
...where,
|
|
133
|
+
data: outside,
|
|
134
|
+
style,
|
|
135
|
+
clip: "none",
|
|
136
|
+
limits: { x: [box.x - hold, box.x + box.w + hold], y: [box.y - hold, box.y + box.h + MLB.backstop] },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// The key goes behind home plate, where no ball comes down.
|
|
141
|
+
if (options.key !== false)
|
|
142
|
+
layers.push(...keyLayers(o, entries, LOOKS, all, [], { y: MLB.backstop * 0.5 }));
|
|
143
|
+
const types = {};
|
|
144
|
+
for (const k of [...known, ...other])
|
|
145
|
+
types[k] = counts.get(k) ?? 0;
|
|
146
|
+
const totals = { balls: count, types, beyondFence: outside.filter(hasPlace).length, skipped, zones: zonesLayer === undefined ? [] : zoneTotals(o, zonesLayer) };
|
|
147
|
+
const list = entries.filter((e) => counts.has(e.outcome ?? "")).map((e) => fill(all.layers.item, { name: e.label, n: counts.get(e.outcome ?? "") ?? 0 })).join(all.layers.listSep);
|
|
148
|
+
const summary = [fill(w.sumMlb, { balls: count, list }), outside.length > 0 ? fill(w.beyondNote, { n: outside.length }) : ""].filter((s) => s !== "").join(all.said.gap);
|
|
149
|
+
const chart = assemble(o, layers, { title: w.titleMlb, summary, totals });
|
|
150
|
+
chart.key = keyItems(entries, LOOKS);
|
|
151
|
+
return chart;
|
|
152
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CurlingOptions, CurlingStone, CurlingTotals, Chart, KeyItem } from "./charts.types.ts";
|
|
2
|
+
/** The colours of the two teams' stones, the handles' colours of a game: red and yellow. */
|
|
3
|
+
export declare const CURLING_COLORS: {
|
|
4
|
+
readonly home: "#cf2e2e";
|
|
5
|
+
readonly away: "#f2c230";
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Judge the stones of an end by the rules. A stone is out of play, and taken off the sheet, when it touches a sideline, when it does not come to rest
|
|
9
|
+
* wholly beyond the hog line, or when it has wholly crossed the back line. A stone with any part within 6 feet of the button (in or touching the
|
|
10
|
+
* house) can score; the team with the stone nearest the button scores one point for each of its stones that are nearer than every stone of the other
|
|
11
|
+
* team. Stones as near as each other are equal, and equal stones do not count: if the two nearest are of different teams and as near, the end is
|
|
12
|
+
* blank. Places are feet from the button (in `units`, feet by default), x across and y along.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { curlingScore } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
16
|
+
* const end = curlingScore([
|
|
17
|
+
* { x: 0.3, y: 0.2, team: "home" },
|
|
18
|
+
* { x: -1.5, y: 1, team: "home" },
|
|
19
|
+
* { x: 2, y: -2, team: "away" },
|
|
20
|
+
* ]);
|
|
21
|
+
* console.log(end.scoring, end.points, end.shot);
|
|
22
|
+
* // home 2 0
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function curlingScore(stones: readonly CurlingStone[], options?: {
|
|
26
|
+
units?: CurlingOptions["units"];
|
|
27
|
+
width?: number;
|
|
28
|
+
}): CurlingTotals;
|
|
29
|
+
/**
|
|
30
|
+
* A chart of the stones of an end on the house: each stone as seen from above (grey granite and its team's handle, with its number), the stones
|
|
31
|
+
* that score ringed and the shot rock, the nearest, ringed twice, and the score of the end under the rules (`curlingScore`). It shows the half of
|
|
32
|
+
* the sheet from the hog line to the back line, with the free guard zone shaded, at the house on the left (the top of a tall drawing) or the right.
|
|
33
|
+
* Stones out of play are taken off, as the rules take them. `layerTable` and `chart.table()` list every stone with its distance from the button.
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { curlingStones } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
37
|
+
* const chart = curlingStones(
|
|
38
|
+
* [
|
|
39
|
+
* { x: 0.4, y: 0.3, team: "home", number: 8 },
|
|
40
|
+
* { x: -1.2, y: -0.8, team: "home", number: 6 },
|
|
41
|
+
* { x: 1.9, y: 1.1, team: "away", number: 7 },
|
|
42
|
+
* { x: 0, y: -12, team: "away", number: 3 },
|
|
43
|
+
* ],
|
|
44
|
+
* { end: 6, hammer: "home", names: { home: "Prairie Ridge", away: "Harbour City" } },
|
|
45
|
+
* );
|
|
46
|
+
* console.log(chart.totals.points, chart.totals.scoring, chart.title);
|
|
47
|
+
* // 2 home Curling stones, End 6
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function curlingStones(stones: readonly CurlingStone[], options?: CurlingOptions): Chart<CurlingTotals> & {
|
|
51
|
+
key: KeyItem[];
|
|
52
|
+
};
|