@johnmorrisdotca/taisen-brackets 0.16.0 → 0.17.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 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.17.0] - 2026-10-20
10
+
11
+ Broadcast boards, step 3: the medal table.
12
+
13
+ ### Added
14
+
15
+ - **`broadcastMedalTable({ title?, subtitle?, date?, source?, rows }, options)`**: the medal table in the manner of a television graphic, as one standalone SVG: a title band (the heading, with the date at its right), a sub bar with the three medal discs over their columns and the word total, and a row for each country or team with its rank, a 4:3 flag, its code or name (`labels`) and the gold, silver and bronze counts each beside its disc, and the total. The same source as `medalTable`, ranked and tied by the same rules (`sort`, `ties`, `top`, `skipEmpty`), so `tallyMedals` feeds it; `ranks: false`. Looks, icons, languages and `--bc-*` custom properties as for the other boards; up to 24 rows.
16
+ - **`broadcastMedalSize(rowCount, { width })`**: the size before it is drawn: the width (1280 by default, 720 to 4000) and a height of 64 + 36 + 10 + 48 a row + 10, scaled.
17
+ - The three boards now have a section on placing them over something: the size is read before drawing, nothing is drawn outside the panel, and the SVG scales as a picture. [docs/broadcast.md](docs/broadcast.md).
18
+ - The demo's Broadcast boards section has the medal table, with the look, icon and width in the address.
19
+
9
20
  ## [0.16.0] - 2026-10-20
10
21
 
11
22
  Broadcast boards, step 2: the bout presentation board.
package/README.md CHANGED
@@ -266,7 +266,7 @@ A judo contest is a tie with a `judo` result: ippon, waza-ari, yuko and shido as
266
266
 
267
267
  ## Broadcast boards
268
268
 
269
- `resultsBoard` draws a results board in the manner of a television graphic: a title band with an icon, a sub bar, and a row for each athlete with a rank in a box, the country code, a 4:3 flag, the name, a record badge (`OR`, `WR`, `NR`) and one or more columns of scores or times at the sport's own precision (a gymnastics final, an all-around, a swimming final, athletics, speed skating). It is one standalone SVG of a width you choose and a height that follows from the rows (`resultsBoardSize`), made to be placed over a picture, in three looks and with no mark of any games or broadcaster. `boutBoard` draws the bout presentation: the two athletes in a red and a blue band, the referee and the judges. [docs/broadcast.md](./docs/broadcast.md) · [try it in the demo](https://johnmorrisdotca.github.io/taisen-brackets/#boards)
269
+ `resultsBoard` draws a results board in the manner of a television graphic: a title band with an icon, a sub bar, and a row for each athlete with a rank in a box, the country code, a 4:3 flag, the name, a record badge (`OR`, `WR`, `NR`) and one or more columns of scores or times at the sport's own precision (a gymnastics final, an all-around, a swimming final, athletics, speed skating). It is one standalone SVG of a width you choose and a height that follows from the rows (`resultsBoardSize`), made to be placed over a picture, in three looks and with no mark of any games or broadcaster. `boutBoard` draws the bout presentation: the two athletes in a red and a blue band, the referee and the judges. `broadcastMedalTable` draws the medal table with a disc for each medal. [docs/broadcast.md](./docs/broadcast.md) · [try it in the demo](https://johnmorrisdotca.github.io/taisen-brackets/#boards)
270
270
 
271
271
  <p align="center"><picture>
272
272
  <img alt="A results board in the blue look: a gymnastics apparatus final with a title band, a sub bar with three score columns, and eight rows with a red rank box, country code, flag, name and scores." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/results-apparatus-blue.webp" width="640">
@@ -322,7 +322,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
322
322
  - **Judo screens for the hall**: `judoContestCard` (the category, the round, the white athlete over the blue one) and `judoMatQueue` (per mat, a big number and colour and the contests to come in order, with flags and codes), on a TV or a tablet, in five languages.
323
323
  - **Rankings**: `rankingList` draws a ranking from your data, text first: rank, flag (a country or a Canadian province), name, club, points, movement arrows, a podium, a champion row, ties and paging; `rankingNumberOnes` the number one of each category and `rankingEntryList` an event's entry list with each athlete's ranking and `rankingMatchup` a head-to-head card ([docs/rankings.md](./docs/rankings.md)).
