@johnmorrisdotca/kyogijo-stadiums 0.6.2 → 0.7.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 +7 -0
- package/README.md +1 -1
- package/dist/charts-entry.d.ts +0 -3
- package/dist/charts-entry.js +1 -3
- package/dist/layout.js +1 -2
- package/dist/mark.d.ts +1 -1
- package/dist/mark.js +1 -1
- package/dist/strings.d.ts +0 -28
- package/dist/strings.js +0 -20
- package/dist/style.js +1 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/road.d.ts +0 -26
- package/dist/road.draw.d.ts +0 -6
- package/dist/road.draw.js +0 -270
- package/dist/road.js +0 -60
- package/dist/road.model.d.ts +0 -63
- package/dist/road.model.js +0 -215
- package/dist/road.style.d.ts +0 -7
- package/dist/road.style.js +0 -26
- package/dist/road.types.d.ts +0 -125
- package/dist/road.types.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
|
|
14
|
+
- **`roadToFinal` and the road-to-the-final chart moved to [`@johnmorrisdotca/taisen-brackets`](https://www.npmjs.com/package/@johnmorrisdotca/taisen-brackets)** (0.4.0 and later; its `/road` entry), where it is built on the bracket package and shares its look. There is no shim here: import it from taisen. Gone from `@johnmorrisdotca/kyogijo-stadiums/charts`: `roadToFinal`, `ROAD_SIZES`, `KYOGIJO_ROAD_STYLE` (and from `KYOGIJO_STYLE`) and the types `RoadChart`, `RoadDecided`, `RoadFinal`, `RoadFinalist`, `RoadGroupMatch`, `RoadMatch`, `RoadOptions`, `RoadRecord`, `RoadScore`, `RoadSize`, `RoadSizeInfo` and `RoadTotals`. The `road` words are gone from `KYOGIJO_STRINGS` (a custom set of words no longer has to give them), and the docs' section, the demo's "Road to the final" section, its gallery items and its tests went with it.
|
|
15
|
+
- **The round mark stays.** `Team.mark`, `markShape` and `KYOGIJO_MARK_STYLE` are for surfaces and are unchanged. The demo's section for them is now "Round marks" (a pitch wide and tall, and a badge), with its own sixteen made-up nations in `demo/nations.js`, the same flags as before.
|
|
16
|
+
|
|
10
17
|
## [0.6.2] - 2026-10-14
|
|
11
18
|
|
|
12
19
|
### Fixed
|
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ console.log(svg.startsWith("<svg"), svg.includes("Northern Owls 3, Lakeshore Anc
|
|
|
98
98
|
- **Wide, tall or fit the box.** Every surface lies wide or tall: tall turns it a quarter, keeps every word upright and puts the teams top and bottom. With `orientation: "auto"` the browser helpers follow the box.
|
|
99
99
|
- **Where a point lies, by the rules, and a click as a place.** `zoneAt("nhl", [69, 22], { frame: "nhl-api" })` names the zone and the area in it from the rink's, court's, pitch's, park's or field's own figures (the offensive zone and its face-off circle, the frontcourt and the corner three, the attacking third and the six-yard box, the infield and the warning track, the red zone and between the hashes), with `isThreePointer`, `inSlot`, `inPenaltyArea`, `isFair`, `overWall` and the like, and `isHomeRun(park, point, { height })` from the park's fence and wall; `clientPointToField(svg, x, y, { provider })` is a pointer's place in any of the frames, with the zone, and `fieldPointToClient` the way back. `marks: true` writes `data-mark-id`, `data-index` and `data-zone` on every mark of a layer, for one listener to link it to a table. [Zones](./docs/zones.md), [pointing](./docs/pointer.md).
|
|
100
100
|
- **Goals, the strike zone and the stumps, end-on.** A goal mouth for hockey (6 by 4 feet), soccer, handball, water polo, lacrosse (6 by 6) and field hockey with the shots laid on it and its zones (the top shelf, the five-hole, the corners), the catcher's view of the strike zone with its nine and thirteen zones and the pitches numbered, and the wicket from the bowler's end with its line and length zones. [Targets](./docs/targets.md).
|
|
101
|
-
- **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts
|
|
101
|
+
- **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts (the road to a tournament's final moved to [`@johnmorrisdotca/taisen-brackets`](https://www.npmjs.com/package/@johnmorrisdotca/taisen-brackets), its `/road` entry). [Data layers](./docs/layers.md), [charts](./docs/charts.md).
|
|
102
102
|
- **Small multiples and backgrounds.** `drawGrid` puts many drawings in one SVG, under a title and a subtitle, over a credit line, with a key they share, in a size for a social card, a square post or a phone; `background` lays a quiet pattern (felt, grass, wood, ice, paper, a grid, dots) or a photograph behind a drawing or a grid. [Layouts and backgrounds](./docs/layouts.md).
|
|
103
103
|
- **Replay.** Tracking data played on any surface: the players with their numbers, the ball and its trail, and the passes, shots and goals as they happen, with controls, the keyboard and a live region, steps for a reader who prefers reduced motion, and a pause when the page is hidden. [Replay](./docs/replay.md).
|
|
104
104
|
- **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner) and a reset, and what the rulebook stands on it at its height: the net and antennae of volleyball, the nets of tennis, badminton, pickleball and table tennis, baskets and stanchions, rink boards and glass, goals, the posts of football, rugby, Gaelic and Australian football, stumps, a ballpark's fence, the walls of padel, squash and racquetball (cut away on the side nearest the eye), a swimming pool's blocks and flags, a diving well's boards and platforms, a water polo goal, a jump's uprights and bar, a throwing cage, the hurdles, and the padding of a skating track. Plain DOM and CSS over the same SVG, flat where 3D is not drawn. [The 3D view](./docs/turn.md).
|
package/dist/charts-entry.d.ts
CHANGED
|
@@ -10,6 +10,3 @@ export type { SplitRow, SplitsOptions, SplitsTotals, SwimmerSplits } from "./cha
|
|
|
10
10
|
export { passNetwork } from "./charts.network.ts";
|
|
11
11
|
export type { PassNetworkOptions, PassNetworkPass, PassNetworkTotals } from "./charts.network.ts";
|
|
12
12
|
export type { Ball, Chart, ChartOptions, CurlingHeatOptions, CurlingHeatTotals, 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";
|
|
13
|
-
export { ROAD_SIZES, roadToFinal } from "./road.ts";
|
|
14
|
-
export { KYOGIJO_ROAD_STYLE } from "./road.style.ts";
|
|
15
|
-
export type { RoadChart, RoadDecided, RoadFinal, RoadFinalist, RoadGroupMatch, RoadMatch, RoadOptions, RoadRecord, RoadScore, RoadSize, RoadSizeInfo, RoadTotals } from "./road.types.ts";
|
package/dist/charts-entry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The ready-made charts: shot charts, shot maps, spray charts and pass charts built on the data layers
|
|
1
|
+
// The ready-made charts: shot charts, shot maps, spray charts and pass charts built on the data layers.
|
|
2
2
|
export { nbaShotChart, nflPassChart, nhlShotMap } from "./charts.js";
|
|
3
3
|
export { mlbSprayChart } from "./charts.baseball.js";
|
|
4
4
|
export { soccerGoalMouth, soccerShotMap } from "./charts.soccer.js";
|
|
@@ -7,5 +7,3 @@ export { curlingHeat } from "./charts.curling.heat.js";
|
|
|
7
7
|
export { CURLING_HIT_TYPES, curlingEndStats, curlingFreeGuards, curlingGuardScores, curlingHammers, curlingInPlay, curlingIsHit, curlingShotStats } from "./curling.stats.js";
|
|
8
8
|
export { splitsChart } from "./charts.pool.js";
|
|
9
9
|
export { passNetwork } from "./charts.network.js";
|
|
10
|
-
export { ROAD_SIZES, roadToFinal } from "./road.js";
|
|
11
|
-
export { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
package/dist/layout.js
CHANGED
|
@@ -8,7 +8,6 @@ import { drawKyogijo } from "./draw.js";
|
|
|
8
8
|
import { el, esc, hash, num } from "./format.js";
|
|
9
9
|
import { KYOGIJO_GRID_STYLE } from "./layout.style.js";
|
|
10
10
|
import { KYOGIJO_MARK_STYLE } from "./mark.js";
|
|
11
|
-
import { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
|
12
11
|
import { isPolygon, shapePath } from "./layers.marks.js";
|
|
13
12
|
import { elements, parseSvg, textOf, writeSvg } from "./scene.xml.js";
|
|
14
13
|
import { KYOGIJO_STRINGS, fill } from "./strings.js";
|
|
@@ -134,7 +133,7 @@ function renamed(root, prefix) {
|
|
|
134
133
|
return { styles };
|
|
135
134
|
}
|
|
136
135
|
/** The rules the package writes into a drawing, in the order it writes them: peeled off the front of a style so that each is said once however many drawings carry it. */
|
|
137
|
-
const KNOWN = [KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_BACKGROUND_STYLE, KYOGIJO_MARK_STYLE
|
|
136
|
+
const KNOWN = [KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_HEAT_STYLE, KYOGIJO_TEAM_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_BACKGROUND_STYLE, KYOGIJO_MARK_STYLE];
|
|
138
137
|
/** A drawing's stylesheet as the pieces it is made of. */
|
|
139
138
|
function pieces(css) {
|
|
140
139
|
const out = [];
|
package/dist/mark.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface RoundMark {
|
|
|
28
28
|
ring?: number;
|
|
29
29
|
/** The mark's group is turned back by this many degrees: pass the turn of the group the mark is in to hold it upright there. The score does not use it: its mark turns with the zone's words. */
|
|
30
30
|
upright?: number;
|
|
31
|
-
/** A class added to the mark's group
|
|
31
|
+
/** A class added to the mark's group. */
|
|
32
32
|
className?: string;
|
|
33
33
|
}
|
|
34
34
|
/** The markup of one round mark. */
|
package/dist/mark.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The round team mark: a flag or a logo clipped to a circle with a thin ring, or, when a team has no picture, its initials on its colour. One function draws it for the
|
|
2
|
-
// score on a surface (a banner at each end, the badge and the strip under a compact card)
|
|
2
|
+
// score on a surface (a banner at each end, the badge and the strip under a compact card), so a country has the same mark everywhere.
|
|
3
3
|
import { inkOn, safeColour } from "./colour.js";
|
|
4
4
|
import { el, esc, num } from "./format.js";
|
|
5
5
|
import { textWidth } from "./text.js";
|
package/dist/strings.d.ts
CHANGED
|
@@ -901,34 +901,6 @@ export interface KyogijoWords {
|
|
|
901
901
|
cell: string;
|
|
902
902
|
listSep: string;
|
|
903
903
|
};
|
|
904
|
-
/**
|
|
905
|
-
* The words of the road-to-the-final chart (`roadToFinal`): its title when the caller gives none, the names of the soccer knockout rounds (any other round is named by
|
|
906
|
-
* the caller, or by its own key), how a match was decided (`ft`, `aet`, `pens` with {a} and {b}), the results, the table's headings and caption, and the summary.
|
|
907
|
-
*/
|
|
908
|
-
road: {
|
|
909
|
-
title: string;
|
|
910
|
-
vs: string;
|
|
911
|
-
group: string;
|
|
912
|
-
rounds: Record<"round-of-32" | "round-of-16" | "quarter-final" | "semi-final" | "final", string>;
|
|
913
|
-
ft: string;
|
|
914
|
-
aet: string;
|
|
915
|
-
pens: string;
|
|
916
|
-
won: string;
|
|
917
|
-
lost: string;
|
|
918
|
-
drew: string;
|
|
919
|
-
tableTeam: string;
|
|
920
|
-
tableRound: string;
|
|
921
|
-
tableOpponent: string;
|
|
922
|
-
tableScore: string;
|
|
923
|
-
tableDecided: string;
|
|
924
|
-
tableResult: string;
|
|
925
|
-
caption: string;
|
|
926
|
-
name: string;
|
|
927
|
-
summary: string;
|
|
928
|
-
final: string;
|
|
929
|
-
champion: string;
|
|
930
|
-
toPlay: string;
|
|
931
|
-
};
|
|
932
904
|
/** The words of a replay: its summary, its marks, what is announced when something happens, and the controls under it. */
|
|
933
905
|
replay: {
|
|
934
906
|
/** The first sentence of the summary: {time} is the length. */
|
package/dist/strings.js
CHANGED
|
@@ -414,16 +414,6 @@ export const KYOGIJO_STRINGS = {
|
|
|
414
414
|
},
|
|
415
415
|
},
|
|
416
416
|
grid: { title: "A grid of drawings ({n})", drawings: "Drawings ({n}): {list}.", key: "Key: {list}.", cell: "Drawing {n} of {total}", listSep: "; " },
|
|
417
|
-
road: {
|
|
418
|
-
title: "Road to the final", vs: "v", group: "Group stage",
|
|
419
|
-
rounds: { "round-of-32": "Round of 32", "round-of-16": "Round of 16", "quarter-final": "Quarter-final", "semi-final": "Semi-final", final: "Final" },
|
|
420
|
-
ft: "FT", aet: "AET", pens: "{a}-{b} pens", won: "Won", lost: "Lost", drew: "Drew",
|
|
421
|
-
tableTeam: "Team", tableRound: "Round", tableOpponent: "Opponent", tableScore: "Score", tableDecided: "Decided", tableResult: "Result",
|
|
422
|
-
caption: "{a} and {b}: every match on the way to the final. Scores are written with the team in the first column first.",
|
|
423
|
-
name: "{a} and {b}: {title}",
|
|
424
|
-
summary: "{a} played {an} matches on the way to the final and {b} played {bn}.",
|
|
425
|
-
final: "The final: {a} {score} {b} ({how}).", champion: "{name} won the final.", toPlay: "The final is still to be played.",
|
|
426
|
-
},
|
|
427
417
|
replay: {
|
|
428
418
|
title: "Replay, {time}", objects: "{n} tracked objects", objectsOne: "{n} tracked object", events: "{n} events", eventsOne: "{n} event", players: "Players", others: "Ball, puck and officials", trail: "Trail of the last {s} seconds", numbers: "Shirt numbers", eventLines: "Passes and shots",
|
|
429
419
|
home: "home", away: "away", ball: "ball", puck: "puck", official: "official", pass: "Pass", shot: "Shot", goal: "Goal", whistle: "Whistle",
|
|
@@ -959,16 +949,6 @@ export const KYOGIJO_STRINGS = {
|
|
|
959
949
|
},
|
|
960
950
|
},
|
|
961
951
|
grid: { title: "図のグリッド({n}枚)", drawings: "図({n}枚):{list}。", key: "凡例:{list}。", cell: "{total}枚中{n}枚目の図", listSep: ";" },
|
|
962
|
-
road: {
|
|
963
|
-
title: "決勝への道", vs: "対", group: "グループステージ",
|
|
964
|
-
rounds: { "round-of-32": "ラウンド32", "round-of-16": "ラウンド16", "quarter-final": "準々決勝", "semi-final": "準決勝", final: "決勝" },
|
|
965
|
-
ft: "90分", aet: "延長", pens: "PK {a}-{b}", won: "勝ち", lost: "負け", drew: "引き分け",
|
|
966
|
-
tableTeam: "チーム", tableRound: "ラウンド", tableOpponent: "対戦相手", tableScore: "スコア", tableDecided: "決着", tableResult: "結果",
|
|
967
|
-
caption: "{a}と{b}:決勝までの全試合。スコアは「チーム」列のチームの得点を先に書いています。",
|
|
968
|
-
name: "{a}と{b}:{title}",
|
|
969
|
-
summary: "{a}は決勝までに{an}試合、{b}は{bn}試合を戦いました。",
|
|
970
|
-
final: "決勝は{a} {score} {b}({how})でした。", champion: "{name}が優勝しました。", toPlay: "決勝はまだ行われていません。",
|
|
971
|
-
},
|
|
972
952
|
replay: {
|
|
973
953
|
title: "リプレイ(再生時間{time})", objects: "追跡対象は{n}個", objectsOne: "追跡対象は{n}個", events: "イベント{n}件", eventsOne: "イベント{n}件", players: "選手", others: "ボール・パック・審判", trail: "直近{s}秒の軌跡", numbers: "背番号", eventLines: "パスとシュート",
|
|
974
954
|
home: "ホーム", away: "アウェイ", ball: "ボール", puck: "パック", official: "審判", pass: "パス", shot: "シュート", goal: "ゴール", whistle: "ホイッスル",
|
package/dist/style.js
CHANGED
|
@@ -12,7 +12,6 @@ import { KYOGIJO_LOOK_STYLE } from "./look.js";
|
|
|
12
12
|
import { KYOGIJO_MARK_STYLE } from "./mark.js";
|
|
13
13
|
import { KYOGIJO_PAGEANT_MOTION, KYOGIJO_PAGEANT_STYLE } from "./pageant.style.js";
|
|
14
14
|
import { KYOGIJO_POOL_STYLE } from "./pool.style.js";
|
|
15
|
-
import { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
|
16
15
|
import { KYOGIJO_SITUATION_STYLE } from "./situation.style.js";
|
|
17
16
|
import { KYOGIJO_SPECIAL_STYLE } from "./special.style.js";
|
|
18
17
|
/**
|
|
@@ -191,4 +190,4 @@ export const KYOGIJO_NOTE_STYLE = `
|
|
|
191
190
|
.kyogijo .ky-an-ar{stroke:var(--c);fill:none}
|
|
192
191
|
.kyogijo .ky-an-hd{fill:var(--c)}`.trim();
|
|
193
192
|
/** Every rule of the package, for a page that draws with `style: "none"` and supplies the stylesheet itself. */
|
|
194
|
-
export const KYOGIJO_STYLE = `${KYOGIJO_BASE_STYLE}\n${KYOGIJO_HEAT_STYLE}\n${KYOGIJO_TEAM_STYLE}\n${KYOGIJO_CURLING_STYLE}\n${KYOGIJO_FOLD_STYLE}\n${KYOGIJO_WALLS_STYLE}\n${KYOGIJO_COURTS_STYLE}\n${KYOGIJO_FIELDS_STYLE}\n${KYOGIJO_POOL_STYLE}\n${KYOGIJO_ATHLETICS_STYLE}\n${KYOGIJO_SKATING_STYLE}\n${KYOGIJO_BUDO_STYLE}\n${KYOGIJO_LOOK_STYLE}\n${KYOGIJO_HARDWOOD_STYLE}\n${KYOGIJO_TRACK_STYLE}\n${KYOGIJO_FEATURE_STYLE}\n${KYOGIJO_NOTE_STYLE}\n${KYOGIJO_SPECIAL_STYLE}\n${KYOGIJO_BACKGROUND_STYLE}\n${KYOGIJO_CROWD_STYLE}\n${KYOGIJO_GRID_STYLE}\n${KYOGIJO_MARK_STYLE}\n${KYOGIJO_PAGEANT_STYLE}\n${KYOGIJO_PAGEANT_MOTION}\n${KYOGIJO_SITUATION_STYLE}
|
|
193
|
+
export const KYOGIJO_STYLE = `${KYOGIJO_BASE_STYLE}\n${KYOGIJO_HEAT_STYLE}\n${KYOGIJO_TEAM_STYLE}\n${KYOGIJO_CURLING_STYLE}\n${KYOGIJO_FOLD_STYLE}\n${KYOGIJO_WALLS_STYLE}\n${KYOGIJO_COURTS_STYLE}\n${KYOGIJO_FIELDS_STYLE}\n${KYOGIJO_POOL_STYLE}\n${KYOGIJO_ATHLETICS_STYLE}\n${KYOGIJO_SKATING_STYLE}\n${KYOGIJO_BUDO_STYLE}\n${KYOGIJO_LOOK_STYLE}\n${KYOGIJO_HARDWOOD_STYLE}\n${KYOGIJO_TRACK_STYLE}\n${KYOGIJO_FEATURE_STYLE}\n${KYOGIJO_NOTE_STYLE}\n${KYOGIJO_SPECIAL_STYLE}\n${KYOGIJO_BACKGROUND_STYLE}\n${KYOGIJO_CROWD_STYLE}\n${KYOGIJO_GRID_STYLE}\n${KYOGIJO_MARK_STYLE}\n${KYOGIJO_PAGEANT_STYLE}\n${KYOGIJO_PAGEANT_MOTION}\n${KYOGIJO_SITUATION_STYLE}`;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.7.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.7.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kyogijo-stadiums",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Kyogijo Stadiums 競技場, 39 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sports",
|
package/dist/road.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { RoadChart, RoadFinalist, RoadOptions } from "./road.types.ts";
|
|
2
|
-
export { ROAD_SIZES } from "./road.draw.ts";
|
|
3
|
-
/**
|
|
4
|
-
* The road to the final: the two finalists as large round marks with their names at the top, each one's matches on its side (the round mark of both teams with the
|
|
5
|
-
* score between them, and FT, AET or the shoot-out under it), the round names down the middle, and the group stage as a compact row of small marks, codes and scores.
|
|
6
|
-
* A finalist is a team (`name`, `abbr`, `logo` for the flag or the crest, `color`) with its `path`, the matches before the final, the earliest first, and optionally its
|
|
7
|
-
* `group`. A match is `{ round, opponent, score: [for, against], decidedBy, pens }`; a round is a key (`quarter-final`) with words in English and Japanese, or any
|
|
8
|
-
* key you name in `labels`, so a series (`First round`, `4-2`) draws as well as a knockout. A team with no `logo` is drawn as its initials on its colour. The side that won
|
|
9
|
-
* each match is brighter; the sizes are `card` (1080 by 1350), `square`, `wide` (1200 by 675) and `phone` (390 wide). `table()` lists every match as text.
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { roadToFinal, type RoadFinalist } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
13
|
-
* const owls: RoadFinalist = { name: "Northern Owls", abbr: "NOW", color: "#3a2d6b", path: [
|
|
14
|
-
* { round: "quarter-final", opponent: { name: "Coastal Pines", abbr: "CPN" }, score: [2, 0] },
|
|
15
|
-
* { round: "semi-final", opponent: { name: "Valley Stars", abbr: "VAL" }, score: [1, 1], decidedBy: "pens", pens: [4, 3] },
|
|
16
|
-
* ] };
|
|
17
|
-
* const gulls: RoadFinalist = { name: "Harbour City Gulls", abbr: "HCG", color: "#1d4f91", path: [
|
|
18
|
-
* { round: "quarter-final", opponent: { name: "Summit Peaks", abbr: "SMT" }, score: [3, 1] },
|
|
19
|
-
* { round: "semi-final", opponent: { name: "Metro Bolts", abbr: "MTB" }, score: [2, 1], decidedBy: "aet" },
|
|
20
|
-
* ] };
|
|
21
|
-
* const chart = roadToFinal([owls, gulls], { title: "Northern Cup 2030", size: "square" });
|
|
22
|
-
* console.log(chart.totals.rounds.join(" "), chart.draw().startsWith("<svg"), chart.table().includes("Semi-final"));
|
|
23
|
-
* // quarter-final semi-final true true
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export declare function roadToFinal(finalists: readonly [RoadFinalist, RoadFinalist], options?: RoadOptions): RoadChart;
|
package/dist/road.draw.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type Model } from "./road.model.ts";
|
|
2
|
-
import type { RoadOptions, RoadSize, RoadSizeInfo } from "./road.types.ts";
|
|
3
|
-
/** The sizes in pixels: `card` 1080 by 1350, `square` 1080 by 1080, `wide` 1200 by 675 and `phone` 390 wide and as tall as the rounds need. */
|
|
4
|
-
export declare const ROAD_SIZES: Readonly<Record<RoadSize, RoadSizeInfo>>;
|
|
5
|
-
/** Draw the chart as an SVG string. `gid` is the prefix of its ids. */
|
|
6
|
-
export declare function drawRoad(model: Model, o: RoadOptions, gid: string): string;
|
package/dist/road.draw.js
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// The road chart, drawn: the two finalists' big round marks with their names, the round names down the middle on a dotted road, each finalist's matches on its side as
|
|
2
|
-
// the round marks of both teams with the score between them, and the group stage as a compact row at the foot. Each size has its own arrangement: `card`, `square` and
|
|
3
|
-
// `phone` stack the finalists over the rounds; `wide` puts them at the two ends. Nothing is measured in a browser: type is set by estimates (`textWidth`).
|
|
4
|
-
import { safeColour } from "./colour.js";
|
|
5
|
-
import { safeLogo } from "./logo.js";
|
|
6
|
-
import { safeClass } from "./common.js";
|
|
7
|
-
import { el, esc, num } from "./format.js";
|
|
8
|
-
import { KYOGIJO_MARK_STYLE, roundMarkup } from "./mark.js";
|
|
9
|
-
import { codeOf, decidedWords, nameOf, summaryOf } from "./road.model.js";
|
|
10
|
-
import { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
|
11
|
-
import { capitals, fitLines, textWidth } from "./text.js";
|
|
12
|
-
import { VERSION } from "./version.js";
|
|
13
|
-
/** The sizes in pixels: `card` 1080 by 1350, `square` 1080 by 1080, `wide` 1200 by 675 and `phone` 390 wide and as tall as the rounds need. */
|
|
14
|
-
export const ROAD_SIZES = Object.freeze({
|
|
15
|
-
card: { width: 1080, height: 1350, about: "1080 by 1350, a tall post" },
|
|
16
|
-
square: { width: 1080, height: 1080, about: "1080 by 1080, a square post" },
|
|
17
|
-
wide: { width: 1200, height: 675, about: "1200 by 675, a wide card with the finalists at the two ends" },
|
|
18
|
-
phone: { width: 390, height: undefined, about: "390 wide, as tall as the rounds need" },
|
|
19
|
-
});
|
|
20
|
-
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
21
|
-
/** A text centred on `y`, `size` high. */
|
|
22
|
-
const text = (cls, x, y, size, content, anchor = "middle") => el("text", { class: cls, x: num(x), y: num(y + size * 0.36), "font-size": num(size), "text-anchor": anchor }, esc(content));
|
|
23
|
-
/** The width of a match's score block in ems of its number size: the two numbers and the dash. */
|
|
24
|
-
const scoreEms = (v) => (v.whole !== undefined ? textWidth(v.whole) : textWidth(v.mine) + textWidth(v.theirs) + 1.1);
|
|
25
|
-
/** The group stage's score as one text. */
|
|
26
|
-
const sub0 = (v) => v.note !== undefined ? v.note !== "" : v.says;
|
|
27
|
-
const groupScore = (v) => v.whole ?? `${v.mine}–${v.theirs}`;
|
|
28
|
-
/** Draw the chart as an SVG string. `gid` is the prefix of its ids. */
|
|
29
|
-
export function drawRoad(model, o, gid) {
|
|
30
|
-
const size = o.size !== undefined && Object.prototype.hasOwnProperty.call(ROAD_SIZES, o.size) ? o.size : "card";
|
|
31
|
-
const info = ROAD_SIZES[size];
|
|
32
|
-
const w = model.words.road;
|
|
33
|
-
const W = info.width;
|
|
34
|
-
const phone = size === "phone";
|
|
35
|
-
const wide = size === "wide";
|
|
36
|
-
const [fa, fb] = model.finalists;
|
|
37
|
-
const rounds = [...model.rounds].reverse();
|
|
38
|
-
const R = rounds.length;
|
|
39
|
-
const nGroup = Math.max(model.group[0].length, model.group[1].length);
|
|
40
|
-
const emphasis = o.winner !== "none";
|
|
41
|
-
const codes = o.codes !== false;
|
|
42
|
-
const theme = o.theme === "light" || o.theme === "dark" ? o.theme : undefined;
|
|
43
|
-
let marks = 0;
|
|
44
|
-
const markOf = (team, cx, cy, r, cls) => roundMarkup({
|
|
45
|
-
id: `${gid}-m${marks++}`,
|
|
46
|
-
cx,
|
|
47
|
-
cy,
|
|
48
|
-
r,
|
|
49
|
-
href: safeLogo(team.logo, { color: team.color, accent: team.textColor, initials: codeOf(team).slice(0, 3), theme }),
|
|
50
|
-
fit: team.markFit === "contain" ? "contain" : undefined,
|
|
51
|
-
initials: codeOf(team).slice(0, 3),
|
|
52
|
-
color: safeColour(team.color),
|
|
53
|
-
ink: safeColour(team.textColor),
|
|
54
|
-
title: team.name,
|
|
55
|
-
className: cls,
|
|
56
|
-
});
|
|
57
|
-
// ---- The type and the room round the edge. ----
|
|
58
|
-
const tall = info.height ?? 1e9;
|
|
59
|
-
const pad = phone ? 16 : Math.round(Math.min(W * 0.045, tall * 0.05));
|
|
60
|
-
const ts = clamp(Math.min(W * 0.044, tall * 0.056), 17, 48);
|
|
61
|
-
const es = clamp(Math.min(W * 0.0185, tall * 0.022), 9, 20);
|
|
62
|
-
const room = W - 2 * pad;
|
|
63
|
-
const titleFit = fitLines(model.title, room * 0.94, ts * 2.3, ts);
|
|
64
|
-
const subSize = model.subtitle === undefined ? 0 : Math.min(ts * 0.5, (room * 0.94) / Math.max(1, textWidth(model.subtitle)));
|
|
65
|
-
const headH = titleFit.height + (model.subtitle === undefined ? 0 : subSize * 1.5) + ts * 0.45;
|
|
66
|
-
const note = o.endnote === undefined ? "" : String(o.endnote).trim();
|
|
67
|
-
const endSize = note === "" ? 0 : Math.min(es, room / Math.max(1, textWidth(note, false)));
|
|
68
|
-
const footH = endSize === 0 ? 0 : endSize * 2.4;
|
|
69
|
-
// ---- The columns: the middle one holds the rounds' names; a wide chart has a column for each finalist at its end. ----
|
|
70
|
-
const cl = phone ? 70 : wide ? W * 0.12 : W * 0.175;
|
|
71
|
-
const fc = wide ? W * 0.17 : 0;
|
|
72
|
-
const sideW = (W - 2 * pad - cl - 2 * fc) / 2;
|
|
73
|
-
const lanes = [
|
|
74
|
-
{ x0: pad + fc, x1: pad + fc + sideW },
|
|
75
|
-
{ x0: W - pad - fc - sideW, x1: W - pad - fc },
|
|
76
|
-
];
|
|
77
|
-
const cx = W / 2;
|
|
78
|
-
// ---- The group stage: how many marks in a row, and how large. ----
|
|
79
|
-
const maxCols = Math.max(1, Math.floor(sideW / (phone ? 46 : W * 0.062)));
|
|
80
|
-
const cols = nGroup === 0 ? 1 : Math.min(maxCols, nGroup <= 4 ? nGroup : Math.ceil(nGroup / 2));
|
|
81
|
-
const groupRows = nGroup === 0 ? 0 : Math.ceil(nGroup / cols);
|
|
82
|
-
const cw = sideW / cols;
|
|
83
|
-
const dm = Math.min(cw * 0.56, phone ? 36 : Math.min(W * 0.052, tall * 0.065));
|
|
84
|
-
const cellH = dm * 2.08;
|
|
85
|
-
const groupBlock = nGroup === 0 ? 0 : groupRows * cellH + (groupRows - 1) * dm * 0.2 + (phone ? 26 : dm * 0.1);
|
|
86
|
-
// ---- The big marks, the rows and the vertical room. ----
|
|
87
|
-
const rowLabelH = phone ? 22 : 0;
|
|
88
|
-
let H = info.height ?? 0;
|
|
89
|
-
let D = wide ? Math.min(fc * 0.8, H * 0.36) : Math.min(sideW * 0.68, (info.height ?? 1e9) * 0.19);
|
|
90
|
-
const names = (d) => [fa, fb].map((t) => fitLines(capitals(t.name), (wide ? fc : sideW) * 0.96, d * 0.17 * 2.3, d * 0.17));
|
|
91
|
-
const slots = wide ? R + 1 : R;
|
|
92
|
-
const minRow = W * 0.085;
|
|
93
|
-
let rowH = phone ? 76 : 0;
|
|
94
|
-
let bandH = 0;
|
|
95
|
-
let regionTop = 0;
|
|
96
|
-
let regionBottom = 0;
|
|
97
|
-
const gapS = phone ? 14 : (info.height ?? 1000) * 0.016;
|
|
98
|
-
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
99
|
-
bandH = D + D * 0.08 + Math.max(...names(D).map((n) => n.height));
|
|
100
|
-
if (phone) {
|
|
101
|
-
regionTop = pad + headH + bandH + gapS;
|
|
102
|
-
regionBottom = regionTop + R * (rowH + rowLabelH);
|
|
103
|
-
H = regionBottom + (nGroup > 0 ? gapS + groupBlock : 0) + footH + pad;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
regionTop = pad + headH + (wide ? 0 : bandH + gapS);
|
|
107
|
-
regionBottom = H - pad - footH - (nGroup > 0 ? groupBlock + gapS : 0);
|
|
108
|
-
rowH = slots > 0 ? (regionBottom - regionTop) / slots : 0;
|
|
109
|
-
if (wide || rowH >= minRow || D < sideW * 0.25)
|
|
110
|
-
break;
|
|
111
|
-
D *= 0.86;
|
|
112
|
-
}
|
|
113
|
-
rowH = Math.min(rowH, W * 0.2);
|
|
114
|
-
// ---- The pieces of a row: the marks, the numbers, the gaps. ----
|
|
115
|
-
const subRatio = 0.42;
|
|
116
|
-
let ns = rowH * 0.34;
|
|
117
|
-
let d = rowH * 0.58;
|
|
118
|
-
let ems = 2.2;
|
|
119
|
-
for (const key of rounds) {
|
|
120
|
-
for (const i of [0, 1]) {
|
|
121
|
-
const v = model.path[i].get(key);
|
|
122
|
-
if (v !== undefined)
|
|
123
|
-
ems = Math.max(ems, scoreEms(v), textWidth(decidedWords(v, w, true), false) * subRatio);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const fit = Math.min(1, (sideW * 0.97) / (2 * d + 2 * d * 0.14 + ems * ns));
|
|
127
|
-
ns *= fit;
|
|
128
|
-
d *= fit;
|
|
129
|
-
const pair = 2 * d + 2 * d * 0.14 + ems * ns;
|
|
130
|
-
// The top of a row, the first being the last round. A wide chart's first slot is the final.
|
|
131
|
-
const rowTop = (i) => {
|
|
132
|
-
if (phone)
|
|
133
|
-
return regionTop + i * (rowH + rowLabelH) + rowLabelH;
|
|
134
|
-
return regionTop + (regionBottom - regionTop - slots * rowH) / 2 + (wide ? i + 1 : i) * rowH;
|
|
135
|
-
};
|
|
136
|
-
const dimOf = (result, forFinalist) => (emphasis && ((forFinalist && result === "loss") || (!forFinalist && result === "win")) ? " ky-r-dim" : "");
|
|
137
|
-
// The round's name: a panel in the middle column, or a line over the row on a phone.
|
|
138
|
-
const panel = (words, yc, height, width) => {
|
|
139
|
-
const fitted = fitLines(words, width * 0.88, height * 0.74, height * 0.54);
|
|
140
|
-
const lines = fitted.lines.map((line, i) => text("ky-r-lab", cx, yc + (i - (fitted.lines.length - 1) / 2) * fitted.size * 1.1, fitted.size, line));
|
|
141
|
-
return el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(yc - height / 2), width: num(width), height: num(height), rx: num(height / 2) }) + lines.join("");
|
|
142
|
-
};
|
|
143
|
-
const label = (key, yc) => {
|
|
144
|
-
const words = model.label(key);
|
|
145
|
-
if (!phone)
|
|
146
|
-
return panel(words, yc, Math.min(rowH * 0.34, cl * 0.2), cl * 0.94);
|
|
147
|
-
const y = yc - rowH / 2 - rowLabelH / 2;
|
|
148
|
-
const width = textWidth(words) * 11.5 + 20;
|
|
149
|
-
return el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(y - 9), width: num(width), height: 18, rx: 9 }) + text("ky-r-lab", cx, y, 11.5, words);
|
|
150
|
-
};
|
|
151
|
-
// One finalist's match in a row: the two marks and the score between them.
|
|
152
|
-
const matchMarkup = (lane, side, yc, v) => {
|
|
153
|
-
const left = v.homeFirst !== undefined ? v.homeFirst : side === 0;
|
|
154
|
-
const xc = (lane.x0 + lane.x1) / 2;
|
|
155
|
-
const y = yc - rowH * 0.04;
|
|
156
|
-
const lx = xc - pair / 2 + d / 2;
|
|
157
|
-
const rx = xc + pair / 2 - d / 2;
|
|
158
|
-
let out = markOf(model.finalists[side], left ? lx : rx, y, d / 2, `${side === 0 ? "ky-r-left" : "ky-r-right"}${dimOf(v.result, true)}`);
|
|
159
|
-
out += markOf(v.opponent, left ? rx : lx, y, d / 2, `ky-r-plain${dimOf(v.result, false)}`);
|
|
160
|
-
if (codes)
|
|
161
|
-
out += text(`ky-r-code${dimOf(v.result, false)}`, left ? rx : lx, y + d / 2 + d * 0.2, d * 0.2, codeOf(v.opponent));
|
|
162
|
-
out += scoreMarkup(v, xc, y, ns, left);
|
|
163
|
-
const sub = decidedWords(v, w, left);
|
|
164
|
-
return sub === "" ? out : out + text("ky-r-sub", xc, y + ns * 0.82, ns * subRatio, sub);
|
|
165
|
-
};
|
|
166
|
-
// The score: the two numbers either side of a dash, the loser's quiet. The first side's number is on the left when `left`.
|
|
167
|
-
const scoreMarkup = (v, x, y, n, left) => {
|
|
168
|
-
if (v.whole !== undefined)
|
|
169
|
-
return text("ky-r-n", x, y, n * 0.9, v.whole);
|
|
170
|
-
const [a, b, lowA, lowB] = left ? [v.mine, v.theirs, v.result === "loss", v.result === "win"] : [v.theirs, v.mine, v.result === "win", v.result === "loss"];
|
|
171
|
-
return (text(`ky-r-n${emphasis && lowA ? " ky-r-low" : ""}`, x - n * 0.4, y, n, a, "end") +
|
|
172
|
-
text("ky-r-dash", x, y, n * 0.8, "–") +
|
|
173
|
-
text(`ky-r-n${emphasis && lowB ? " ky-r-low" : ""}`, x + n * 0.4, y, n, b, "start"));
|
|
174
|
-
};
|
|
175
|
-
let body = "";
|
|
176
|
-
for (const [i, key] of rounds.entries()) {
|
|
177
|
-
const yc = rowTop(i) + rowH / 2;
|
|
178
|
-
body += label(key, yc);
|
|
179
|
-
for (const side of [0, 1]) {
|
|
180
|
-
const v = model.path[side].get(key);
|
|
181
|
-
if (v !== undefined)
|
|
182
|
-
body += matchMarkup(lanes[side], side, yc, v);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// ---- The finalists. ----
|
|
186
|
-
const final = model.final;
|
|
187
|
-
const lost = (side) => emphasis && final !== undefined && (side === 0 ? final.result === "loss" : final.result === "win");
|
|
188
|
-
const named = names(D);
|
|
189
|
-
const flagX = wide ? [pad + fc / 2, W - pad - fc / 2] : [(lanes[0].x0 + lanes[0].x1) / 2, (lanes[1].x0 + lanes[1].x1) / 2];
|
|
190
|
-
const flagY = wide ? (regionTop + regionBottom) / 2 - (D * 0.08 + Math.max(...named.map((n) => n.height))) / 2 : pad + headH + (phone ? 0 : (info.height ?? 1000) * 0.008) + D / 2;
|
|
191
|
-
let band = "";
|
|
192
|
-
for (const side of [0, 1]) {
|
|
193
|
-
const dim = lost(side) ? " ky-r-dim" : "";
|
|
194
|
-
band += markOf(model.finalists[side], flagX[side], flagY, D / 2, `${side === 0 ? "ky-r-left" : "ky-r-right"}${dim}`);
|
|
195
|
-
const nb = named[side];
|
|
196
|
-
const top = flagY + D / 2 + D * 0.08;
|
|
197
|
-
band += nb.lines.map((line, k) => text(`ky-r-name${dim}`, flagX[side], top + nb.size * 0.55 + k * nb.size * 1.1, nb.size, line)).join("");
|
|
198
|
-
}
|
|
199
|
-
// The final between them: its name over its score, or "v" when it is not played.
|
|
200
|
-
const fy = wide ? rowTop(-1) + rowH / 2 : flagY;
|
|
201
|
-
const labelSize = clamp(cl * 0.13, 9, 26);
|
|
202
|
-
const fs = wide ? Math.min(clamp(cl * 0.27, 14, 58), rowH * 0.36) : clamp(cl * 0.27, 14, 58);
|
|
203
|
-
let middle = text("ky-r-sub", cx, fy - (wide ? fs * 0.95 : Math.min(D * 0.3, cl * 0.34)), labelSize, model.label("final"));
|
|
204
|
-
if (final === undefined)
|
|
205
|
-
middle += text("ky-r-vs", cx, fy + labelSize * 0.2, fs * 0.9, w.vs);
|
|
206
|
-
else {
|
|
207
|
-
middle += scoreMarkup(final, cx, fy, fs, true);
|
|
208
|
-
const sub = decidedWords(final, w, true);
|
|
209
|
-
if (sub !== "")
|
|
210
|
-
middle += text("ky-r-sub", cx, fy + fs * 0.85, fs * 0.4, sub);
|
|
211
|
-
}
|
|
212
|
-
// ---- The road: a dotted line down the middle, behind the round names, from the final down to the group stage. ----
|
|
213
|
-
const groupTop = phone ? regionBottom + gapS : H - pad - footH - groupBlock;
|
|
214
|
-
const roadTop = wide ? fy + rowH * 0.42 : fy + (final === undefined ? labelSize * 1.6 : fs * (sub0(final) ? 1.5 : 1.0));
|
|
215
|
-
const roadEnd = nGroup > 0 ? groupTop + (phone ? 0 : cellH / 2) : R > 0 ? rowTop(R - 1) + rowH / 2 : roadTop;
|
|
216
|
-
const spine = R > 0 && roadEnd > roadTop
|
|
217
|
-
? el("path", { class: "ky-r-spine", d: `M${num(cx)} ${num(roadTop)}V${num(roadEnd)}`, "stroke-width": num(clamp(W * 0.004, 1.5, 4)), "stroke-dasharray": `0.1 ${num(clamp(W * 0.014, 5, 16))}`, fill: "none" })
|
|
218
|
-
: "";
|
|
219
|
-
// ---- The group stage: small round marks with their codes and scores. ----
|
|
220
|
-
let groupMarkup = "";
|
|
221
|
-
if (nGroup > 0) {
|
|
222
|
-
const gy = groupTop + (phone ? 26 : 0);
|
|
223
|
-
if (phone) {
|
|
224
|
-
const width = textWidth(model.groupLabel) * 11.5 + 20;
|
|
225
|
-
groupMarkup += el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(groupTop + 1), width: num(width), height: 18, rx: 9 }) + text("ky-r-lab", cx, groupTop + 10, 11.5, model.groupLabel);
|
|
226
|
-
}
|
|
227
|
-
else
|
|
228
|
-
groupMarkup += panel(model.groupLabel, gy + cellH / 2, Math.min(cellH * 0.42, cl * 0.2), cl * 0.94);
|
|
229
|
-
for (const side of [0, 1]) {
|
|
230
|
-
const lane = lanes[side];
|
|
231
|
-
model.group[side].forEach((v, k) => {
|
|
232
|
-
const row = Math.floor(k / cols);
|
|
233
|
-
const inRow = Math.min(cols, model.group[side].length - row * cols);
|
|
234
|
-
const x = (lane.x0 + lane.x1) / 2 - (inRow * cw) / 2 + (k % cols) * cw + cw / 2;
|
|
235
|
-
const y = gy + row * (cellH + dm * 0.2);
|
|
236
|
-
const dim = dimOf(v.result, false);
|
|
237
|
-
groupMarkup += markOf(v.opponent, x, y + dm / 2, dm / 2, `ky-r-plain${dim}`);
|
|
238
|
-
if (codes)
|
|
239
|
-
groupMarkup += text(`ky-r-code${dim}`, x, y + dm * 1.27, dm * 0.3, codeOf(v.opponent));
|
|
240
|
-
groupMarkup += text(`ky-r-n${emphasis && v.result === "loss" ? " ky-r-low" : ""}`, x, y + dm * 1.78, dm * 0.44, groupScore(v));
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// ---- The head and the foot. ----
|
|
245
|
-
let head = titleFit.lines.map((line, k) => text("ky-r-t", cx, pad + titleFit.size * 0.55 + k * titleFit.size * 1.1, titleFit.size, line)).join("");
|
|
246
|
-
if (model.subtitle !== undefined)
|
|
247
|
-
head += text("ky-r-s", cx, pad + titleFit.height + subSize * 0.7, subSize, model.subtitle);
|
|
248
|
-
const foot = endSize === 0 ? "" : text("ky-r-e", cx, H - pad - endSize * 0.2, endSize, note);
|
|
249
|
-
const sheet = o.style === "none" ? "" : `${KYOGIJO_MARK_STYLE}\n${KYOGIJO_ROAD_STYLE}`;
|
|
250
|
-
const extra = safeClass(o.className);
|
|
251
|
-
return el("svg", {
|
|
252
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
253
|
-
class: `kyogijo kyogijo-road${extra === "" ? "" : ` ${extra}`}`,
|
|
254
|
-
viewBox: `0 0 ${num(W)} ${num(H)}`,
|
|
255
|
-
role: "img",
|
|
256
|
-
"aria-labelledby": `${gid}-t ${gid}-d`,
|
|
257
|
-
"data-size": size,
|
|
258
|
-
"data-theme": theme,
|
|
259
|
-
"data-version": VERSION,
|
|
260
|
-
}, `<title id="${gid}-t">${esc(nameOf(model))}</title><desc id="${gid}-d">${esc(summaryOf(model))}</desc>` +
|
|
261
|
-
(sheet === "" ? "" : `<style>${sheet}</style>`) +
|
|
262
|
-
el("rect", { class: "ky-r-bg", x: 0, y: 0, width: num(W), height: num(H) }) +
|
|
263
|
-
head +
|
|
264
|
-
spine +
|
|
265
|
-
band +
|
|
266
|
-
middle +
|
|
267
|
-
body +
|
|
268
|
-
groupMarkup +
|
|
269
|
-
foot);
|
|
270
|
-
}
|
package/dist/road.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// The road-to-the-final chart: the two finalists of a tournament with the matches each played on the way, as round flags (or crests) of both teams with the score between
|
|
2
|
-
// them, the rounds named down the middle, and the group stage as a compact row at the foot. The idea is that of the "road to the final" graphics that sports sites post
|
|
3
|
-
// for a World Cup or a Stanley Cup; the arrangement, the type and the code are this package's own, and no one's artwork is used: the marks are the caller's flags and
|
|
4
|
-
// logos, or the team's initials on its colour.
|
|
5
|
-
import { withEmblems } from "./emblems-source.js";
|
|
6
|
-
import { hash } from "./format.js";
|
|
7
|
-
import { safeClass } from "./common.js";
|
|
8
|
-
import { drawRoad } from "./road.draw.js";
|
|
9
|
-
import { nameOf, readModel, summaryOf, tableOf, totalsOf } from "./road.model.js";
|
|
10
|
-
import { VERSION } from "./version.js";
|
|
11
|
-
export { ROAD_SIZES } from "./road.draw.js";
|
|
12
|
-
const SIZES = ["card", "square", "wide", "phone"];
|
|
13
|
-
/**
|
|
14
|
-
* The road to the final: the two finalists as large round marks with their names at the top, each one's matches on its side (the round mark of both teams with the
|
|
15
|
-
* score between them, and FT, AET or the shoot-out under it), the round names down the middle, and the group stage as a compact row of small marks, codes and scores.
|
|
16
|
-
* A finalist is a team (`name`, `abbr`, `logo` for the flag or the crest, `color`) with its `path`, the matches before the final, the earliest first, and optionally its
|
|
17
|
-
* `group`. A match is `{ round, opponent, score: [for, against], decidedBy, pens }`; a round is a key (`quarter-final`) with words in English and Japanese, or any
|
|
18
|
-
* key you name in `labels`, so a series (`First round`, `4-2`) draws as well as a knockout. A team with no `logo` is drawn as its initials on its colour. The side that won
|
|
19
|
-
* each match is brighter; the sizes are `card` (1080 by 1350), `square`, `wide` (1200 by 675) and `phone` (390 wide). `table()` lists every match as text.
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* import { roadToFinal, type RoadFinalist } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
23
|
-
* const owls: RoadFinalist = { name: "Northern Owls", abbr: "NOW", color: "#3a2d6b", path: [
|
|
24
|
-
* { round: "quarter-final", opponent: { name: "Coastal Pines", abbr: "CPN" }, score: [2, 0] },
|
|
25
|
-
* { round: "semi-final", opponent: { name: "Valley Stars", abbr: "VAL" }, score: [1, 1], decidedBy: "pens", pens: [4, 3] },
|
|
26
|
-
* ] };
|
|
27
|
-
* const gulls: RoadFinalist = { name: "Harbour City Gulls", abbr: "HCG", color: "#1d4f91", path: [
|
|
28
|
-
* { round: "quarter-final", opponent: { name: "Summit Peaks", abbr: "SMT" }, score: [3, 1] },
|
|
29
|
-
* { round: "semi-final", opponent: { name: "Metro Bolts", abbr: "MTB" }, score: [2, 1], decidedBy: "aet" },
|
|
30
|
-
* ] };
|
|
31
|
-
* const chart = roadToFinal([owls, gulls], { title: "Northern Cup 2030", size: "square" });
|
|
32
|
-
* console.log(chart.totals.rounds.join(" "), chart.draw().startsWith("<svg"), chart.table().includes("Semi-final"));
|
|
33
|
-
* // quarter-final semi-final true true
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export function roadToFinal(finalists, options = {}) {
|
|
37
|
-
if (typeof options !== "object" || options === null)
|
|
38
|
-
throw new TypeError("options is an object");
|
|
39
|
-
const check = (o) => {
|
|
40
|
-
if (o.size !== undefined && !SIZES.includes(o.size))
|
|
41
|
-
throw new RangeError(`Unknown size "${String(o.size)}": use one of ${SIZES.join(", ")}`);
|
|
42
|
-
};
|
|
43
|
-
check(options);
|
|
44
|
-
const model = readModel(finalists, options);
|
|
45
|
-
const build = (extra = {}) => {
|
|
46
|
-
const merged = { ...options, ...extra };
|
|
47
|
-
check(merged);
|
|
48
|
-
const m = readModel(finalists, merged);
|
|
49
|
-
const gid = safeClass(merged.id).replace(/ /g, "-") || `kr-${hash(JSON.stringify([finalists, merged, VERSION]))}`;
|
|
50
|
-
return withEmblems(merged.emblems, () => drawRoad(m, merged, gid));
|
|
51
|
-
};
|
|
52
|
-
return {
|
|
53
|
-
options: { ...options },
|
|
54
|
-
title: nameOf(model),
|
|
55
|
-
summary: summaryOf(model),
|
|
56
|
-
totals: totalsOf(model),
|
|
57
|
-
draw: build,
|
|
58
|
-
table: (tableOptions = {}) => tableOf(tableOptions.language === undefined ? model : readModel(finalists, { ...options, language: tableOptions.language }), tableOptions),
|
|
59
|
-
};
|
|
60
|
-
}
|
package/dist/road.model.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { type KyogijoWords } from "./strings.ts";
|
|
2
|
-
import type { RoadDecided, RoadFinalist, RoadOptions, RoadTotals } from "./road.types.ts";
|
|
3
|
-
import type { Language, Team } from "./types.ts";
|
|
4
|
-
/** Who won a match, from the finalist's side. `unknown` when the score is not a pair of numbers. */
|
|
5
|
-
export type Result = "win" | "loss" | "draw" | "unknown";
|
|
6
|
-
/** A match as it is drawn: the two scores written, who won, and how it was decided. */
|
|
7
|
-
export interface MatchView {
|
|
8
|
-
/** The round's key; empty for a match of the group stage. */
|
|
9
|
-
round: string;
|
|
10
|
-
opponent: Team;
|
|
11
|
-
/** The finalist's score and the opponent's, written. For a score that is one text that could not be split, `whole` is that text and the two are empty. */
|
|
12
|
-
mine: string;
|
|
13
|
-
theirs: string;
|
|
14
|
-
whole?: string;
|
|
15
|
-
result: Result;
|
|
16
|
-
decided: RoadDecided;
|
|
17
|
-
pens?: [string, string];
|
|
18
|
-
/** The words that replace the usual ones under the score, or undefined. */
|
|
19
|
-
note?: string;
|
|
20
|
-
/** Whether the usual words (FT, AET, the shoot-out) are written: always for a knockout match, only when asked for a group match. */
|
|
21
|
-
says: boolean;
|
|
22
|
-
homeFirst?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/** The chart's data, ready to draw. */
|
|
25
|
-
export interface Model {
|
|
26
|
-
language: Language;
|
|
27
|
-
words: KyogijoWords;
|
|
28
|
-
finalists: [Team, Team];
|
|
29
|
-
/** The matches of the group stage, for each finalist. */
|
|
30
|
-
group: [MatchView[], MatchView[]];
|
|
31
|
-
/** The knockout matches for each finalist, by round key. */
|
|
32
|
-
path: [Map<string, MatchView>, Map<string, MatchView>];
|
|
33
|
-
/** The rounds, the earliest first. */
|
|
34
|
-
rounds: string[];
|
|
35
|
-
/** The final, if played: the scores of the first finalist and the second. */
|
|
36
|
-
final?: MatchView;
|
|
37
|
-
label(key: string): string;
|
|
38
|
-
groupLabel: string;
|
|
39
|
-
title: string;
|
|
40
|
-
subtitle: string | undefined;
|
|
41
|
-
}
|
|
42
|
-
/** A number from a number or from text that is one, else undefined. */
|
|
43
|
-
export declare function numeric(value: unknown): number | undefined;
|
|
44
|
-
/** The rounds of two paths, the earliest first: a round both have is once, and a round only one has stays after the round before it. */
|
|
45
|
-
export declare function mergeRounds(paths: readonly (readonly string[])[]): string[];
|
|
46
|
-
/** Read and check what `roadToFinal` was given. */
|
|
47
|
-
export declare function readModel(finalists: readonly RoadFinalist[], options: RoadOptions): Model;
|
|
48
|
-
/** The words under a score: FT, AET or the shoot-out (the first side's numbers first), a note in their place, or nothing. */
|
|
49
|
-
export declare function decidedWords(view: MatchView, w: KyogijoWords["road"], mineFirst: boolean): string;
|
|
50
|
-
/** The score of a match as one text, the first side's first. */
|
|
51
|
-
export declare function scoreText(view: MatchView, mineFirst?: boolean): string;
|
|
52
|
-
/** The abbreviation written under a mark: the team's own, else made from its name. */
|
|
53
|
-
export declare function codeOf(team: Team): string;
|
|
54
|
-
/** The numbers behind the chart. */
|
|
55
|
-
export declare function totalsOf(model: Model): RoadTotals;
|
|
56
|
-
/** The chart's accessible name. */
|
|
57
|
-
export declare function nameOf(model: Model): string;
|
|
58
|
-
/** What the chart shows, in a sentence or two. */
|
|
59
|
-
export declare function summaryOf(model: Model): string;
|
|
60
|
-
/** One HTML table with a row for every match: the group stage, the knockout rounds in order, and the final. */
|
|
61
|
-
export declare function tableOf(model: Model, options?: {
|
|
62
|
-
className?: string;
|
|
63
|
-
}): string;
|
package/dist/road.model.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// The road chart's data, read and checked: the scores split into the two sides, who won each match, the words under each score, the rounds in the order played, and
|
|
2
|
-
// the sentences and the table that say the same in words. Nothing here draws.
|
|
3
|
-
import { esc } from "./format.js";
|
|
4
|
-
import { safeClass } from "./common.js";
|
|
5
|
-
import { KYOGIJO_STRINGS, fill } from "./strings.js";
|
|
6
|
-
import { abbreviate } from "./text.js";
|
|
7
|
-
const own = (object, key) => object !== undefined && Object.prototype.hasOwnProperty.call(object, key);
|
|
8
|
-
/** A number from a number or from text that is one, else undefined. */
|
|
9
|
-
export function numeric(value) {
|
|
10
|
-
if (typeof value === "number")
|
|
11
|
-
return Number.isFinite(value) ? value : undefined;
|
|
12
|
-
if (typeof value === "string" && /^\s*-?\d+(?:\.\d+)?\s*$/.test(value))
|
|
13
|
-
return Number(value);
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
const written = (value) => (value === undefined || value === null ? "" : String(value).trim());
|
|
17
|
-
/** The two sides of a score. */
|
|
18
|
-
function split(score) {
|
|
19
|
-
if (Array.isArray(score) && score.length === 2)
|
|
20
|
-
return { mine: written(score[0]), theirs: written(score[1]) };
|
|
21
|
-
const text = written(score);
|
|
22
|
-
const parts = /^(\S+?)\s*[-–—:]\s*(\S+)$/.exec(text);
|
|
23
|
-
if (parts !== null)
|
|
24
|
-
return { mine: parts[1], theirs: parts[2] };
|
|
25
|
-
return { mine: "", theirs: "", whole: text };
|
|
26
|
-
}
|
|
27
|
-
function resultOf(mine, theirs, pens) {
|
|
28
|
-
const a = numeric(mine);
|
|
29
|
-
const b = numeric(theirs);
|
|
30
|
-
if (a === undefined || b === undefined)
|
|
31
|
-
return "unknown";
|
|
32
|
-
if (a !== b)
|
|
33
|
-
return a > b ? "win" : "loss";
|
|
34
|
-
const p = pens === undefined ? undefined : [numeric(pens[0]), numeric(pens[1])];
|
|
35
|
-
if (p !== undefined && p[0] !== undefined && p[1] !== undefined && p[0] !== p[1])
|
|
36
|
-
return p[0] > p[1] ? "win" : "loss";
|
|
37
|
-
return "draw";
|
|
38
|
-
}
|
|
39
|
-
function pensOf(value) {
|
|
40
|
-
return Array.isArray(value) && value.length === 2 && written(value[0]) !== "" && written(value[1]) !== "" ? [written(value[0]), written(value[1])] : undefined;
|
|
41
|
-
}
|
|
42
|
-
function viewOf(m, opponent, round, knockout) {
|
|
43
|
-
const { mine, theirs, whole } = split(m.score);
|
|
44
|
-
const pens = pensOf(m.pens);
|
|
45
|
-
const decided = m.decidedBy === "aet" || m.decidedBy === "pens" || m.decidedBy === "ft" ? m.decidedBy : pens !== undefined ? "pens" : "ft";
|
|
46
|
-
const note = typeof m.note === "string" ? m.note.trim() : undefined;
|
|
47
|
-
return {
|
|
48
|
-
round,
|
|
49
|
-
opponent,
|
|
50
|
-
mine,
|
|
51
|
-
theirs,
|
|
52
|
-
...(whole === undefined ? {} : { whole }),
|
|
53
|
-
result: resultOf(mine, theirs, pens),
|
|
54
|
-
decided,
|
|
55
|
-
...(pens === undefined ? {} : { pens }),
|
|
56
|
-
...(note === undefined ? {} : { note }),
|
|
57
|
-
says: knockout || m.decidedBy !== undefined || pens !== undefined,
|
|
58
|
-
...("homeFirst" in m && typeof m.homeFirst === "boolean" ? { homeFirst: m.homeFirst } : {}),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/** The rounds of two paths, the earliest first: a round both have is once, and a round only one has stays after the round before it. */
|
|
62
|
-
export function mergeRounds(paths) {
|
|
63
|
-
const out = [];
|
|
64
|
-
for (const path of paths) {
|
|
65
|
-
let at = 0;
|
|
66
|
-
for (const key of path) {
|
|
67
|
-
const found = out.indexOf(key);
|
|
68
|
-
if (found >= 0)
|
|
69
|
-
at = found + 1;
|
|
70
|
-
else {
|
|
71
|
-
out.splice(at, 0, key);
|
|
72
|
-
at += 1;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
const isTeam = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.name === "string";
|
|
79
|
-
/** Read and check what `roadToFinal` was given. */
|
|
80
|
-
export function readModel(finalists, options) {
|
|
81
|
-
if (!Array.isArray(finalists) || finalists.length !== 2)
|
|
82
|
-
throw new TypeError("roadToFinal takes the two finalists as a list of two teams, each with a path");
|
|
83
|
-
const language = options.language === "ja" ? "ja" : "en";
|
|
84
|
-
const words = KYOGIJO_STRINGS[language];
|
|
85
|
-
const w = words.road;
|
|
86
|
-
const teams = finalists;
|
|
87
|
-
for (const [i, f] of teams.entries()) {
|
|
88
|
-
if (!isTeam(f))
|
|
89
|
-
throw new TypeError(`Finalist ${i + 1} is a team with a name`);
|
|
90
|
-
if (!Array.isArray(f.path))
|
|
91
|
-
throw new TypeError(`Finalist ${i + 1} (${f.name}) needs a path: the list of its matches before the final`);
|
|
92
|
-
if (f.group !== undefined && !Array.isArray(f.group))
|
|
93
|
-
throw new TypeError(`The group of ${f.name} is a list of matches`);
|
|
94
|
-
for (const m of [...f.path, ...(f.group ?? [])]) {
|
|
95
|
-
if (typeof m !== "object" || m === null || !isTeam(m.opponent) || m.score === undefined || m.score === null)
|
|
96
|
-
throw new TypeError(`Every match of ${f.name} has an opponent (a team with a name) and a score`);
|
|
97
|
-
}
|
|
98
|
-
for (const m of f.path)
|
|
99
|
-
if (typeof m.round !== "string" || m.round.trim() === "")
|
|
100
|
-
throw new TypeError(`Every match of the path of ${f.name} has a round`);
|
|
101
|
-
}
|
|
102
|
-
const path = teams.map((f) => {
|
|
103
|
-
const map = new Map();
|
|
104
|
-
for (const m of f.path)
|
|
105
|
-
map.set(m.round.trim(), viewOf(m, m.opponent, m.round.trim(), true));
|
|
106
|
-
return map;
|
|
107
|
-
});
|
|
108
|
-
const group = teams.map((f) => (f.group ?? []).map((m) => viewOf(m, m.opponent, "", false)));
|
|
109
|
-
const rounds = mergeRounds(teams.map((f) => f.path.map((m) => m.round.trim())));
|
|
110
|
-
const labels = options.labels;
|
|
111
|
-
const model = {
|
|
112
|
-
language,
|
|
113
|
-
words,
|
|
114
|
-
finalists: teams,
|
|
115
|
-
group,
|
|
116
|
-
path,
|
|
117
|
-
rounds,
|
|
118
|
-
label: (key) => (own(labels, key) && typeof labels?.[key] === "string" ? labels[key] : own(w.rounds, key) ? w.rounds[key] : key),
|
|
119
|
-
groupLabel: typeof options.groupLabel === "string" && options.groupLabel.trim() !== "" ? options.groupLabel.trim() : w.group,
|
|
120
|
-
title: typeof options.title === "string" && options.title.trim() !== "" ? options.title.trim() : w.title,
|
|
121
|
-
subtitle: typeof options.subtitle === "string" && options.subtitle.trim() !== "" ? options.subtitle.trim() : typeof options.title === "string" && options.title.trim() !== "" ? w.title : undefined,
|
|
122
|
-
};
|
|
123
|
-
if (options.final !== undefined) {
|
|
124
|
-
if (typeof options.final !== "object" || options.final === null || options.final.score === undefined || options.final.score === null)
|
|
125
|
-
throw new TypeError("The final is { score }: the first finalist's score first");
|
|
126
|
-
model.final = viewOf(options.final, teams[1], "final", true);
|
|
127
|
-
}
|
|
128
|
-
return model;
|
|
129
|
-
}
|
|
130
|
-
/** The words under a score: FT, AET or the shoot-out (the first side's numbers first), a note in their place, or nothing. */
|
|
131
|
-
export function decidedWords(view, w, mineFirst) {
|
|
132
|
-
if (view.note !== undefined)
|
|
133
|
-
return view.note;
|
|
134
|
-
if (!view.says)
|
|
135
|
-
return "";
|
|
136
|
-
if (view.decided === "aet")
|
|
137
|
-
return w.aet;
|
|
138
|
-
if (view.decided === "pens" && view.pens !== undefined)
|
|
139
|
-
return fill(w.pens, mineFirst ? { a: view.pens[0], b: view.pens[1] } : { a: view.pens[1], b: view.pens[0] });
|
|
140
|
-
return view.decided === "pens" ? w.aet : w.ft;
|
|
141
|
-
}
|
|
142
|
-
/** The score of a match as one text, the first side's first. */
|
|
143
|
-
export function scoreText(view, mineFirst = true) {
|
|
144
|
-
if (view.whole !== undefined)
|
|
145
|
-
return view.whole;
|
|
146
|
-
return mineFirst ? `${view.mine}-${view.theirs}` : `${view.theirs}-${view.mine}`;
|
|
147
|
-
}
|
|
148
|
-
/** The abbreviation written under a mark: the team's own, else made from its name. */
|
|
149
|
-
export function codeOf(team) {
|
|
150
|
-
return (typeof team.abbr === "string" && team.abbr.trim() !== "" ? team.abbr.trim() : abbreviate(String(team.name ?? ""))).slice(0, 5).toLocaleUpperCase("en");
|
|
151
|
-
}
|
|
152
|
-
function record(f, group, path) {
|
|
153
|
-
const all = [...group, ...path.values()];
|
|
154
|
-
let scored = 0;
|
|
155
|
-
let conceded = 0;
|
|
156
|
-
for (const v of all) {
|
|
157
|
-
const a = numeric(v.mine);
|
|
158
|
-
const b = numeric(v.theirs);
|
|
159
|
-
if (a !== undefined && b !== undefined) {
|
|
160
|
-
scored += a;
|
|
161
|
-
conceded += b;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { name: f.name, matches: all.length, won: all.filter((v) => v.result === "win").length, drawn: all.filter((v) => v.result === "draw").length, lost: all.filter((v) => v.result === "loss").length, scored, conceded };
|
|
165
|
-
}
|
|
166
|
-
/** The numbers behind the chart. */
|
|
167
|
-
export function totalsOf(model) {
|
|
168
|
-
const [a, b] = model.finalists;
|
|
169
|
-
const final = model.final;
|
|
170
|
-
const champion = final === undefined || final.result === "unknown" || final.result === "draw" ? null : final.result === "win" ? a.name : b.name;
|
|
171
|
-
return { finalists: [record(a, model.group[0], model.path[0]), record(b, model.group[1], model.path[1])], rounds: [...model.rounds], played: final !== undefined, champion };
|
|
172
|
-
}
|
|
173
|
-
/** The chart's accessible name. */
|
|
174
|
-
export function nameOf(model) {
|
|
175
|
-
const heading = [model.title, model.subtitle].filter((part) => part !== undefined).join(model.words.said.sep);
|
|
176
|
-
return fill(model.words.road.name, { a: model.finalists[0].name, b: model.finalists[1].name, title: heading });
|
|
177
|
-
}
|
|
178
|
-
/** What the chart shows, in a sentence or two. */
|
|
179
|
-
export function summaryOf(model) {
|
|
180
|
-
const w = model.words.road;
|
|
181
|
-
const [a, b] = model.finalists;
|
|
182
|
-
const t = totalsOf(model);
|
|
183
|
-
const parts = [fill(w.summary, { a: a.name, b: b.name, an: t.finalists[0].matches, bn: t.finalists[1].matches })];
|
|
184
|
-
const final = model.final;
|
|
185
|
-
if (final === undefined)
|
|
186
|
-
parts.push(w.toPlay);
|
|
187
|
-
else {
|
|
188
|
-
parts.push(fill(w.final, { a: a.name, b: b.name, score: scoreText(final), how: decidedWords(final, w, true) || w.ft }));
|
|
189
|
-
if (t.champion !== null)
|
|
190
|
-
parts.push(fill(w.champion, { name: t.champion }));
|
|
191
|
-
}
|
|
192
|
-
return parts.join(model.words.said.gap);
|
|
193
|
-
}
|
|
194
|
-
/** One HTML table with a row for every match: the group stage, the knockout rounds in order, and the final. */
|
|
195
|
-
export function tableOf(model, options = {}) {
|
|
196
|
-
const w = model.words.road;
|
|
197
|
-
const cls = safeClass(options.className) || "ky-table";
|
|
198
|
-
const [a, b] = model.finalists;
|
|
199
|
-
const result = (r) => (r === "win" ? w.won : r === "loss" ? w.lost : r === "draw" ? w.drew : "");
|
|
200
|
-
const row = (team, round, v) => `<tr><th scope="row">${esc(team.name)}</th><td>${esc(round)}</td><td>${esc(v.opponent.name)}</td><td>${esc(scoreText(v))}</td><td>${esc(decidedWords(v, w, true))}</td><td>${esc(result(v.result))}</td></tr>`;
|
|
201
|
-
const rows = [];
|
|
202
|
-
for (const [i, team] of [a, b].entries()) {
|
|
203
|
-
for (const v of model.group[i])
|
|
204
|
-
rows.push(row(team, model.groupLabel, v));
|
|
205
|
-
for (const key of model.rounds) {
|
|
206
|
-
const v = model.path[i].get(key);
|
|
207
|
-
if (v !== undefined)
|
|
208
|
-
rows.push(row(team, model.label(key), v));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (model.final !== undefined)
|
|
212
|
-
rows.push(row(a, model.label("final"), { ...model.final, opponent: b }));
|
|
213
|
-
const head = [w.tableTeam, w.tableRound, w.tableOpponent, w.tableScore, w.tableDecided, w.tableResult].map((name) => `<th scope="col">${esc(name)}</th>`).join("");
|
|
214
|
-
return `<table class="${cls}" data-kind="road"><caption>${esc(fill(w.caption, { a: a.name, b: b.name }))}</caption><thead><tr>${head}</tr></thead><tbody>${rows.join("")}</tbody></table>`;
|
|
215
|
-
}
|
package/dist/road.style.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rules of the road-to-the-final chart (`roadToFinal`): the ground, the ink and the quiet ink in light and dark, the panel behind a round's name, the road down the
|
|
3
|
-
* middle, and the classes of the title, the names, the scores and the small words under them. The colours are custom properties on the chart (`--ky-road-bg`,
|
|
4
|
-
* `--ky-road-ink`, `--ky-road-muted`, `--ky-road-panel`, `--ky-road-spine`, `--ky-road-left`, `--ky-road-right`), so a page can set them. A chart writes these rules in,
|
|
5
|
-
* with the round mark's (`KYOGIJO_MARK_STYLE`); `KYOGIJO_STYLE` has them for a page that draws with `style: "none"`.
|
|
6
|
-
*/
|
|
7
|
-
export declare const KYOGIJO_ROAD_STYLE: string;
|
package/dist/road.style.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rules of the road-to-the-final chart (`roadToFinal`): the ground, the ink and the quiet ink in light and dark, the panel behind a round's name, the road down the
|
|
3
|
-
* middle, and the classes of the title, the names, the scores and the small words under them. The colours are custom properties on the chart (`--ky-road-bg`,
|
|
4
|
-
* `--ky-road-ink`, `--ky-road-muted`, `--ky-road-panel`, `--ky-road-spine`, `--ky-road-left`, `--ky-road-right`), so a page can set them. A chart writes these rules in,
|
|
5
|
-
* with the round mark's (`KYOGIJO_MARK_STYLE`); `KYOGIJO_STYLE` has them for a page that draws with `style: "none"`.
|
|
6
|
-
*/
|
|
7
|
-
export const KYOGIJO_ROAD_STYLE = `
|
|
8
|
-
:where(.kyogijo-road){--ky-road-bg:#f6f4ee;--ky-road-ink:#1d2421;--ky-road-muted:#5a655f;--ky-road-panel:#e7e3d6;--ky-road-spine:#c4bfae;--ky-road-left:#2f6db0;--ky-road-right:#b5452c;--ky-road-plain:#8b948f;--ky-mark-ring:#fff;--ky-mark-edge:rgba(0,0,0,.3);--ky-mark-plain:#fff;--ky-font:system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif;display:block;width:100%;height:auto;font-family:var(--ky-font)}
|
|
9
|
-
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .kyogijo-road:not([data-theme=light])){--ky-road-bg:#131716;--ky-road-ink:#ece8dc;--ky-road-muted:#a9b2ac;--ky-road-panel:#252b28;--ky-road-spine:#3d4541;--ky-road-left:#6aa9f0;--ky-road-right:#f59a5b;--ky-mark-ring:#e9ece6;--ky-mark-edge:rgba(0,0,0,.6);--ky-mark-plain:#2a302d}}
|
|
10
|
-
:where(html[data-theme=dark] .kyogijo-road,.kyogijo-road[data-theme=dark]){--ky-road-bg:#131716;--ky-road-ink:#ece8dc;--ky-road-muted:#a9b2ac;--ky-road-panel:#252b28;--ky-road-spine:#3d4541;--ky-road-left:#6aa9f0;--ky-road-right:#f59a5b;--ky-mark-ring:#e9ece6;--ky-mark-edge:rgba(0,0,0,.6);--ky-mark-plain:#2a302d}
|
|
11
|
-
.kyogijo-road .ky-r-bg{fill:var(--ky-road-bg)}
|
|
12
|
-
.kyogijo-road text{font-family:var(--ky-font);font-variant-numeric:tabular-nums}
|
|
13
|
-
.kyogijo-road .ky-r-t{fill:var(--ky-road-ink);font-weight:800}
|
|
14
|
-
.kyogijo-road .ky-r-s,.kyogijo-road .ky-r-e{fill:var(--ky-road-muted);font-weight:600}
|
|
15
|
-
.kyogijo-road .ky-r-name{fill:var(--ky-road-ink);font-weight:800}
|
|
16
|
-
.kyogijo-road .ky-r-panel{fill:var(--ky-road-panel)}
|
|
17
|
-
.kyogijo-road .ky-r-lab{fill:var(--ky-road-ink);font-weight:700}
|
|
18
|
-
.kyogijo-road .ky-r-spine{stroke:var(--ky-road-spine);fill:none;stroke-linecap:round}
|
|
19
|
-
.kyogijo-road .ky-r-n{fill:var(--ky-road-ink);font-weight:800}
|
|
20
|
-
.kyogijo-road .ky-r-n.ky-r-low,.kyogijo-road .ky-r-dash{fill:var(--ky-road-muted);font-weight:700}
|
|
21
|
-
.kyogijo-road .ky-r-sub,.kyogijo-road .ky-r-code{fill:var(--ky-road-muted);font-weight:700}
|
|
22
|
-
.kyogijo-road .ky-r-vs{fill:var(--ky-road-muted);font-weight:700}
|
|
23
|
-
.kyogijo-road .ky-r-dim{opacity:.5}
|
|
24
|
-
.kyogijo-road .ky-r-left{--c:var(--ky-road-left)}
|
|
25
|
-
.kyogijo-road .ky-r-right{--c:var(--ky-road-right)}
|
|
26
|
-
.kyogijo-road .ky-r-plain{--c:var(--ky-road-plain)}`.trim();
|
package/dist/road.types.d.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import type { EmblemRenderer } from "./emblems-source.ts";
|
|
2
|
-
import type { Language, Team } from "./types.ts";
|
|
3
|
-
/** A match's score from the finalist's side: `[for, against]` (numbers, or text such as a series' `4`), or one text, `"4-2"`, that is split at its dash. */
|
|
4
|
-
export type RoadScore = readonly [number | string, number | string] | string;
|
|
5
|
-
/** How a match was decided: `ft` (full time, the default), `aet` (after extra time) or `pens` (a shoot-out after extra time). */
|
|
6
|
-
export type RoadDecided = "ft" | "aet" | "pens";
|
|
7
|
-
/** One match on a finalist's way. */
|
|
8
|
-
export interface RoadMatch {
|
|
9
|
-
/** The round, as a key. `round-of-32`, `round-of-16`, `quarter-final` and `semi-final` have words in English and Japanese; any other key is written as it is, or as `options.labels` names it. */
|
|
10
|
-
round: string;
|
|
11
|
-
/** The team it played: its name, abbreviation, `logo` (the flag or the crest) and `color`. */
|
|
12
|
-
opponent: Team;
|
|
13
|
-
/** The score, the finalist's first: `[2, 1]` is a 2-1 win. */
|
|
14
|
-
score: RoadScore;
|
|
15
|
-
/** How it was decided: `ft` by default, or `pens` when `pens` is given. Written under the score: FT, AET, or the shoot-out. */
|
|
16
|
-
decidedBy?: RoadDecided;
|
|
17
|
-
/** The shoot-out, the finalist's first: `[4, 3]`. */
|
|
18
|
-
pens?: readonly [number | string, number | string];
|
|
19
|
-
/** The order of the pair, left to right: `true` the finalist first, `false` its opponent first. By default the finalist is at the outer edge of its side, so that the two paths mirror each other. */
|
|
20
|
-
homeFirst?: boolean;
|
|
21
|
-
/** Words to write under the score in place of FT, AET or the shoot-out (`Game 6`, `OT`). An empty text writes nothing. */
|
|
22
|
-
note?: string;
|
|
23
|
-
}
|
|
24
|
-
/** One match of the group stage: as a knockout match, without a round. */
|
|
25
|
-
export type RoadGroupMatch = Omit<RoadMatch, "round"> & {
|
|
26
|
-
round?: string;
|
|
27
|
-
};
|
|
28
|
-
/** A finalist: a team and the matches it played on the way, the earliest first. */
|
|
29
|
-
export interface RoadFinalist extends Team {
|
|
30
|
-
/** The knockout matches before the final, the earliest first. */
|
|
31
|
-
path: readonly RoadMatch[];
|
|
32
|
-
/** The group stage, in the order played. A row of small marks, codes and scores at the foot. */
|
|
33
|
-
group?: readonly RoadGroupMatch[];
|
|
34
|
-
}
|
|
35
|
-
/** The final, when it has been played. */
|
|
36
|
-
export interface RoadFinal {
|
|
37
|
-
/** The score, the first finalist's first. */
|
|
38
|
-
score: RoadScore;
|
|
39
|
-
decidedBy?: RoadDecided;
|
|
40
|
-
/** The shoot-out, the first finalist's first. */
|
|
41
|
-
pens?: readonly [number | string, number | string];
|
|
42
|
-
note?: string;
|
|
43
|
-
}
|
|
44
|
-
/** The sizes of the chart: `card` 1080 by 1350 (a tall post), `square` 1080 by 1080, `wide` 1200 by 675 (the finalists at the two ends) and `phone` 390 wide and as tall as it needs. */
|
|
45
|
-
export type RoadSize = "card" | "square" | "wide" | "phone";
|
|
46
|
-
/** What `roadToFinal` takes besides the finalists. */
|
|
47
|
-
export interface RoadOptions {
|
|
48
|
-
/** The words of the chart: its title, the rounds' names, how a match was decided and the table. English by default. */
|
|
49
|
-
language?: Language;
|
|
50
|
-
/** 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. */
|
|
51
|
-
id?: string;
|
|
52
|
-
/** A renderer of ready-made emblems for this chart alone, so that a finalist's `logo` may be an emblem's id. Over the one the page plugged in with `useEmblems`. */
|
|
53
|
-
emblems?: EmblemRenderer;
|
|
54
|
-
/** The tournament, set large at the top. "Road to the final" when missing. */
|
|
55
|
-
title?: string;
|
|
56
|
-
/** The line under it. "Road to the final" when a `title` is given. */
|
|
57
|
-
subtitle?: string;
|
|
58
|
-
/** The final, if it has been played: its score is written between the two big marks, the winner is brighter, and the table has a row for it. */
|
|
59
|
-
final?: RoadFinal;
|
|
60
|
-
/** The names of the rounds, by key, over the package's own words. Any key a match uses may be named here: `{ "first-round": "First round" }`. */
|
|
61
|
-
labels?: Readonly<Record<string, string>>;
|
|
62
|
-
/** The group stage's name, over the package's own. */
|
|
63
|
-
groupLabel?: string;
|
|
64
|
-
/** `card` (the default), `square`, `wide` or `phone`. */
|
|
65
|
-
size?: RoadSize;
|
|
66
|
-
/** `light` or `dark` to force one; `auto` (the default) follows the device and the page. */
|
|
67
|
-
theme?: "auto" | "light" | "dark";
|
|
68
|
-
/** Who is brighter: `auto` (the default) the side that won each match, `none` nobody. */
|
|
69
|
-
winner?: "auto" | "none";
|
|
70
|
-
/** Write the opponent's three-letter code under its mark. On by default. */
|
|
71
|
-
codes?: boolean;
|
|
72
|
-
/** A line at the foot: a source, a credit, "Made-up results". */
|
|
73
|
-
endnote?: string;
|
|
74
|
-
/** A class for the root. */
|
|
75
|
-
className?: string;
|
|
76
|
-
/** `none` leaves the stylesheet to the page (`KYOGIJO_STYLE` has every rule). */
|
|
77
|
-
style?: "embed" | "none";
|
|
78
|
-
}
|
|
79
|
-
/** One finalist's record on the way. */
|
|
80
|
-
export interface RoadRecord {
|
|
81
|
-
name: string;
|
|
82
|
-
/** Matches played in the group and the knockout rounds (not the final), and how many it won, drew and lost. */
|
|
83
|
-
matches: number;
|
|
84
|
-
won: number;
|
|
85
|
-
drawn: number;
|
|
86
|
-
lost: number;
|
|
87
|
-
/** The goals it scored and let in over the matches whose score is a pair of numbers. */
|
|
88
|
-
scored: number;
|
|
89
|
-
conceded: number;
|
|
90
|
-
}
|
|
91
|
-
/** The numbers behind a road chart. */
|
|
92
|
-
export interface RoadTotals {
|
|
93
|
-
finalists: [RoadRecord, RoadRecord];
|
|
94
|
-
/** The rounds of the chart, the earliest first, as keys. */
|
|
95
|
-
rounds: string[];
|
|
96
|
-
/** The final has a score. */
|
|
97
|
-
played: boolean;
|
|
98
|
-
/** The winner of the final, or null when it has not been played or is level. */
|
|
99
|
-
champion: string | null;
|
|
100
|
-
}
|
|
101
|
-
/** The size in pixels a size draws at, and what it is for. */
|
|
102
|
-
export interface RoadSizeInfo {
|
|
103
|
-
readonly width: number;
|
|
104
|
-
/** `undefined` for a size that is as tall as the rounds need. */
|
|
105
|
-
readonly height: number | undefined;
|
|
106
|
-
readonly about: string;
|
|
107
|
-
}
|
|
108
|
-
/** What `roadToFinal` gives back. */
|
|
109
|
-
export interface RoadChart {
|
|
110
|
-
/** The options the chart was made with, with the finalists: pass changes to `draw`. */
|
|
111
|
-
options: RoadOptions;
|
|
112
|
-
/** The chart's accessible name, in the chart's language. */
|
|
113
|
-
title: string;
|
|
114
|
-
/** What the chart shows, in a sentence or two, for a caption. */
|
|
115
|
-
summary: string;
|
|
116
|
-
/** The numbers behind it. */
|
|
117
|
-
totals: RoadTotals;
|
|
118
|
-
/** The drawing as an SVG string. `extra` is laid over the options (`size`, `theme`, `language`, `labels`, `final`...). */
|
|
119
|
-
draw(extra?: Partial<RoadOptions>): string;
|
|
120
|
-
/** One HTML table with a row for every match: the table that stands for the drawing for a screen reader. */
|
|
121
|
-
table(options?: {
|
|
122
|
-
className?: string;
|
|
123
|
-
language?: Language;
|
|
124
|
-
}): string;
|
|
125
|
-
}
|
package/dist/road.types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|