@johnmorrisdotca/taisen-brackets 0.6.1 → 0.8.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 +31 -0
- package/NOTICE.md +3 -1
- package/README.md +15 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/judo-board.d.ts +87 -0
- package/dist/judo-board.js +460 -0
- package/dist/judo-model.d.ts +66 -0
- package/dist/judo-model.js +102 -0
- package/dist/judo-text.d.ts +19 -0
- package/dist/judo-text.js +84 -0
- package/dist/judo.d.ts +44 -0
- package/dist/judo.js +75 -0
- package/dist/layout.js +2 -2
- package/dist/layouts.js +4 -3
- package/dist/locales.js +30 -0
- package/dist/matchup.js +41 -8
- package/dist/playin.js +2 -1
- package/dist/render.js +37 -18
- package/dist/table.js +3 -2
- package/dist/theme.d.ts +9 -0
- package/dist/theme.js +5 -0
- package/dist/types.d.ts +56 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { layoutBracket } from "./layout.js";
|
|
2
2
|
import { abbrOf, describeTie, noteParts } from "./matchup.js";
|
|
3
|
+
import { shidoCards, sideScore } from "./judo-text.js";
|
|
3
4
|
import { cleanSvg, drawEmblem, drawMark, hasTrophy as hasTrophyOf, isEmblemId, markDefs, safeHref, safeUrl } from "./marks.js";
|
|
4
5
|
import { roundLabel } from "./rounds.js";
|
|
5
6
|
import { styleFor } from "./theme.js";
|
|
@@ -115,7 +116,7 @@ export function drawBracket(input, options = {}) {
|
|
|
115
116
|
const orientation = options.orientation && options.orientation !== "auto" ? options.orientation : kind === "series" || sizeName === "phone" || sizeName === "card" || (size.height !== null && size.width < size.height * 0.8) ? "vertical" : "horizontal";
|
|
116
117
|
const road = options.look === "road";
|
|
117
118
|
const hasBg = typeof options.background === "string" || (typeof options.background === "object" && options.background !== null);
|
|
118
|
-
const prefix = options.id ?? `tb-${hash(JSON.stringify([v.locale.code, layout.teams.map((t) => t.name), layout.ties.map((t) => [t.id, t.sides.map((s) => s?.id ?? null), t.winner, t.result.score, t.round]), options.size, orientation, options.detail, theme, options.title, options.subtitle, options.seeds, options.scores, options.champion, options.markShape, options.roundLabels, options.thirdPlace, options.trophy ? 1 : 0, road ? 1 : 0, options.highlightPath ?? null, options.flags ? 1 : 0, ...(hasBg ? [options.background, options.vs, options.codes, options.seeds] : []), ...(options.flagHref ? ["h"] : []), ...(kind !== "tree" ? [kind, options.at, options.standings, options.qualify] : []), ...(options.headers ? ["head"] : [])]))}`;
|
|
119
|
+
const prefix = options.id ?? `tb-${hash(JSON.stringify([v.locale.code, layout.teams.map((t) => t.name), layout.ties.map((t) => [t.id, t.sides.map((s) => s?.id ?? null), t.winner, t.result.score, t.round, ...(t.judo ? [t.judo.method, t.judo.goldenScore, t.judo.seconds, t.judo.sides] : [])]), options.size, orientation, options.detail, theme, options.title, options.subtitle, options.seeds, options.scores, options.champion, options.markShape, options.roundLabels, options.thirdPlace, options.trophy ? 1 : 0, road ? 1 : 0, options.highlightPath ?? null, options.flags ? 1 : 0, ...(hasBg ? [options.background, options.vs, options.codes, options.seeds] : []), ...(options.flagHref ? ["h"] : []), ...(kind !== "tree" ? [kind, options.at, options.standings, options.qualify] : []), ...(options.headers ? ["head"] : [])]))}`;
|
|
119
120
|
const ctx = { radii: new Set(), symbols: new Map(), prefix, shape: options.markShape ?? "round", look: road ? "road" : "classic", flags: options.flags, flagHref: options.flagHref, theme: theme === "light" ? "light" : theme === "dark" || theme === "poster" ? "dark" : "inherit", flagCache: new Map(), defs: new Map() };
|
|
120
121
|
const path = pathOf(layout, options.highlightPath);
|
|
121
122
|
const linked = { count: 0 };
|
|
@@ -124,10 +125,10 @@ export function drawBracket(input, options = {}) {
|
|
|
124
125
|
// The group tables and the play-in are not the tree: they plan and draw their own parts, and share the frame (the style, the background, the title) with it.
|
|
125
126
|
const own = kind === "groups" ? drawGroups(layout, options, v, size, ctx, road, linked) : kind === "playin" ? drawPlayIn(layout, options, v, size, ctx, road, linked) : null;
|
|
126
127
|
if (own)
|
|
127
|
-
return frame({ prefix, v, options, theme, road, kind, title, desc: own.desc ?? desc, width: own.width, height: own.height, followed: false, links: [], parts: [...own.body, ...own.texts], linkCount: linked.count, ctx });
|
|
128
|
+
return frame({ prefix, v, options, theme, road, kind, title, desc: own.desc ?? desc, width: own.width, height: own.height, followed: false, links: [], parts: [...own.body, ...own.texts], linkCount: linked.count, ctx, judo: layout.ties.some((t) => t.judo !== undefined) });
|
|
128
129
|
const plan = planBracket(layout, options, v, size, orientation === "vertical", ctx, road, path, kind);
|
|
129
130
|
const drawn = plan.boxes.map((b) => drawBox(b, plan, options, v, ctx, linked, layout.place[b.tie.index])); // draws the marks, so before the defs are written
|
|
130
|
-
return frame({ prefix, v, options, theme, road, kind, title, desc, width: plan.width, height: plan.height, followed: path.size > 0, links: plan.links, parts: [...drawn, ...plan.texts, ...plan.extras], linkCount: linked.count, ctx });
|
|
131
|
+
return frame({ prefix, v, options, theme, road, kind, title, desc, width: plan.width, height: plan.height, followed: path.size > 0, links: plan.links, parts: [...drawn, ...plan.texts, ...plan.extras], linkCount: linked.count, ctx, judo: layout.ties.some((t) => t.judo !== undefined) });
|
|
131
132
|
}
|
|
132
133
|
/** The drawing's frame: the root `<svg>` with its title, its style, the defs the marks wrote and the background, round the parts a layout drew. */
|
|
133
134
|
function frame(f) {
|
|
@@ -135,7 +136,7 @@ function frame(f) {
|
|
|
135
136
|
const picture = resolveBackground(options.background, theme, f.width, f.height, prefix);
|
|
136
137
|
const defs = markDefs(ctx) + (picture?.defs ?? "");
|
|
137
138
|
const scope = scopeOf(options, prefix);
|
|
138
|
-
const style = theme === "none" || options.style === false ? "" : `<style>${styleFor(`.${scope}`, theme, { road, links: f.linkCount > 0, follow: f.followed, bgAuto: picture?.auto, layout: f.kind !== "tree" || options.headers === true }, picture?.inks)}</style>`;
|
|
139
|
+
const style = theme === "none" || options.style === false ? "" : `<style>${styleFor(`.${scope}`, theme, { road, links: f.linkCount > 0, follow: f.followed, bgAuto: picture?.auto, layout: f.kind !== "tree" || options.headers === true, judo: f.judo }, picture?.inks)}</style>`;
|
|
139
140
|
const bg = options.background === false ? "" : `<rect class="tb-bg" width="${num(f.width)}" height="${num(f.height)}"${theme === "none" ? ' fill="none"' : ""}/>${picture?.body ?? ""}`;
|
|
140
141
|
return [
|
|
141
142
|
`<svg xmlns="http://www.w3.org/2000/svg" lang="${esc(v.locale.code)}" class="taisen ${prefix}${scope !== prefix ? ` ${scope}` : ""}${road ? " tb-look-road" : ""}${f.followed ? " tb-follow" : ""}${options.className ? ` ${esc(options.className)}` : ""}" viewBox="0 0 ${num(f.width)} ${num(f.height)}" width="${num(f.width)}" height="${num(f.height)}" style="max-width:100%;height:auto" role="img" aria-labelledby="${prefix}-t ${prefix}-d">`,
|
|
@@ -168,7 +169,7 @@ export function bracketStyle(options) {
|
|
|
168
169
|
if (theme === "none")
|
|
169
170
|
return "";
|
|
170
171
|
const picture = resolveBackground(options.background, theme, 100, 100, "x");
|
|
171
|
-
return styleFor(`.${scope}`, theme, { road: options.look === "road", links: typeof options.href === "function", follow: options.highlightPath !== undefined, bgAuto: picture?.auto, layout: (options.layout ?? "tree") !== "tree" || options.headers === true }, picture?.inks);
|
|
172
|
+
return styleFor(`.${scope}`, theme, { road: options.look === "road", links: typeof options.href === "function", follow: options.highlightPath !== undefined, bgAuto: picture?.auto, layout: (options.layout ?? "tree") !== "tree" || options.headers === true, judo: options.judo === true }, picture?.inks);
|
|
172
173
|
}
|
|
173
174
|
/** The groups' names with the east first, for the series card: the names the teams carry, ordered so that a name with "east" comes before one with "west". */
|
|
174
175
|
function eastFirst(input) {
|
|
@@ -181,6 +182,8 @@ function eastFirst(input) {
|
|
|
181
182
|
}
|
|
182
183
|
// ---------------------------------------------------------------------------------------------- the plan
|
|
183
184
|
function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
185
|
+
// The ties kept apart below the tree are a little taller in the road look when any of them has a note to write (a judo contest's method and time), so that the note has its line.
|
|
186
|
+
const OH = road && [...layout.other, ...layout.robin, ...layout.playIn].some((i) => noteParts(layout.ties[i], v).length > 0) ? 66 : OTHER_H;
|
|
184
187
|
// The layouts that draw each tie their own way take the tree's places and lines, and fill the boxes themselves.
|
|
185
188
|
const V = VARIANTS.includes(kind) ? kind : null;
|
|
186
189
|
const pad = vertical ? 14 : 22;
|
|
@@ -238,7 +241,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
238
241
|
const top = pad + titleH + (hasGroups ? groupRoom : 0) + labelRoom;
|
|
239
242
|
others = othersOf(layout);
|
|
240
243
|
ow = clamp(D * 1.1, 120, 200);
|
|
241
|
-
extraH0 = othersHeight(others.length, W0, pad, ow,
|
|
244
|
+
extraH0 = othersHeight(others.length, W0, pad, ow, OH) + thirdBlock;
|
|
242
245
|
const bodyH = size.height === null ? rows * 60 : size.height - top - pad - extraH0;
|
|
243
246
|
rowStep = bodyH / rows;
|
|
244
247
|
S = clamp(rowStep - (road ? 7 : 9), 24, road ? 132 : 104);
|
|
@@ -264,6 +267,8 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
264
267
|
const tagW = tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 8))) + 8 : 0;
|
|
265
268
|
// The road look's marks and scores are large: they are scaled down together until a row fits its box, before a name is dropped for its abbreviation.
|
|
266
269
|
const anyNotes = layout.ties.some((t) => noteParts(t, v).length > 0);
|
|
270
|
+
// The widest score a row may carry: two digits, or a judo contest's marks ("W2 Y3").
|
|
271
|
+
const probe = layout.ties.reduce((best, t) => [0, 1].reduce((b, k) => { const x = sideScore(t, k, v); return wide(x, 10) > wide(b, 10) ? x : b; }, best), "00");
|
|
267
272
|
// The road look's boxes of marks alone carry each score under its mark, unless marks alone were asked for.
|
|
268
273
|
const markScores = road && o.detail !== "mark" && (o.scores ?? true) && layout.ties.some((t) => t.result.score !== null);
|
|
269
274
|
const fitOf = (w, h, named) => {
|
|
@@ -273,7 +278,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
273
278
|
const anyPens = layout.ties.some((t) => t.result.pens !== null);
|
|
274
279
|
const extra = (anyBye ? wide(v.words.bye, Math.max(font - 2, 8)) + 8 : 0) + (anyPens ? wide("(00)", Math.max(score * 0.55, 8)) + 5 : 0);
|
|
275
280
|
const fixed = clamp(rowH * 0.16, 5, 12) + clamp(rowH * 0.18, 5, 10) + 8 + 10 + (tagW > 0 ? tagW + 3 : 0);
|
|
276
|
-
const varying = md + (named === "full" ? longest : longestAbbr) * (font / 13) + wide(
|
|
281
|
+
const varying = md + (named === "full" ? longest : longestAbbr) * (font / 13) + wide(probe, score) + extra;
|
|
277
282
|
return Math.min(1, (w - fixed) / varying);
|
|
278
283
|
};
|
|
279
284
|
let fitNow = 1;
|
|
@@ -284,7 +289,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
284
289
|
const anyBye = layout.sides.some((sd) => sd.boxes.some((b) => b.byes.length > 0));
|
|
285
290
|
const anyPens = layout.ties.some((t) => t.result.pens !== null);
|
|
286
291
|
const extra = (anyBye ? boldWidth(v.words.bye, Math.max(font - 2, 8)) + 8 : 0) + (anyPens ? boldWidth("(00)", Math.max(font - 2, 8)) + 3 : 0);
|
|
287
|
-
const needed = 6 + md + 5 + longest * (font / 13) + 8 + boldWidth(
|
|
292
|
+
const needed = 6 + md + 5 + longest * (font / 13) + 8 + boldWidth(probe, score) + extra + 14;
|
|
288
293
|
const named = o.detail && o.detail !== "auto" ? o.detail : w >= needed && h >= 36 ? "full" : w >= 76 && h >= 28 ? "compact" : "mark";
|
|
289
294
|
return named === "mark" && o.markShape === "none" ? "compact" : named;
|
|
290
295
|
};
|
|
@@ -389,7 +394,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
389
394
|
dl = pick(wl, hl);
|
|
390
395
|
const strays = othersOf(layout);
|
|
391
396
|
const ows = Math.min(availW, 200);
|
|
392
|
-
const strayH = othersHeight(strays.length, W0, pad, ows,
|
|
397
|
+
const strayH = othersHeight(strays.length, W0, pad, ows, OH);
|
|
393
398
|
const labelH = showLabels ? 22 : 8;
|
|
394
399
|
const content = (champ ? champH + 14 : 0) + labelH + hl + (withThird ? 8 + 18 + hl + 8 : 0) + strayH;
|
|
395
400
|
const top0 = pad + titleH;
|
|
@@ -410,7 +415,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
410
415
|
y += 8 + 18 + hl + 8;
|
|
411
416
|
}
|
|
412
417
|
if (strays.length > 0)
|
|
413
|
-
othersBlock(layout, strays, y + 6, W0, pad, ows,
|
|
418
|
+
othersBlock(layout, strays, y + 6, W0, pad, ows, OH, v, road, boxes, texts);
|
|
414
419
|
texts.push(...headings(v, o, W0, pad, poster));
|
|
415
420
|
return finish({ width: W0, height, boxes, links: [], texts, extras, detail: dl, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
416
421
|
}
|
|
@@ -518,7 +523,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
518
523
|
}
|
|
519
524
|
if (bottomThird)
|
|
520
525
|
thirdAtFoot(layout, height - pad - extraH0 + 6, width / 2, ow, OTHER_H, v, road, boxes, texts);
|
|
521
|
-
othersBlock(layout, others, height - pad - extraH0 + thirdBlock + 6, width, pad, ow,
|
|
526
|
+
othersBlock(layout, others, height - pad - extraH0 + thirdBlock + 6, width, pad, ow, OH, v, road, boxes, texts);
|
|
522
527
|
texts.push(...headings(v, o, width, pad, poster));
|
|
523
528
|
return finish({ width, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
524
529
|
}
|
|
@@ -531,7 +536,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
531
536
|
const bandCount = halves ? 2 * bandsPerSide : nc + (champ ? 1 : 0);
|
|
532
537
|
others = othersOf(layout);
|
|
533
538
|
ow = Math.min(W0 - 2 * pad, 150);
|
|
534
|
-
const othersV = othersHeight(others.length, W0, pad, ow,
|
|
539
|
+
const othersV = othersHeight(others.length, W0, pad, ow, OH);
|
|
535
540
|
// The height the page needs for boxes `h` high with `gap` between the rounds, and what it keeps for the final's band and the third-place match.
|
|
536
541
|
const needOf = (h, gap) => {
|
|
537
542
|
const finalHere = V === "columns" ? Math.max(h, finalH) : detail === "mark" ? Math.max(h, 52) : h;
|
|
@@ -664,7 +669,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
|
664
669
|
}
|
|
665
670
|
if (bottomThird)
|
|
666
671
|
thirdAtFoot(layout, totalT - pad + 6, W0 / 2, Math.min(W0 - 2 * pad, road ? Math.max(boxW, 200) : 200), thirdH2, v, road, boxes, texts);
|
|
667
|
-
othersBlock(layout, others, totalT - pad + thirdBlockV + 6, W0, pad, ow,
|
|
672
|
+
othersBlock(layout, others, totalT - pad + thirdBlockV + 6, W0, pad, ow, OH, v, road, boxes, texts);
|
|
668
673
|
texts.push(...headings(v, o, W0, pad, poster));
|
|
669
674
|
return finish({ width: W0, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
670
675
|
}
|
|
@@ -868,7 +873,10 @@ function othersBlock(layout, list, y0, width, pad, ow, oh, v, road, boxes, texts
|
|
|
868
873
|
return;
|
|
869
874
|
const cols = Math.max(1, Math.floor((width - 2 * pad + 10) / (ow + 10)));
|
|
870
875
|
const x0 = pad + (width - 2 * pad - (Math.min(cols, list.length) * (ow + 10) - 10)) / 2;
|
|
871
|
-
|
|
876
|
+
const stages = new Set(list.map((i) => layout.ties[i].stage ?? "other"));
|
|
877
|
+
const w = v.words;
|
|
878
|
+
const heading = stages.size === 1 && stages.has("repechage") ? w.judoRepechage : stages.size === 1 && stages.has("bronze") ? w.judoBronze : [...stages].every((x) => x === "repechage" || x === "bronze") ? w.judoRepechageBronze : w.other;
|
|
879
|
+
texts.push(label(v, road, heading, width / 2, y0 + 12, "middle"));
|
|
872
880
|
list.forEach((i, n) => {
|
|
873
881
|
const rect = { x: x0 + (n % cols) * (ow + 10), y: y0 + 24 + Math.floor(n / cols) * (oh + 10), w: ow, h: oh };
|
|
874
882
|
boxes.push({ rect, tie: layout.ties[i], order: [0, 1], bye: null, byes: [], final: false, champion: false });
|
|
@@ -932,7 +940,7 @@ function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
|
932
940
|
const withScore = plan.markScores && scoresOn && r.score !== null;
|
|
933
941
|
const tags = [0, 1].map((k) => (plan.tags ? tagOf(tie, k) : ""));
|
|
934
942
|
const tagH = tags.some((t) => t !== "") ? 12 : 0;
|
|
935
|
-
const scoreOf = (k) => (withScore
|
|
943
|
+
const scoreOf = (k) => (withScore ? sideScore(tie, k, v) : "");
|
|
936
944
|
const pensOf = (k) => (withScore && r.pens ? `(${v.number(r.pens[k])})` : "");
|
|
937
945
|
const hasPens = withScore && r.pens !== null;
|
|
938
946
|
const lay = (m) => {
|
|
@@ -1028,7 +1036,13 @@ function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
|
1028
1036
|
rowParts.push(`<rect class="tb-tagbox" x="${num(tx)}" y="${num(cy - (tf + 4) / 2)}" width="${num(tw)}" height="${num(tf + 4)}" rx="${num((tf + 4) / 2)}"/><text class="tb-tag" x="${num(tx + tw / 2)}" y="${num(cy + tf * 0.35)}" font-size="${num(tf)}" text-anchor="middle">${esc(tag)}</text>`);
|
|
1029
1037
|
tx += tw + 3;
|
|
1030
1038
|
}
|
|
1031
|
-
const score = scoresOn
|
|
1039
|
+
const score = scoresOn ? sideScore(tie, k, v) : "";
|
|
1040
|
+
// A judo contest: a bar at the row's edge in the colour of the side (the first is white, the second blue) and its shido cards beside the score.
|
|
1041
|
+
if (tie.judo)
|
|
1042
|
+
rowParts.push(`<rect class="${k === 0 ? "tb-jw" : "tb-jb"}" x="${num(x + 1.5)}" y="${num(cy - rowH * 0.3)}" width="3" height="${num(rowH * 0.6)}" rx="1.5"/>`);
|
|
1043
|
+
const cards = tie.judo && scoresOn ? shidoCards(tie, k) : [];
|
|
1044
|
+
const cardSize = Math.max(5, Math.min(9, rowH * 0.3));
|
|
1045
|
+
const cardsW = cards.length ? cards.length * (cardSize * 0.72 + 2) + 4 : 0;
|
|
1032
1046
|
const pensText = scoresOn && r.pens ? `(${v.number(r.pens[k])})` : "";
|
|
1033
1047
|
const bye = p.byes.includes(k);
|
|
1034
1048
|
const right = x + w - padX;
|
|
@@ -1040,7 +1054,8 @@ function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
|
1040
1054
|
const byeFits = bye && right - tx - (scoreW + pensW) - (mw(v.words.bye, small2) + 6) - 6 >= mw(`${seed}${nm}`, font);
|
|
1041
1055
|
const byeText = byeFits ? v.words.bye : "";
|
|
1042
1056
|
const byeW = byeText ? mw(byeText, small2) : 0;
|
|
1043
|
-
const
|
|
1057
|
+
const cardsOn = cardsW > 0 && right - tx - (scoreW + pensW) - cardsW - 6 >= 36;
|
|
1058
|
+
const avail = right - tx - (scoreW + pensW) - (byeW ? byeW + 6 : 0) - 6 - (cardsOn ? cardsW : 0);
|
|
1044
1059
|
// A name that does not fit is set a little smaller before it is cut.
|
|
1045
1060
|
let nameFont = font;
|
|
1046
1061
|
while (nameFont > 9.5 && mw(`${seed}${nm}`, nameFont) > avail)
|
|
@@ -1049,6 +1064,10 @@ function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
|
1049
1064
|
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>`);
|
|
1050
1065
|
if (score)
|
|
1051
1066
|
rowParts.push(`<text class="tb-score" x="${num(right)}" y="${num(cy + scoreFont * 0.35)}" font-size="${num(scoreFont)}" text-anchor="end" font-variant-numeric="tabular-nums">${esc(score)}</text>`);
|
|
1067
|
+
if (cardsOn) {
|
|
1068
|
+
const cw = cardSize * 0.72;
|
|
1069
|
+
cards.forEach((c, n) => rowParts.push(`<rect class="${c === "r" ? "tb-sh-r" : "tb-sh-y"}" x="${num(right - scoreW - pensW - cardsW + 2 + n * (cw + 2))}" y="${num(cy - cardSize / 2)}" width="${num(cw)}" height="${num(cardSize)}" rx="1"/>`));
|
|
1070
|
+
}
|
|
1052
1071
|
if (pensText)
|
|
1053
1072
|
rowParts.push(`<text class="tb-muted" x="${num(right - scoreW - (road ? 4 : 3))}" y="${num(cy + small * 0.35)}" font-size="${num(small)}" text-anchor="end">${esc(pensText)}</text>`);
|
|
1054
1073
|
if (byeText)
|
|
@@ -1059,7 +1078,7 @@ function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
|
1059
1078
|
if (noteH) {
|
|
1060
1079
|
const f = road ? 9 : 9.5;
|
|
1061
1080
|
const line = road ? v.upper(notes.join(" · ")) : notes.join(" · ");
|
|
1062
|
-
parts.push(`<text class="tb-muted${road ? " tb-note" : ""}" x="${num(x + w / 2)}" y="${num(y + h - (road ? 5 : 4))}" font-size="${f}"${road ? ' font-weight="700" letter-spacing=".05em"' : ""} text-anchor="middle">${esc(fit(line, f * (road ? 1.1 : 1), w - 8))}</text>`);
|
|
1081
|
+
parts.push(`<text class="tb-muted${road ? " tb-note" : ""}" x="${num(x + w / 2)}" y="${num(y + h - (road ? 5 : 4))}" font-size="${f}"${road ? ' font-weight="700" letter-spacing=".05em"' : ""} text-anchor="middle">${esc(fit(line, f * (road ? 1.1 : 1), w - 8, boldWidth))}</text>`);
|
|
1063
1082
|
}
|
|
1064
1083
|
}
|
|
1065
1084
|
const extra = tieClassOf(o, tie);
|
package/dist/table.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { layoutBracket } from "./layout.js";
|
|
2
2
|
import { noteLong } from "./matchup.js";
|
|
3
|
+
import { sideScore } from "./judo-text.js";
|
|
3
4
|
import { fill, voiceOf } from "./locales.js";
|
|
4
5
|
import { localizedLayout } from "./render.js";
|
|
5
6
|
import { roundLabel } from "./rounds.js";
|
|
@@ -23,12 +24,12 @@ export function bracketTable(input, options = {}) {
|
|
|
23
24
|
const text = team ? esc(team.name) : esc(w.tbd);
|
|
24
25
|
return t.winner === k ? `<strong>${text}</strong>` : text;
|
|
25
26
|
};
|
|
26
|
-
const score = (t) => (t.result.score ? fill(w.scoreText, { a:
|
|
27
|
+
const score = (t) => (t.result.score ? fill(w.scoreText, { a: sideScore(t, 0, v), b: sideScore(t, 1, v) }) : "");
|
|
27
28
|
// Where a side comes from, in words, when the tie says so: "Loser of A v B".
|
|
28
29
|
const named = (i) => fill(w.tieVs, { a: layout.ties[i].sides[0]?.name ?? w.tbd, b: layout.ties[i].sides[1]?.name ?? w.tbd });
|
|
29
30
|
const origins = (t) => (t.from ?? []).flatMap((s) => (s ? [fill(s.result === "loser" ? w.loserOf : w.winnerOf, { name: named(s.tie) })] : []));
|
|
30
31
|
const rows = order.map((t) => {
|
|
31
|
-
const roundText = t.third ? w.thirdPlace : t.playIn ? w.playIn : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
32
|
+
const roundText = t.third ? w.thirdPlace : t.stage === "repechage" ? w.judoRepechage : t.stage === "bronze" ? w.judoBronze : t.playIn ? w.playIn : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
32
33
|
const date = dated ? `<td>${esc(v.date(t.date) ?? "")}</td>` : "";
|
|
33
34
|
return `<tr><th scope="row">${esc(roundText)}</th>${date}<td>${name(t, 0)}</td><td>${esc(score(t))}</td><td>${name(t, 1)}</td><td>${esc([...noteLong(t, v), ...origins(t)].join(w.noteJoin))}</td></tr>`;
|
|
34
35
|
});
|
package/dist/theme.d.ts
CHANGED
|
@@ -36,7 +36,16 @@ export interface StyleParts {
|
|
|
36
36
|
bgAuto?: boolean;
|
|
37
37
|
/** The rules of the layouts of their own (slots, shields, conference colours, header tiles, arrows, group cards). */
|
|
38
38
|
layout?: boolean;
|
|
39
|
+
/** A judo bracket: the white and blue side bars and the shido cards (`--tb-judo-white`, `--tb-judo-blue`, `--tb-shido`, `--tb-hansoku`). */
|
|
40
|
+
judo?: boolean;
|
|
39
41
|
}
|
|
42
|
+
/** The colours of a judo contest: the white and the blue judogi, the yellow shido card and the red hansoku-make card. */
|
|
43
|
+
export declare const JUDO_COLORS: Readonly<{
|
|
44
|
+
white: "#ffffff";
|
|
45
|
+
blue: "#1f4fa8";
|
|
46
|
+
shido: "#f0c000";
|
|
47
|
+
hansoku: "#d3302a";
|
|
48
|
+
}>;
|
|
40
49
|
/** The colours of the two halves (conferences) of the layouts that colour them, each with white letters on it: `--tb-conf-a` and `--tb-conf-b`. Both read 5 to 1 or more against white. */
|
|
41
50
|
export declare const CONFERENCE_COLORS: readonly [string, string];
|
|
42
51
|
/** The colours a background says to draw in (`backgroundSvg`'s `ink`): what `styleFor` takes in place of a look's own palette. */
|
package/dist/theme.js
CHANGED
|
@@ -10,6 +10,8 @@ export const ROAD_THEMES = Object.freeze({
|
|
|
10
10
|
poster: { ring: "#f6f1e3", edge: "rgba(0,0,0,.5)", panel: "#1b2766", spine: "#d9b24c" },
|
|
11
11
|
});
|
|
12
12
|
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Hiragino Sans", "Yu Gothic UI", "Noto Sans CJK JP", Meiryo, sans-serif';
|
|
13
|
+
/** The colours of a judo contest: the white and the blue judogi, the yellow shido card and the red hansoku-make card. */
|
|
14
|
+
export const JUDO_COLORS = Object.freeze({ white: "#ffffff", blue: "#1f4fa8", shido: "#f0c000", hansoku: "#d3302a" });
|
|
13
15
|
/** The colours of the two halves (conferences) of the layouts that colour them, each with white letters on it: `--tb-conf-a` and `--tb-conf-b`. Both read 5 to 1 or more against white. */
|
|
14
16
|
export const CONFERENCE_COLORS = Object.freeze(["#2457c5", "#b3261e"]);
|
|
15
17
|
/** A palette and a road palette from a background's ink. */
|
|
@@ -59,6 +61,9 @@ function rules(p, r, poster, withFont, parts, side) {
|
|
|
59
61
|
const [ca, cb] = CONFERENCE_COLORS;
|
|
60
62
|
list.push([[".tb-slot"], `fill:${v("plain")};fill-opacity:.55;stroke:${v("edge")};stroke-width:1`], [[".tb-won .tb-slot"], `fill-opacity:.9;stroke:${v("win")};stroke-width:1.6`], [[".tb-lost .tb-slot"], "fill-opacity:.35"], [[".tb-emptybar"], `fill:${v("plain")};opacity:.8`], [[".tb-seed"], "font-variant-numeric:tabular-nums"], [[".tb-seedn"], `fill:${v("muted")};opacity:.85`], [[".tb-shield"], `fill:none;stroke:${v("edge")};stroke-width:1.4`], [[".tb-won .tb-shield"], `stroke:${v("win")};stroke-width:2.2`], [[".tb-shield.tb-empty"], `fill:${v("plain")};fill-opacity:.5`], [[".tb-vs", ".tb-code"], `fill:${v("muted")};font-weight:700`], [[".tb-c0 .tb-box"], `stroke:var(--tb-conf-a, ${ca});stroke-width:1.6`], [[".tb-c1 .tb-box"], `stroke:var(--tb-conf-b, ${cb});stroke-width:1.6`], [[".tb-atag"], `fill:${v("ink")}`], [[".tb-atag-t"], `fill:${v("bg")}`], [[".tb-c0 .tb-atag"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-c1 .tb-atag"], `fill:var(--tb-conf-b, ${cb})`], [[".tb-c0 .tb-atag-t", ".tb-c1 .tb-atag-t"], "fill:#fff"], [[".tb-head"], `fill:${rv("panel", "panel")}`], [[".tb-head.tb-c0"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-head.tb-c1"], `fill:var(--tb-conf-b, ${cb})`], [[".tb-head-t"], `fill:${v("ink")};font-weight:800`], [[".tb-head.tb-c0 + .tb-head-t", ".tb-head.tb-c1 + .tb-head-t"], "fill:#fff"], [[".tb-arrow"], `stroke:${v("line")};stroke-width:1.6;fill:none;stroke-linecap:round;stroke-linejoin:round`], [[".tb-arrow-loser"], "stroke-dasharray:5 4"], [[".tb-arrowhead"], `fill:${v("line")};stroke:none`], [[".tb-gcard"], `fill:${v("box")};stroke:${v("edge")};stroke-width:1`], [[".tb-gtile"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-gtile-t"], "fill:#fff;font-weight:800"], [[".tb-ghead"], `fill:${v("muted")};font-weight:700`], [[".tb-gcell"], `fill:${v("ink")};font-variant-numeric:tabular-nums`], [[".tb-gcell-p"], "font-weight:800"], [[".tb-gname"], `fill:${v("ink")};font-weight:700`], [[".tb-qual"], `fill:${v("win")};fill-opacity:.16`], [[".tb-gsep"], `stroke:${v("edge")};stroke-width:1;stroke-opacity:.6`]);
|
|
61
63
|
}
|
|
64
|
+
if (parts.judo === true) {
|
|
65
|
+
list.push([[".tb-jw"], `fill:var(--tb-judo-white, ${JUDO_COLORS.white});stroke:${v("edge")};stroke-width:.8`], [[".tb-jb"], `fill:var(--tb-judo-blue, ${JUDO_COLORS.blue});stroke:${v("edge")};stroke-width:.8`], [[".tb-sh-y"], `fill:var(--tb-shido, ${JUDO_COLORS.shido});stroke:rgba(0,0,0,.35);stroke-width:.6`], [[".tb-sh-r"], `fill:var(--tb-hansoku, ${JUDO_COLORS.hansoku});stroke:rgba(0,0,0,.35);stroke-width:.6`]);
|
|
66
|
+
}
|
|
62
67
|
if (parts.follow === true) {
|
|
63
68
|
list.push([[".tb-tie"], "opacity:.4"], [[".tb-tie.tb-on-path"], "opacity:1"], [[".tb-on-path .tb-box"], `stroke:${v("win")};stroke-width:2.2`], [[".tb-line"], "stroke-opacity:.25"], [[".tb-line.tb-path"], `stroke:${v("win")};stroke-width:4.5;stroke-opacity:1;stroke-linecap:round;stroke-dasharray:.1 9`]);
|
|
64
69
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* The types of Taisen: what goes in (teams and matchups), what the layout works out (ties, rounds, halves) and what
|
|
3
3
|
* the drawing takes as options. Nothing here runs; the functions are in `matchup.ts`, `layout.ts` and `render.ts`.
|
|
4
4
|
*/
|
|
5
|
+
import type { JudoOutcome, JudoResult } from "./judo-model.ts";
|
|
5
6
|
/** A side's key: a team code, a franchise id, anything that is unique within one bracket. */
|
|
6
7
|
export type SideId = string | number;
|
|
7
8
|
/** A side of a tie: a team, with the marks and labels a drawing needs. Only `id` and `name` are required. */
|
|
@@ -106,8 +107,13 @@ export interface Matchup {
|
|
|
106
107
|
walkover?: boolean;
|
|
107
108
|
/** This is the third-place match: it leads nowhere and is drawn apart. */
|
|
108
109
|
third?: boolean;
|
|
109
|
-
/**
|
|
110
|
-
|
|
110
|
+
/**
|
|
111
|
+
* `"play-in"`: a game of a play-in (the NBA's) that is outside the rounds of the tree: its winner (and loser, see `feeds`) goes on to a place the tree's ties name. Drawn by the `playin` layout and, apart, under the other layouts' trees.
|
|
112
|
+
* `"repechage"` and `"bronze"`: a judo repechage contest and a bronze medal contest, fed by the losers of the tree's ties (`judoBracket` writes them); drawn apart under the tree.
|
|
113
|
+
*/
|
|
114
|
+
stage?: "play-in" | "repechage" | "bronze";
|
|
115
|
+
/** A judo contest's scores: ippon, waza-ari, yuko, shido, hansoku-make, golden score and time. The first side is white, the second blue. The score, the winner and the notes come from it; see `judoOutcome`. */
|
|
116
|
+
judo?: JudoResult;
|
|
111
117
|
/** The side playing at home (0 or 1), for the `at` wording of the `columns` layout: the home side is drawn second, after "AT". Left out, the second side. */
|
|
112
118
|
home?: 0 | 1;
|
|
113
119
|
note?: string;
|
|
@@ -151,8 +157,12 @@ export interface Tie {
|
|
|
151
157
|
seeds?: readonly [string | number | null, string | number | null];
|
|
152
158
|
/** Where each side comes from, when the matchup's `feeds` name it: another tie's winner or its loser. Left out for a tie that says nothing of the kind. */
|
|
153
159
|
from?: readonly [Source | null, Source | null];
|
|
154
|
-
/** The tie is a play-in game (`Matchup.stage`). */
|
|
160
|
+
/** The tie is a play-in game, a judo repechage contest or a bronze medal contest (`Matchup.stage`). */
|
|
155
161
|
playIn?: boolean;
|
|
162
|
+
/** The tie's `Matchup.stage`, when it has one. */
|
|
163
|
+
stage?: "play-in" | "repechage" | "bronze";
|
|
164
|
+
/** A judo contest's result, checked and worked out (`Matchup.judo`). */
|
|
165
|
+
judo?: JudoOutcome;
|
|
156
166
|
/** The side at home (`Matchup.home`), when given. */
|
|
157
167
|
home?: 0 | 1;
|
|
158
168
|
}
|
|
@@ -342,6 +352,46 @@ export interface Words {
|
|
|
342
352
|
/** `{name}`: a side that is the winner or loser of another tie, in a sentence or a table: "Loser of {name}". */
|
|
343
353
|
winnerOf: string;
|
|
344
354
|
loserOf: string;
|
|
355
|
+
/** Judo. The marks of the scores on a box (ippon, waza-ari, yuko), the same written out, and how a contest was won. */
|
|
356
|
+
judoIppon: string;
|
|
357
|
+
judoWazaAri: string;
|
|
358
|
+
judoYuko: string;
|
|
359
|
+
judoIpponLong: string;
|
|
360
|
+
judoWazaAriLong: string;
|
|
361
|
+
judoYukoLong: string;
|
|
362
|
+
judoMethodIppon: string;
|
|
363
|
+
judoMethodAwasete: string;
|
|
364
|
+
judoMethodScore: string;
|
|
365
|
+
judoMethodHansoku: string;
|
|
366
|
+
judoMethodFusen: string;
|
|
367
|
+
judoMethodKiken: string;
|
|
368
|
+
/** Golden score, short and in a sentence. */
|
|
369
|
+
judoGolden: string;
|
|
370
|
+
judoGoldenLong: string;
|
|
371
|
+
/** `{t}`: the contest time ("3:42"). */
|
|
372
|
+
judoTime: string;
|
|
373
|
+
/** `{name}`, `{n}`: a side's shido ("Kenji Nakamura: 2 shido"). */
|
|
374
|
+
judoShidoOf: string;
|
|
375
|
+
/** `{a}`, `{b}`: the sides' shido in a short note. */
|
|
376
|
+
judoShidoShort: string;
|
|
377
|
+
/** The headings over the repechage contests and the bronze medal contests, alone and together; the sides' colours. */
|
|
378
|
+
judoRepechage: string;
|
|
379
|
+
judoBronze: string;
|
|
380
|
+
judoRepechageBronze: string;
|
|
381
|
+
judoWhite: string;
|
|
382
|
+
judoBlue: string;
|
|
383
|
+
/** `{sex}`, `{w}` (the number of kilograms, no sign): a weight category up to and including that weight ("Men -66 kg"), one over it ("Women +78 kg"), and the two sexes' words. */
|
|
384
|
+
judoWeightClass: string;
|
|
385
|
+
judoWeightOver: string;
|
|
386
|
+
judoMen: string;
|
|
387
|
+
judoWomen: string;
|
|
388
|
+
/** The scoreboard of a contest: the osaekomi timer's label, the shido row's, a mat (`{n}`), a contest (`{n}`), the coach (`{name}`), and the drawing's default title. */
|
|
389
|
+
judoOsaekomi: string;
|
|
390
|
+
judoShido: string;
|
|
391
|
+
judoMat: string;
|
|
392
|
+
judoContest: string;
|
|
393
|
+
judoCoach: string;
|
|
394
|
+
judoBoardTitle: string;
|
|
345
395
|
}
|
|
346
396
|
/** A language: its words, the names of the rounds (by their English names), and the tag `Intl` formats numbers and dates for. */
|
|
347
397
|
export interface Locale {
|
|
@@ -352,6 +402,7 @@ export interface Locale {
|
|
|
352
402
|
/** Round names by their English name: `{ "Round of 16": "Huitièmes de finale" }`. A round with no entry keeps its name (or `roundOf`). */
|
|
353
403
|
rounds: Readonly<Record<string, string>>;
|
|
354
404
|
}
|
|
405
|
+
export type { JudoMethod, JudoOutcome, JudoResult, JudoSide } from "./judo-model.ts";
|
|
355
406
|
export type ThemeName = "auto" | "light" | "dark" | "poster" | "none";
|
|
356
407
|
export type SizeName = "wide" | "square" | "phone" | "card";
|
|
357
408
|
export interface Size {
|
|
@@ -434,6 +485,8 @@ export interface DrawOptions extends LayoutOptions {
|
|
|
434
485
|
standings?: "points" | "record";
|
|
435
486
|
/** In the `groups` layout, how many teams of each group go through: their rows are marked. Default 0, none. */
|
|
436
487
|
qualify?: number;
|
|
488
|
+
/** For `bracketStyle`: include the judo rules (the white and blue side bars, the shido cards). `drawBracket` adds them by itself when a tie has judo scores. */
|
|
489
|
+
judo?: boolean;
|
|
437
490
|
/** Write the `<style>` into the drawing. Default true; false leaves it out, for a second drawing of a page that shares the first's (`styleScope`, `bracketStyle`). */
|
|
438
491
|
style?: boolean;
|
|
439
492
|
/** A class that carries this drawing's rules in place of its own: two drawings with the same `styleScope` take their colours from one `<style>`, and the second can leave it out (`style: false`). A word of letters, digits, `-` and `_`. */
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.8.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.8.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/taisen-brackets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|