324
324
  - **A medal table for any sport**: `medalTable` ranks countries or teams gold first or by the total, with shared ranks, flags or badges, filters by sport, weight class and Games through `tallyMedals`, and former countries as rows of their own ([docs/medals.md](./docs/medals.md)).
325
- - **Broadcast boards**: `resultsBoard` draws a results graphic (title band, rank boxes, 4:3 flags, record badges, columns of scores) and `boutBoard` the bout presentation (the two athletes, the referee and the judges) as a standalone SVG that follows from its rows, to place over a picture, in three looks and with no mark of any games ([docs/broadcast.md](./docs/broadcast.md)).
325
+ - **Broadcast boards**: `resultsBoard` draws a results graphic (title band, rank boxes, 4:3 flags, record badges, columns of scores) and `boutBoard` the bout presentation (the two athletes, the referee and the judges) and `broadcastMedalTable` the medal table as a standalone SVG that follows from its rows, to place over a picture, in three looks and with no mark of any games ([docs/broadcast.md](./docs/broadcast.md)).
326
326
  - **Judo scoring**: `Matchup.judo` takes ippon, waza-ari, yuko, shido, hansoku-make, golden score and the contest time, checked against the IJF rules (`judoOutcome`); a box shows the marks, the shido cards and how the contest was won, and `judoBracket` adds the repechage and the two bronze medal contests ([docs/judo.md](./docs/judo.md)).
327
327
  - **Formats by era**: NHL, NBA, MLB, NFL, CFL, MLS and FIFA World Cup round names, series lengths and byes.
328
328
  - **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.
@@ -686,7 +686,7 @@ Everything is exported from `@johnmorrisdotca/taisen-brackets`; the layout part
686
686
  - `judoOutcome(result, winner?)`, `judoBracket(athletes, options?)`, `judoPodium(input)`, `judoCategoryLabel(category, locale?)`, `JUDO_WEIGHT_CLASSES`, `judoSeconds(time)`, `judoClock(seconds)`: judo results checked against the IJF rules, the bracket with its repechage and two bronze medal contests, the medallists, a weight category as a heading ([docs/judo.md](./docs/judo.md)). `judoScoreboard(contest, options?)`, `describeJudoContest(contest, options?)`, `JUDO_BOARD_SIZES`: the scoreboard of a contest as SVG and as a sentence. `judoContestCard(contest, options?)`, `judoMatQueue(mats, options?)`, `describeJudoCard`, `describeJudoQueue`, `JUDO_MAT_COLORS`: the contest card and the mat queue board as SVG and as sentences. `judoWins(bracket, athlete, options?)`, `judoRoadPanel(source, options?)`, `describeJudoRoad`: an athlete's wins on the road to the final, and the panel. `Matchup.judo` and `Matchup.stage` (`"repechage"`, `"bronze"`) carry them into `drawBracket`.
687
687
  - `rankingList(source, options?)`, `rankingRanks(entries, options?)`, `rankingCategoryLabel(category, locale?)`, `describeRanking(source, options?)`, `BOARD_SIZES`: a ranking list as SVG, its ranks as data, a category as a heading and the drawing as a sentence. `rankingNumberOnes(source, options?)`, `describeNumberOnes`, `NUMBER_ONE_COLORS`: the number one of each category as cards. `rankingEntryList(source, options?)`, `describeEntryList`: an event's entry list with each athlete's ranking. `rankingMatchup(source, options?)`, `describeMatchup`, `matchupRecord(meetings)`: the head-to-head card of two athletes and their meetings ([docs/rankings.md](./docs/rankings.md)).
688
688
  - `medalTable(source, options?)`, `medalRanks(rows, options?)`, `tallyMedals(awards, filter?)`, `describeMedalTable(source, options?)`: a medal table as SVG, its ranks as data, a list of medals added up and filtered, and the table as a sentence ([docs/medals.md](./docs/medals.md)).
