@johnmorrisdotca/taisen-brackets 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/NOTICE.md +1 -0
- package/README.md +9 -3
- package/dist/layout.js +11 -1
- package/dist/locales.js +3 -0
- package/dist/matchup.js +3 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +3 -0
- package/dist/render.js +24 -14
- package/dist/types.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ All notable changes to this project are written here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **MLS**: `playoffFormat("mls", year)` for the format since 2023 (18 teams, nine a conference; a wild card match, a best-of-three round one, then single matches to the MLS Cup; seven teams of each conference skip the wild card match), checked against English Wikipedia, with the round names in all five languages. A demo example, the 2024 MLS Cup Playoffs, read from spxn.com's MLS data (openfootball, CC0, checked against English Wikipedia, CC BY-SA 4.0), with a test that pins its champion (LA Galaxy) and final (2–1) and a picture.
|
|
14
|
+
- `LayoutBox.byes`: every side that skipped the earlier rounds, which includes both sides of a later-round tie that no tie feeds (MLS's round one); each such side says "bye" beside its score.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- A series counts a game level at the end of play as won by the side that won the shoot-out (MLS's best-of-three round one), so its score and winner are right.
|
|
19
|
+
|
|
9
20
|
## [0.2.0] - 2026-10-14
|
|
10
21
|
|
|
11
22
|
### Added
|
|
@@ -41,7 +52,8 @@ First version.
|
|
|
41
52
|
- A demo with real results read from spxn.com (the NHL 2024 playoffs, the World Cups of 2022, 1938 and 2026, the MLB 2022 postseason; sources and terms in NOTICE.md), two made-up examples (two-legged ties, a 32-team field) and a flags-only poster of the 2026 World Cup, in a playground that changes the size, look and shape.
|
|
42
53
|
- Playoff formats checked against English Wikipedia, each era citing its articles (docs/formats.md). The year is the year the playoffs are played in.
|
|
43
54
|
|
|
44
|
-
[Unreleased]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.2.
|
|
55
|
+
[Unreleased]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.2.1...HEAD
|
|
56
|
+
[0.2.1]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.2.0...v0.2.1
|
|
45
57
|
[0.2.0]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.1.1...v0.2.0
|
|
46
58
|
[0.1.1]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.1.0...v0.1.1
|
|
47
59
|
[0.1.0]: https://github.com/johnmorrisdotca/taisen-brackets/releases/tag/v0.1.0
|
package/NOTICE.md
CHANGED
|
@@ -9,6 +9,7 @@ The code of this package is under the MIT licence (see [LICENSE](LICENSE)). The
|
|
|
9
9
|
| The demo's World Cup results of 2026 (`demo/data/worldcup-2026.js`) | openfootball/worldcup, https://github.com/openfootball/worldcup, read through spxn.com | CC0 |
|
|
10
10
|
| The demo's NHL results (`demo/data/nhl-2024.js`, `demo/data/nhl-1984.js`) | Every game of the 2024 and the 1984 Stanley Cup playoffs and each team's conference, from the NHL ("NHL", as spxn.com credits it), read through spxn.com | Facts; credited to the NHL |
|
|
11
11
|
| The demo's NBA results (`demo/data/nba-2024.js`) | Every game of the 2024 NBA playoffs and each team's conference, from English Wikipedia (https://en.wikipedia.org/wiki/2024_NBA_playoffs), read through spxn.com, which holds them as sourced from Wikipedia | **CC BY-SA 4.0** (https://creativecommons.org/licenses/by-sa/4.0/legalcode). That file is an adaptation and is shared under the same licence, with this credit; the pictures of the NBA bracket carry it too |
|
|
12
|
+
| The demo's MLS results (`demo/data/mls-2024.js`) | Every match of the 2024 MLS Cup Playoffs and each club's conference, read through spxn.com, whose MLS data is from openfootball/world (CC0, https://github.com/openfootball/world) checked against English Wikipedia (https://en.wikipedia.org/wiki/2024_MLS_Cup_Playoffs) | **CC BY-SA 4.0** (Wikipedia is in the mix, so the file is an adaptation shared under the same licence, with this credit); openfootball's part is CC0; the pictures of the MLS bracket carry the same licence |
|
|
12
13
|
| The demo's NFL results (`demo/data/nfl-2024.js`) | Every game of the playoffs of the 2024 NFL season (January and February 2025) and each team's conference, from nflverse (https://github.com/nflverse/nflverse-data), read through spxn.com | CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), credited to nflverse |
|
|
13
14
|
| The demo's CFL results (`demo/data/cfl-2024.js`) | Every game of the 2024 CFL playoffs and each team's division, from English Wikipedia (https://en.wikipedia.org/wiki/2024_CFL_season), read through spxn.com | **CC BY-SA 4.0**, as for the NBA file above; the pictures of the CFL bracket carry it too |
|
|
14
15
|
| The demo's MLB results (`demo/data/mlb-2022.js`) | Every game of the 2022 MLB postseason, read through spxn.com. The information used here was obtained free of charge from and is copyrighted by Retrosheet. Interested parties may contact Retrosheet at www.retrosheet.org | Retrosheet's notice, kept as it asks |
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">Taisen Brackets <sub>対戦</sub></h1>
|
|
2
2
|
|
|
3
3
|
<p align="center"><strong>Knockout brackets as SVG, from a list of ties.</strong><br>
|
|
4
|
-
Give it the games, series, two-legged ties or replays of a tournament and it works out the rounds, the halves, the byes and who feeds whom, then draws the bracket: two halves that meet at the final, or one tree left to right, wide, square or tall for a phone, in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and a table for readers the drawing does not serve. NHL, NBA, MLB, NFL, CFL and World Cup formats by era, in English, French, Japanese, Spanish and German. One function, no dependencies.</p>
|
|
4
|
+
Give it the games, series, two-legged ties or replays of a tournament and it works out the rounds, the halves, the byes and who feeds whom, then draws the bracket: two halves that meet at the final, or one tree left to right, wide, square or tall for a phone, in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and a table for readers the drawing does not serve. NHL, NBA, MLB, NFL, CFL, MLS and World Cup formats by era, in English, French, Japanese, Spanish and German. One function, no dependencies.</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
7
|
<a href="https://github.com/johnmorrisdotca/taisen-brackets/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/johnmorrisdotca/taisen-brackets/actions/workflows/ci.yml/badge.svg"></a>
|
|
@@ -51,6 +51,11 @@ One layout draws them all, each from real results: the demo has a picker for the
|
|
|
51
51
|
<td align="center" valign="top"><img alt="The 1938 World Cup: replays." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/replays-desk-light.png" width="360"><br><em>World Cup 1938, replays</em></td>
|
|
52
52
|
<td align="center" valign="top"><img alt="The 2026 World Cup as a flags-only poster, in Japanese." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/poster-desk-ja.png" width="360"><br><em>World Cup 2026 poster, in Japanese</em></td>
|
|
53
53
|
</tr>
|
|
54
|
+
<tr>
|
|
55
|
+
<td align="center" valign="top"><img alt="The 2024 MLS Cup Playoffs: a wild card match, a best-of-three round one, then single matches to the MLS Cup, LA Galaxy as champion." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/mls-desk-light.png" width="360"><br><em>MLS 2024, best of three</em></td>
|
|
56
|
+
<td></td>
|
|
57
|
+
<td></td>
|
|
58
|
+
</tr>
|
|
54
59
|
</table>
|
|
55
60
|
|
|
56
61
|
## In 30 seconds
|
|
@@ -90,7 +95,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
|
|
|
90
95
|
- **Team marks you supply**: a logo address or inline SVG, drawn round or square; a team with none is its initials on a colour. Round flags from [Hata](https://github.com/johnmorrisdotca/hata) go in as they are.
|
|
91
96
|
- **The champion in the centre**, with a slot for your trophy.
|
|
92
97
|
- **Accessible**: `role="img"` with a title and a description, a tooltip sentence on every tie, and `bracketTable`.
|
|
93
|
-
- **Formats by era**: NHL, NBA, MLB, NFL, CFL and FIFA World Cup round names, series lengths and byes.
|
|
98
|
+
- **Formats by era**: NHL, NBA, MLB, NFL, CFL, MLS and FIFA World Cup round names, series lengths and byes.
|
|
94
99
|
- **Five languages**: English, French, Japanese, Spanish and German, for every word the drawing, the tooltips, the description and the table write, with the football terms of each language; countries named by `Intl` from their ISO code; numbers and dates by `Intl`. Add a language of your own.
|
|
95
100
|
- **Deterministic**: no clock, no random number, no counter; the same input gives the same string, and ids come from a hash of the input.
|
|
96
101
|
- **No dependencies**, ESM with types, about 40 kB packed.
|
|
@@ -321,7 +326,7 @@ Everything is exported from `@johnmorrisdotca/taisen-brackets`; the layout part
|
|
|
321
326
|
- `singleElimination(teams, options?)`, `advanceWinners(matchups)`, `standardSeeds(size)`: brackets for any number of teams.
|
|
322
327
|
- `matchupsFromGames(games, options)`: matchups from a flat list of games.
|
|
323
328
|
- `resolveMatchup`, `collectTeams`, `describeTie`, `noteParts`, `noteLong`: one tie's result, the teams of a bracket, a tie in a sentence.
|
|
324
|
-
- `playoffFormat(league, year)`, `playoffEras(league)`, `LEAGUES`: the formats by era (`nhl`, `nba`, `mlb`, `nfl`, `cfl`, `worldcup`).
|
|
329
|
+
- `playoffFormat(league, year)`, `playoffEras(league)`, `LEAGUES`: the formats by era (`nhl`, `nba`, `mlb`, `nfl`, `cfl`, `mls`, `worldcup`).
|
|
325
330
|
- `roundName(remaining)`, `roundNamesFor(count)`, `roundLabel(layout, round, names, words)`: round names.
|
|
326
331
|
- `LOCALES`, `LOCALE_CODES`, `defineLocale`, `localeOf`, `voiceOf`, `countryName`: the languages (see Languages), a language of your own, the formats and the names of countries from `Intl`. `localizedLayout(layout, voice)` is a layout with the team names in a language.
|
|
327
332
|
- `SIZES`, `THEMES`, `WORDS`, `VERSION`: the sizes, the looks' colours, the English words, the version.
|
|
@@ -375,6 +380,7 @@ console.log(svg.includes('lang="nl"')); // true
|
|
|
375
380
|
| MLB | 1969 on (no postseason in 1994) | Wild Card Series, Division Series, League Championship Series, World Series |
|
|
376
381
|
| NFL | 1970 on, with 1982 as its own | Wild Card, Divisional, Conference Championship, Super Bowl |
|
|
377
382
|
| CFL | 1996 on | Division Semi-Final, Division Final, Grey Cup |
|
|
383
|
+
| MLS | 2023 on | Wild Card Round, Round One (best of three), Conference Semifinals, Conference Finals, MLS Cup |
|
|
378
384
|
| FIFA World Cup | every edition with a knockout, 1930 to 2026 (1950 had none) | Round of 32 (2026), Round of 16, Quarter-finals, Semi-finals, Final |
|
|
379
385
|
| Any other | single elimination for any number of teams | Round of N … Final |
|
|
380
386
|
|
package/dist/layout.js
CHANGED
|
@@ -140,6 +140,16 @@ export function layoutBracket(input, options = {}) {
|
|
|
140
140
|
const other = (1 - fed);
|
|
141
141
|
return ties[i].sides[other] ? other : null;
|
|
142
142
|
};
|
|
143
|
+
/** Every side that skipped the rounds before this tie's: the bye side, or both sides of a later-round tie that nothing feeds. */
|
|
144
|
+
const byesOf = (i) => {
|
|
145
|
+
const one = byeOf(i);
|
|
146
|
+
if (one !== null)
|
|
147
|
+
return [one];
|
|
148
|
+
const ch = children.get(i) ?? [];
|
|
149
|
+
if (round[i] <= 1 || ch.length > 0)
|
|
150
|
+
return [];
|
|
151
|
+
return [0, 1].filter((k) => ties[i].sides[k] !== null);
|
|
152
|
+
};
|
|
143
153
|
/** Lays one tree out: leaves take the next rows, a tie sits at the middle of the ones that feed it. */
|
|
144
154
|
const lay = (root, mark) => {
|
|
145
155
|
const boxes = [];
|
|
@@ -148,7 +158,7 @@ export function layoutBracket(input, options = {}) {
|
|
|
148
158
|
const ch = children.get(i) ?? [];
|
|
149
159
|
const ys = ch.map(go);
|
|
150
160
|
const y = ys.length ? ys.reduce((a, b) => a + b, 0) / ys.length : next++;
|
|
151
|
-
boxes.push({ i, round: round[i], y, to: null, fed: ch.length > 0, order: orderOf(i), bye: byeOf(i) });
|
|
161
|
+
boxes.push({ i, round: round[i], y, to: null, fed: ch.length > 0, order: orderOf(i), bye: byeOf(i), byes: byesOf(i) });
|
|
152
162
|
place[i] = mark;
|
|
153
163
|
return y;
|
|
154
164
|
};
|
package/dist/locales.js
CHANGED
|
@@ -79,6 +79,9 @@ const ROUNDS = {
|
|
|
79
79
|
"Division Series": ["Série de division", "ディビジョンシリーズ", "Serie divisional", "Division Series"],
|
|
80
80
|
"League Championship Series": ["Série de championnat", "リーグチャンピオンシップシリーズ", "Serie de Campeonato de Liga", "League Championship Series"],
|
|
81
81
|
"World Series": ["Série mondiale", "ワールドシリーズ", "Serie Mundial", "World Series"],
|
|
82
|
+
"Wild Card Round": ["Tour de wild card", "ワイルドカードラウンド", "Ronda de comodín", "Wild-Card-Runde"],
|
|
83
|
+
"Round One": ["Premier tour", "1回戦", "Primera ronda", "Erste Runde"],
|
|
84
|
+
"MLS Cup": ["Coupe MLS", "MLSカップ", "Copa MLS", "MLS Cup"],
|
|
82
85
|
"Wild Card": ["Wild card", "ワイルドカード・プレーオフ", "Comodín", "Wild-Card-Runde"],
|
|
83
86
|
"Divisional": ["Ronde divisionnaire", "ディビジョナル・プレーオフ", "Ronda divisional", "Divisional Round"],
|
|
84
87
|
"Divisional Playoffs": ["Ronde divisionnaire", "ディビジョナル・プレーオフ", "Ronda divisional", "Divisional Round"],
|
package/dist/matchup.js
CHANGED
|
@@ -34,8 +34,10 @@ function settle(m, kind) {
|
|
|
34
34
|
const byPens = (pens) => (pens ? byScore(pens[0], pens[1]) : null);
|
|
35
35
|
if (kind === "series") {
|
|
36
36
|
let wins = null;
|
|
37
|
+
// A game level at the end of play but settled by a shoot-out is won by the side that won the shoot-out (MLS's best-of-three round).
|
|
38
|
+
const won = (g) => byScore(g.score[0], g.score[1]) ?? byPens(g.pens ?? null);
|
|
37
39
|
if (games.length)
|
|
38
|
-
wins = [games.filter((g) => g
|
|
40
|
+
wins = [games.filter((g) => won(g) === 0).length, games.filter((g) => won(g) === 1).length];
|
|
39
41
|
else if (m.score)
|
|
40
42
|
wins = m.score;
|
|
41
43
|
const need = bestOf ? Math.ceil(bestOf / 2) : null;
|
package/dist/presets.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Wikipedia articles in its `sources` (2026-10-13); where a season did something of its own (a strike, a pandemic) it has
|
|
5
5
|
* its own entry. The year is the year the playoffs are played in (the NHL's and the NBA's 2024 are the 2024 playoffs, of the 2023-24 season), except for the NFL, whose year is its season's: 2024 is the season played in 2024, with playoffs in January and February 2025.
|
|
6
6
|
*/
|
|
7
|
-
export type League = "nhl" | "nba" | "mlb" | "nfl" | "cfl" | "worldcup";
|
|
7
|
+
export type League = "nhl" | "nba" | "mlb" | "nfl" | "cfl" | "mls" | "worldcup";
|
|
8
8
|
export interface PlayoffRound {
|
|
9
9
|
/** The round's name as the league calls it. */
|
|
10
10
|
name: string;
|
package/dist/presets.js
CHANGED
|
@@ -47,6 +47,9 @@ const ERAS = {
|
|
|
47
47
|
cfl: [
|
|
48
48
|
{ league: "cfl", from: 1996, to: 2026, label: "division semi-finals and finals, with a crossover", teams: 6, byes: 1, groups: ["West", "East"], rounds: [r("Division Semi-Final", 1), r("Division Final", 1), r("Grey Cup", 1)], note: "Three teams of each division qualify and the first-placed team has a bye. A fourth-placed team with a better record than the third-placed team of the other division takes that place (the crossover, since 1996). The league has announced an eight-team format from 2027.", sources: wiki("1996 CFL season", "2012 CFL season", "2019 CFL season", "CFL playoffs") },
|
|
49
49
|
],
|
|
50
|
+
mls: [
|
|
51
|
+
{ league: "mls", from: 2023, to: null, label: "18 teams, nine a conference: a wild card match, then best of three", teams: 18, byes: 7, groups: WEST_EAST, rounds: [r("Wild Card Round", 1), r("Round One", 3), r("Conference Semifinals", 1), r("Conference Finals", 1), r("MLS Cup", 1)], note: "The teams ranked 8th and 9th of each conference play one wild card match (settled by a shoot-out if level); the other seven teams skip it. Round One is a best-of-three series (a game level after play is settled by a shoot-out, and counts as a win); the rest are single matches. Earlier seasons had other formats and are not covered.", sources: wiki("MLS Cup Playoffs", "2024 MLS Cup Playoffs") },
|
|
52
|
+
],
|
|
50
53
|
worldcup: [
|
|
51
54
|
{ league: "worldcup", from: 1930, to: 1930, label: "four group winners, straight to the semi-finals", teams: 4, byes: 0, groups: null, rounds: [r("Semi-finals", 1), r("Final", 1)], years: [1930], sources: wiki("1930 FIFA World Cup") },
|
|
52
55
|
{ league: "worldcup", from: 1934, to: 1938, label: "16 teams, straight knockout, drawn matches replayed", teams: 16, byes: 0, groups: null, rounds: [r("Round of 16", 1), r("Quarter-finals", 1), r("Semi-finals", 1), r("Final", 1)], years: [1934, 1938], note: "A match still level after extra time was replayed (1934, 1938). There was a third-place match in both. In 1938 Austria withdrew, so 15 teams played.", sources: wiki("1934 FIFA World Cup", "FIFA World Cup") },
|
package/dist/render.js
CHANGED
|
@@ -137,7 +137,11 @@ function planBracket(layout, o, v, size, vertical, ctx) {
|
|
|
137
137
|
const rowH = h / 2;
|
|
138
138
|
const font = clamp(rowH * 0.5, 8.5, 16);
|
|
139
139
|
const md = clamp(rowH * 0.7, 10, 28);
|
|
140
|
-
|
|
140
|
+
// Room for the score, and for the words some boxes carry beside it: a bye, a shoot-out's score.
|
|
141
|
+
const anyBye = layout.sides.some((sd) => sd.boxes.some((b) => b.byes.length > 0));
|
|
142
|
+
const anyPens = layout.ties.some((t) => t.result.pens !== null);
|
|
143
|
+
const extra = (anyBye ? textWidth(v.words.bye, Math.max(font - 2, 8)) + 8 : 0) + (anyPens ? textWidth("(00)", Math.max(font - 2, 8)) + 3 : 0);
|
|
144
|
+
const needed = 6 + md + 5 + longest * (font / 13) + 8 + textWidth("00", font + 1) + extra + 14;
|
|
141
145
|
const named = o.detail && o.detail !== "auto" ? o.detail : w >= needed && h >= 36 ? "full" : w >= 76 && h >= 28 ? "compact" : "mark";
|
|
142
146
|
return named === "mark" && o.markShape === "none" ? "compact" : named;
|
|
143
147
|
};
|
|
@@ -188,14 +192,14 @@ function planBracket(layout, o, v, size, vertical, ctx) {
|
|
|
188
192
|
for (const b of sd.boxes) {
|
|
189
193
|
const rect = place(sd.key, b);
|
|
190
194
|
rects.set(b.i, rect);
|
|
191
|
-
boxes.push({ rect, tie: layout.ties[b.i], order: b.order, bye: b.bye, final: false, champion: false });
|
|
195
|
+
boxes.push({ rect, tie: layout.ties[b.i], order: b.order, bye: b.bye, byes: b.byes, final: false, champion: false });
|
|
192
196
|
}
|
|
193
197
|
const midX = (halves ? starts[nc] : starts[nc] ?? 0) + (halves ? centreT : 0) / 2;
|
|
194
198
|
if (halves && layout.final) {
|
|
195
199
|
const fy = treeY0 + layout.final.y * rowStep + rowStep / 2;
|
|
196
200
|
const rect = { x: midX - finalW / 2, y: fy - finalH / 2, w: finalW, h: finalH };
|
|
197
201
|
rects.set(layout.final.i, rect);
|
|
198
|
-
boxes.push({ rect, tie: layout.ties[layout.final.i], order: layout.final.order, bye: null, final: true, champion: champ });
|
|
202
|
+
boxes.push({ rect, tie: layout.ties[layout.final.i], order: layout.final.order, bye: null, byes: [], final: true, champion: champ });
|
|
199
203
|
if (showLabels)
|
|
200
204
|
texts.push(label(v, roundLabel(layout, layout.rounds, o.roundNames, v), midX, rect.y - 7, "middle"));
|
|
201
205
|
if (champ)
|
|
@@ -205,7 +209,7 @@ function planBracket(layout, o, v, size, vertical, ctx) {
|
|
|
205
209
|
const r3 = { x: midX - finalW / 2, y: rect.y + rect.h + 14 + 18, w: finalW, h: boxH };
|
|
206
210
|
rects.set(layout.third, r3);
|
|
207
211
|
texts.push(label(v, v.words.thirdPlace, midX, r3.y - 6, "middle"));
|
|
208
|
-
boxes.push({ rect: r3, tie: t3, order: [0, 1], bye: null, final: false, champion: false });
|
|
212
|
+
boxes.push({ rect: r3, tie: t3, order: [0, 1], bye: null, byes: [], final: false, champion: false });
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
else if (!halves) {
|
|
@@ -287,7 +291,7 @@ function planBracket(layout, o, v, size, vertical, ctx) {
|
|
|
287
291
|
const c = layout.columns.indexOf(b.round);
|
|
288
292
|
const rect = { x: xOf(b.y), y: bandStart(sd.key, c), w: S, h: D };
|
|
289
293
|
rects.set(b.i, rect);
|
|
290
|
-
boxes.push({ rect, tie: layout.ties[b.i], order: b.order, bye: b.bye, final: false, champion: false });
|
|
294
|
+
boxes.push({ rect, tie: layout.ties[b.i], order: b.order, bye: b.bye, byes: b.byes, final: false, champion: false });
|
|
291
295
|
}
|
|
292
296
|
const midX = W0 / 2;
|
|
293
297
|
if (halves && layout.final) {
|
|
@@ -301,7 +305,7 @@ function planBracket(layout, o, v, size, vertical, ctx) {
|
|
|
301
305
|
}
|
|
302
306
|
const rect = { x: midX - finalW / 2, y: y + 6, w: finalW, h: finalH };
|
|
303
307
|
rects.set(layout.final.i, rect);
|
|
304
|
-
boxes.push({ rect, tie: layout.ties[layout.final.i], order: layout.final.order, bye: null, final: true, champion: champ });
|
|
308
|
+
boxes.push({ rect, tie: layout.ties[layout.final.i], order: layout.final.order, bye: null, byes: [], final: true, champion: champ });
|
|
305
309
|
}
|
|
306
310
|
else if (!halves && champ && layout.finalIndex !== null) {
|
|
307
311
|
const fr = rects.get(layout.finalIndex);
|
|
@@ -402,13 +406,13 @@ function othersBlock(layout, list, y0, width, pad, ow, oh, v, boxes, texts) {
|
|
|
402
406
|
texts.push(label(v, v.words.other, width / 2, y0 + 12, "middle"));
|
|
403
407
|
list.forEach((i, n) => {
|
|
404
408
|
const rect = { x: x0 + (n % cols) * (ow + 10), y: y0 + 24 + Math.floor(n / cols) * (oh + 10), w: ow, h: oh };
|
|
405
|
-
boxes.push({ rect, tie: layout.ties[i], order: [0, 1], bye: null, final: false, champion: false });
|
|
409
|
+
boxes.push({ rect, tie: layout.ties[i], order: [0, 1], bye: null, byes: [], final: false, champion: false });
|
|
406
410
|
});
|
|
407
411
|
}
|
|
408
412
|
/** The third-place match with its label, centred on `cx`, its label at `y0`. */
|
|
409
413
|
function thirdAtFoot(layout, y0, cx, w, h, v, boxes, texts) {
|
|
410
414
|
texts.push(label(v, v.words.thirdPlace, cx, y0 + 12, "middle"));
|
|
411
|
-
boxes.push({ rect: { x: cx - w / 2, y: y0 + 18, w, h }, tie: layout.ties[layout.third], order: [0, 1], bye: null, final: false, champion: false });
|
|
415
|
+
boxes.push({ rect: { x: cx - w / 2, y: y0 + 18, w, h }, tie: layout.ties[layout.third], order: [0, 1], bye: null, byes: [], final: false, champion: false });
|
|
412
416
|
}
|
|
413
417
|
// ---------------------------------------------------------------------------------------------- one box
|
|
414
418
|
function drawBox(p, plan, o, v, ctx) {
|
|
@@ -453,17 +457,23 @@ function drawBox(p, plan, o, v, ctx) {
|
|
|
453
457
|
}
|
|
454
458
|
const score = scoresOn && r.score ? v.number(r.score[k]) : "";
|
|
455
459
|
const pensText = scoresOn && r.pens ? `(${v.number(r.pens[k])})` : "";
|
|
456
|
-
const bye = p.
|
|
460
|
+
const bye = p.byes.includes(k);
|
|
457
461
|
const right = x + w - 6;
|
|
458
462
|
const scoreW = score ? textWidth(score, font + 1) : 0;
|
|
459
463
|
const pensW = pensText ? textWidth(pensText, font - 2) + 3 : 0;
|
|
460
|
-
const byeText = bye ? v.words.bye : "";
|
|
461
|
-
const byeW = byeText ? textWidth(byeText, Math.max(font - 2, 8)) : 0;
|
|
462
|
-
const avail = right - tx - (scoreW + pensW) - (byeW ? byeW + 6 : 0) - 6;
|
|
463
464
|
const seed = seedsOn && team?.seed !== undefined ? `${v.number(team.seed)} ` : "";
|
|
464
465
|
const nm = team ? (detail === "full" ? team.name : abbrOf(team, v)) : v.words.tbd;
|
|
465
|
-
|
|
466
|
-
|
|
466
|
+
// The word for a bye goes beside the score only where the name still fits beside it.
|
|
467
|
+
const byeFits = bye && right - tx - (scoreW + pensW) - (textWidth(v.words.bye, Math.max(font - 2, 8)) + 6) - 6 >= textWidth(`${seed}${nm}`, font);
|
|
468
|
+
const byeText = byeFits ? v.words.bye : "";
|
|
469
|
+
const byeW = byeText ? textWidth(byeText, Math.max(font - 2, 8)) : 0;
|
|
470
|
+
const avail = right - tx - (scoreW + pensW) - (byeW ? byeW + 6 : 0) - 6;
|
|
471
|
+
// A name that does not fit is set a little smaller before it is cut.
|
|
472
|
+
let nameFont = font;
|
|
473
|
+
while (nameFont > 9.5 && textWidth(`${seed}${nm}`, nameFont) > avail)
|
|
474
|
+
nameFont -= 0.5;
|
|
475
|
+
const text = fit(`${seed}${nm}`, nameFont, Math.max(avail, 8));
|
|
476
|
+
rowParts.push(`<text class="tb-name${team ? "" : " tb-muted"}" x="${num(tx)}" y="${num(cy + nameFont * 0.35)}" font-size="${num(nameFont)}">${esc(text)}</text>`);
|
|
467
477
|
if (score)
|
|
468
478
|
rowParts.push(`<text class="tb-score" x="${num(right)}" y="${num(cy + (font + 1) * 0.35)}" font-size="${num(font + 1)}" text-anchor="end" font-variant-numeric="tabular-nums">${esc(score)}</text>`);
|
|
469
479
|
if (pensText)
|
package/dist/types.d.ts
CHANGED
|
@@ -147,6 +147,8 @@ export interface LayoutBox {
|
|
|
147
147
|
order: readonly [0 | 1, 0 | 1];
|
|
148
148
|
/** The side that had a bye (it came from no tie, though the other did), or `null`. */
|
|
149
149
|
bye: 0 | 1 | null;
|
|
150
|
+
/** Every side that skipped the earlier rounds: the `bye` side, and both sides of a tie of a later round that no tie feeds (MLS's round one, where seven teams of nine skip the wild card round). */
|
|
151
|
+
byes: readonly (0 | 1)[];
|
|
150
152
|
}
|
|
151
153
|
export interface LayoutSide {
|
|
152
154
|
/** `l` the left half (the top half on a phone), `r` the right. */
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.2.
|
|
1
|
+
export declare const VERSION = "0.2.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.2.
|
|
1
|
+
export const VERSION = "0.2.1";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/taisen-brackets",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Taisen Brackets 対戦, knockout brackets as SVG: from a list of ties (games, best-of series, two legs, replays, byes, a third-place match) it works out the rounds and halves and draws the bracket with the halves meeting at the final, or left to right, wide, square or for a phone, in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and an accessible table. NHL, NBA, MLB, NFL, CFL and World Cup formats by era, in English, French, Japanese, Spanish and German. Zero dependencies.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Taisen Brackets 対戦, knockout brackets as SVG: from a list of ties (games, best-of series, two legs, replays, byes, a third-place match) it works out the rounds and halves and draws the bracket with the halves meeting at the final, or left to right, wide, square or for a phone, in light, dark or a gold-on-navy poster look, with each team's logo or round flag, the champion in the centre, and an accessible table. NHL, NBA, MLB, NFL, CFL, MLS and World Cup formats by era, in English, French, Japanese, Spanish and German. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bracket",
|
|
7
7
|
"tournament",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"mlb",
|
|
18
18
|
"nfl",
|
|
19
19
|
"cfl",
|
|
20
|
+
"mls",
|
|
20
21
|
"soccer",
|
|
21
22
|
"sports",
|
|
22
23
|
"multilingual",
|