689
- - `resultsBoard(source, options?)`, `resultsBoardSize(rowCount, options?)`, `describeResultsBoard(source, options?)`, `BROADCAST_LOOKS`: a broadcast-style results board as a standalone SVG, its size from its rows, and the board as a sentence. `boutBoard(source, options?)`, `boutBoardSize(officialCount, options?)`, `describeBoutBoard(source, options?)`: the bout presentation board, its size and its description ([docs/broadcast.md](./docs/broadcast.md)).
689
+ - `resultsBoard(source, options?)`, `resultsBoardSize(rowCount, options?)`, `describeResultsBoard(source, options?)`, `BROADCAST_LOOKS`: a broadcast-style results board as a standalone SVG, its size from its rows, and the board as a sentence. `boutBoard(source, options?)`, `boutBoardSize(officialCount, options?)`, `describeBoutBoard(source, options?)`: the bout presentation board, its size and its description. `broadcastMedalTable(source, options?)`, `broadcastMedalSize(rowCount, options?)`: the medal table in the broadcast style and its size ([docs/broadcast.md](./docs/broadcast.md)).
690
690
  - `playoffFormat(league, year)`, `playoffEras(league)`, `LEAGUES`: the formats by era (`nhl`, `nba`, `mlb`, `nfl`, `cfl`, `mls`, `worldcup`). `LEAGUE_EMBLEMS`: each league's default emblem id.
691
691
  - `isEmblemId(value)`, and the types `EmblemRenderer`, `EmblemRequest`, `EmblemStyle`: the trophy emblems (see "Emblems for the trophy").
692
692
  - `roundName(remaining)`, `roundNamesFor(count)`, `roundLabel(layout, round, names, words)`: round names.
@@ -79,6 +79,7 @@ export function broadcastStyle(scope, look, fixed = "") {
79
79
  s(".bc-ink", `fill:${v("ink", p.ink)}`), s(".bc-sub", `fill:${v("sub", p.sub)}`),
80
80
  s(".bc-rank", `fill:${v("rank", p.rank)};stroke:${v("rank-edge", p.rankEdge)};stroke-width:1.5`), s(".bc-rankink", `fill:${v("rank-ink", p.rankInk)}`),
81
81
  s(".bc-g1", `fill:${v("gold", p.gold)};stroke:none`), s(".bc-g2", `fill:${v("silver", p.silver)};stroke:none`), s(".bc-g3", `fill:${v("bronze", p.bronze)};stroke:none`), s(".bc-medalink", `fill:${v("medal-ink", p.medalInk)}`),
82
+ s(".bc-discrim", "fill:none;stroke:rgba(255,255,255,.55);stroke-width:1.5"),
82
83
  s(".bc-badge", `fill:${v("badge", p.badge)}`), s(".bc-badgeink", `fill:${v("badge-ink", p.badgeInk)}`),
83
84
  s(".bc-red", `fill:${v("red", p.red)}`), s(".bc-blue", `fill:${v("blue", p.blue)}`),
84
85
  s(".bc-band", `fill:${v("band", p.band)}`), s(".bc-bandink", `fill:${v("band-ink", p.bandInk)}`),
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The medal table in the manner of a television graphic: a title band (the heading, with the date at its right), a sub bar with the three medal discs and the total over their columns, and a row
3
+ * for each country or team with its rank, a 4:3 flag, the country code (or the name) and the gold, silver and bronze counts, each beside its disc, and the total. The rows are ranked and tied as
4
+ * `medalTable` ranks them. One standalone SVG of a width you choose and a height that follows from the rows (`broadcastMedalSize`), made to be placed over a picture.
5
+ */
6
+ import { type BroadcastOptions } from "./broadcast-kit.ts";
7
+ import { type MedalTableOptions, type MedalTableSource } from "./medals.ts";
8
+ export interface BroadcastMedalOptions extends BroadcastOptions, Pick<MedalTableOptions, "sort" | "ties" | "top" | "skipEmpty"> {
9
+ /** Write each row's `code` (the default where every row has one) or its `name`. */
10
+ labels?: "code" | "name";
11
+ /** Write the rank before each row. Default true. */
12
+ ranks?: boolean;
13
+ }
14
+ /** The width and the height of a broadcast medal table of `rowCount` rows: the width given (1280 by default) and a height of 64 + 36 + 10 + 48 a row + 10, in units of the width over 1280. */
15
+ export declare function broadcastMedalSize(rowCount: number, options?: Pick<BroadcastOptions, "width">): {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ /**
20
+ * The broadcast-style medal table as a standalone SVG. Throws a `RangeError` for what `medalTable` refuses, more than 24 rows after `top` and `skipEmpty`, a `labels` that is not `code` or `name`,
21
+ * a look or icon that is not one of its values, or a width outside 720 to 4000.
22
+ */
23
+ export declare function broadcastMedalTable(source: MedalTableSource, options?: BroadcastMedalOptions): string;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The medal table in the manner of a television graphic: a title band (the heading, with the date at its right), a sub bar with the three medal discs and the total over their columns, and a row
3
+ * for each country or team with its rank, a 4:3 flag, the country code (or the name) and the gold, silver and bronze counts, each beside its disc, and the total. The rows are ranked and tied as
4
+ * `medalTable` ranks them. One standalone SVG of a width you choose and a height that follows from the rows (`broadcastMedalSize`), made to be placed over a picture.
5
+ */
6
+ import { BC, box, broadcastFrame, broadcastWrap, bw, fitSize, gloss, panel, t, titleBand } from "./broadcast-kit.js";
7
+ import { chosenMedalRows, describeMedalTable, validateMedalSource } from "./medals.js";
8
+ import { drawMark } from "./marks.js";
9
+ import { abbreviate, esc, fit, num } from "./text.js";
10
+ const MAX_ROWS = 24;
11
+ /** The height of a row of this table, in units of the width over 1280. */
12
+ const ROW = 46;
13
+ /** The width and the height of a broadcast medal table of `rowCount` rows: the width given (1280 by default) and a height of 64 + 36 + 10 + 48 a row + 10, in units of the width over 1280. */
14
+ export function broadcastMedalSize(rowCount, options = {}) {
15
+ const width = options.width ?? 1280;
16
+ const u = width / 1280;
17
+ return { width, height: Math.round((BC.title + BC.sub + BC.pad + rowCount * (ROW + BC.gap) + BC.pad) * u) };
18
+ }
19
+ const count = (n) => n ?? 0;
20
+ /** A medal disc: a filled circle in the medal's colour with a lighter rim and a highlight. */
21
+ function disc(cx, cy, r, kind) {
22
+ return `<circle class="bc-g${kind}" cx="${num(cx)}" cy="${num(cy)}" r="${num(r)}"/><circle class="bc-discrim" cx="${num(cx)}" cy="${num(cy)}" r="${num(r * 0.72)}"/>`;
23
+ }
24
+ /**
25
+ * The broadcast-style medal table as a standalone SVG. Throws a `RangeError` for what `medalTable` refuses, more than 24 rows after `top` and `skipEmpty`, a `labels` that is not `code` or `name`,
26
+ * a look or icon that is not one of its values, or a width outside 720 to 4000.
27
+ */
28
+ export function broadcastMedalTable(source, options = {}) {
29
+ const rule = { ...(options.sort !== undefined ? { sort: options.sort } : {}), ...(options.ties !== undefined ? { ties: options.ties } : {}), ...(options.top !== undefined ? { top: options.top } : {}), ...(options.skipEmpty !== undefined ? { skipEmpty: options.skipEmpty } : {}) };
30
+ validateMedalSource(source, rule);
31
+ if (options.labels !== undefined && options.labels !== "code" && options.labels !== "name")
32
+ throw new RangeError(`taisen-brackets: labels is "code" or "name", not ${JSON.stringify(options.labels)}`);
33
+ if (options.width !== undefined && options.width < 720)
34
+ throw new RangeError("taisen-brackets: a board's width is from 720 to 4000");
35
+ const rows = chosenMedalRows(source, rule);
36
+ if (rows.length > MAX_ROWS)
37
+ throw new RangeError(`taisen-brackets: a broadcast medal table has at most ${MAX_ROWS} rows (it is a screen graphic): cut the list with top`);
38
+ const size = broadcastMedalSize(rows.length, options);
39
+ const f = broadcastFrame(options, [source, options.sort, options.ties, options.top, options.skipEmpty, options.labels, options.ranks], size.height / (size.width / 1280));
40
+ const { W, u, v } = f;
41
+ const w = v.words;
42
+ const body = [panel(f)];
43
+ const title = titleBand(f, 0, source.title ?? w.medalTitle, source.date);
44
+ body.push(title.svg);
45
+ // The columns, from the right: a disc and a count for each medal, then the total.
46
+ const ns = 28 * u;
47
+ const maxMedal = Math.max(1, ...rows.map(({ row }) => Math.max(count(row.gold), count(row.silver), count(row.bronze))));
48
+ const maxTotal = Math.max(1, ...rows.map(({ row }) => count(row.gold) + count(row.silver) + count(row.bronze)));
49
+ const discR = 11 * u;
50
+ const medalW = discR * 2 + 12 * u + bw(v.number(maxMedal), ns) + 22 * u;
51
+ const totalLabel = v.upper(w.medalTotal);
52
+ const totalW = Math.max(bw(v.number(maxTotal), ns), bw(totalLabel, 18 * u * 1.07)) + 30 * u;
53
+ const right = W - 16 * u;
54
+ const totalX = right - totalW / 2;
55
+ const medalX = [0, 1, 2].map((k) => right - totalW - (2 - k) * medalW - medalW / 2);
56
+ const left = medalX[0] - medalW / 2;
57
+ // The sub bar: the subtitle at the left, the discs over their columns and the word total.
58
+ const sub = { svg: "", h: BC.sub * u };
59
+ const sy = title.h;
60
+ const subParts = [box(0, sy, W, sub.h, "", 0, ` fill="url(#${esc(f.scope)}-gs)"`)];
61
+ const fs = 20 * u;
62
+ const subText = v.upper(source.subtitle ?? "");
63
+ if (subText) {
64
+ const room = (left - 20 * u - 14 * u) / 1.07;
65
+ const z = fitSize(subText, fs, room, 0.6);
66
+ subParts.push(t(20 * u, sy + sub.h / 2 + z * 0.36, z, fit(subText, z, room, bw), "bc-subink", "start", 800, ' letter-spacing=".02em"'));
67
+ }
68
+ [1, 2, 3].forEach((k, i) => {
69
+ const name = [w.medalGold, w.medalSilver, w.medalBronze][i];
70
+ subParts.push(`<g class="bc-medalhead"><title>${esc(name)}</title>${disc(medalX[i] - medalW / 2 + discR + 4 * u, sy + sub.h / 2, 12 * u, k)}</g>`);
71
+ });
72
+ const tz = fitSize(totalLabel, 18 * u, (totalW - 8 * u) / 1.07, 0.6);
73
+ subParts.push(t(totalX, sy + sub.h / 2 + tz * 0.36, tz, fit(totalLabel, tz, (totalW - 8 * u) / 1.07, bw), "bc-subink", "middle", 800, ' letter-spacing=".02em"'));
74
+ body.push(subParts.join(""));
75
+ const top = title.h + sub.h + BC.pad * u;
76
+ const h = ROW * u;
77
+ const showRank = options.ranks ?? true;
78
+ const useCode = (options.labels ?? (rows.every(({ row }) => row.code) ? "code" : "name")) === "code";
79
+ const labelOf = (r) => (useCode ? r.code ?? abbreviate(r.name).slice(0, 3).toUpperCase() : r.name);
80
+ const rankTexts = rows.map(({ rank }) => String(v.number(rank)));
81
+ const rankW = showRank ? Math.max(...rankTexts.map((x) => bw(x, ns))) + 18 * u : 0;
82
+ const flagH = 28 * u, flagW = flagH * (4 / 3);
83
+ rows.forEach(({ row }, i) => {
84
+ const y = top + i * (h + BC.gap * u);
85
+ const cy = y + h / 2;
86
+ const g = [box(0, y, W, h, i % 2 === 0 ? "bc-rowa" : "bc-rowb"), gloss(f, 0, y, W, h)];
87
+ let x = 14 * u;
88
+ if (showRank) {
89
+ g.push(t(x + rankW / 2 - 6 * u, cy + ns * 0.36, ns * 0.9, rankTexts[i], "bc-ink", "middle", 800));
90
+ x += rankW;
91
+ }
92
+ g.push(drawMark({ id: `m${i}`, name: row.name, abbr: (row.code ?? abbreviate(row.name)).slice(0, 3).toUpperCase(), ...(row.flag ? { flag: row.flag } : {}), ...(row.mark ? { mark: row.mark } : {}) }, x + flagW / 2, cy, flagH, f.ctx));
93
+ x += flagW + 14 * u;
94
+ const label = labelOf(row);
95
+ const avail = Math.max(40 * u, left - 12 * u - x);
96
+ const lz = fitSize(label, 28 * u, avail, 0.6);
97
+ g.push(t(x, cy + lz * 0.36, lz, fit(label, lz, avail + 1, bw), "bc-ink", "start", 700));
98
+ [row.gold, row.silver, row.bronze].forEach((c, k) => {
99
+ const cx = medalX[k];
100
+ g.push(disc(cx - medalW / 2 + discR + 4 * u, cy, discR, (k + 1)));
101
+ g.push(t(cx + medalW / 2 - 14 * u, cy + ns * 0.36, ns, v.number(count(c)), "bc-ink", "end", 700));
102
+ });
103
+ g.push(t(totalX + totalW / 2 - 14 * u, cy + ns * 0.36 * 1.05, ns * 1.05, v.number(count(row.gold) + count(row.silver) + count(row.bronze)), "bc-ink", "end", 800));
104
+ body.push(`<g class="bc-row">${g.join("")}</g>`);
105
+ });
106
+ if (rows.length === 0)
107
+ body.push(t(20 * u, top + 30 * u, 24 * u, w.medalNone, "bc-sub", "start", 700));
108
+ return broadcastWrap(f, options.title ?? source.title ?? w.medalTitle, describeMedalTable(source, { ...rule, ...(options.locale !== undefined ? { locale: options.locale } : {}), ...(options.words !== undefined ? { words: options.words } : {}) }), body, "broadcast-medal-table");
109
+ }
package/dist/index.d.ts CHANGED
@@ -41,3 +41,5 @@ export { BROADCAST_LOOKS } from "./broadcast-kit.ts";
41
41
  export type { BroadcastIcon, BroadcastLook, BroadcastOptions } from "./broadcast-kit.ts";
42
42
  export { boutBoard, boutBoardSize, describeBoutBoard } from "./bout-board.ts";
43
43
  export type { BoutAthlete, BoutBoardOptions, BoutBoardSource, BoutOfficial } from "./bout-board.ts";
44
+ export { broadcastMedalTable, broadcastMedalSize } from "./broadcast-medals.ts";
45
+ export type { BroadcastMedalOptions } from "./broadcast-medals.ts";
package/dist/index.js CHANGED
@@ -22,3 +22,4 @@ export { medalTable, medalRanks, tallyMedals, describeMedalTable } from "./medal
22
22
  export { resultsBoard, resultsBoardSize, describeResultsBoard } from "./results-board.js";
23
23
  export { BROADCAST_LOOKS } from "./broadcast-kit.js";
24
24
  export { boutBoard, boutBoardSize, describeBoutBoard } from "./bout-board.js";
25
+ export { broadcastMedalTable, broadcastMedalSize } from "./broadcast-medals.js";
package/dist/medals.d.ts CHANGED
@@ -76,6 +76,13 @@ export interface MedalFilter {
76
76
  export declare function tallyMedals(awards: readonly MedalAward[], filter?: MedalFilter): MedalRow[];
77
77
  /** The ranks of a list of rows, as `medalTable` draws them (`[1, 2, 2, 4]`), in the order they are drawn. */
78
78
  export declare function medalRanks(rows: readonly MedalRow[], options?: Pick<MedalTableOptions, "sort" | "ties">): number[];
79
+ /** Checks a medal table's source and options (shared by the broadcast-style table). */
80
+ export declare function validateMedalSource(source: MedalTableSource, options: MedalTableOptions): void;
81
+ /** The rows that are drawn: sorted, ranked, with the empty ones and all but the top N left out when asked. */
82
+ export declare function chosenMedalRows(source: MedalTableSource, options: Pick<MedalTableOptions, "sort" | "ties" | "skipEmpty" | "top">): {
83
+ row: MedalRow;
84
+ rank: number;
85
+ }[];
79
86
  /** The table as a sentence, the drawing's description. */
80
87
  export declare function describeMedalTable(source: MedalTableSource, options?: Pick<MedalTableOptions, "locale" | "words" | "sort" | "ties" | "top" | "skipEmpty">): string;
81
88
  /**
package/dist/medals.js CHANGED
@@ -54,7 +54,8 @@ function ordered(rows, sort, ties) {
54
54
  export function medalRanks(rows, options = {}) {
55
55
  return ordered(rows, options.sort ?? "gold", options.ties ?? true).map((x) => x.rank);
56
56
  }
57
- function validate(source, options) {
57
+ /** Checks a medal table's source and options (shared by the broadcast-style table). */
58
+ export function validateMedalSource(source, options) {
58
59
  if (!source || !Array.isArray(source.rows))
59
60
  throw new RangeError("taisen-brackets: a medal table needs a list of rows");
60
61
  source.rows.forEach((r, i) => {
@@ -79,7 +80,7 @@ function validate(source, options) {
79
80
  throw new RangeError("taisen-brackets: page is a whole number from 0");
80
81
  }
81
82
  /** The rows that are drawn: sorted, ranked, with the empty ones and all but the top N left out when asked. */
82
- function chosen(source, options) {
83
+ export function chosenMedalRows(source, options) {
83
84
  let list = ordered(source.rows, options.sort ?? "gold", options.ties ?? true);
84
85
  if (options.skipEmpty)
85
86
  list = list.filter((x) => totalOf(x.row) > 0);
@@ -94,7 +95,7 @@ export function describeMedalTable(source, options = {}) {
94
95
  parts.push(source.subtitle);
95
96
  if (source.date)
96
97
  parts.push(source.date);
97
- const list = chosen(source, options);
98
+ const list = chosenMedalRows(source, options);
98
99
  if (list.length === 0)
99
100
  parts.push(w.medalNone);
100
101
  for (const { row, rank } of list)
@@ -106,13 +107,13 @@ const teamFor = (r, id) => ({ id, name: r.name, abbr: (r.code ?? abbreviate(r.na
106
107
  * The medal table as an SVG. Throws a `RangeError` for a row with no name, a medal count that is not a whole number from 0, an option that is not one of its values, or a bad size or theme.
107
108
  */
108
109
  export function medalTable(source, options = {}) {
109
- validate(source, options);
110
+ validateMedalSource(source, options);
110
111
  const f = boardFrame(options, [source, options.sort, options.ties, options.rankStyle, options.top, options.rows, options.page, options.columns, options.highlight, options.skipEmpty]);
111
112
  const { W, H, v } = f;
112
113
  const w = v.words;
113
114
  const unit = Math.min(W, H);
114
115
  const pad = Math.round(unit * 0.035);
115
- const all = chosen(source, options);
116
+ const all = chosenMedalRows(source, options);
116
117
  const columns = options.columns ?? (W / H >= 1.5 && all.length > 10 ? 2 : 1);
117
118
  const head = heading(f, source.title ?? w.medalTitle, source.subtitle, source.date, pad, columns === 2 ? W - pad * 2 : Math.min(W - pad * 2, unit * 1.3), source.source);
118
119
  const body = [...head.svg];
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.16.0";
1
+ export declare const VERSION = "0.17.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.16.0";
1
+ export const VERSION = "0.17.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/taisen-brackets",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
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, as a tall card or for a phone, in a classic or a road look (large round flags, big scores, a team's dotted path, a link on every box), in light, dark or a gold-on-navy poster look, in eight layouts (the tree, rows, shields, a score-update card, tall columns, the NBA's play-in, a series card and group tables), on one of twenty-five poster backgrounds (the playing surfaces of five sports among them) or your own, with each team's logo or round flag, the champion in the centre, and an accessible table; and the road to the final of either finalist. 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",