Files
ffsaf-site/src/main/webapp/public/competition.js
Thibaut Valentin bf377c9c5e
Some checks failed
Deploy Production Server / if_merged (pull_request) Failing after 2m31s
feat: external website result connector
2025-12-29 21:03:05 +01:00

947 lines
35 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let apiUrlRoot = "";
const rootDiv = document.getElementById("safca_api_data");
const header = `<h4>Résultat de la compétition :</h4>`
const backButton = `<a onclick="setSubPage('home')" href="javascript:void(0);">Retour</a>`
const cupImg = `<img decoding="async" loading="lazy" width="16" height="16" class="wp-image-1635"
style="width: 16px;" src="https://intra.ffsaf.fr/img/171891.png"
alt="">`
const voidFunction = () => {
}
let lastRf = 0;
let rfFonction = voidFunction;
setInterval(() => {
rfFonction();
}, 15000);
function setSubPage(name) {
window.location.hash = name;
const location = name.split('/');
console.log(location);
switch (location[0]) {
case 'home':
homePage();
break;
case 'poule':
poulePage(location);
break;
case 'comb':
combPage(location);
break;
case 'club':
clubPage(location);
break;
case 'all':
combsPage();
break;
}
}
function homePage() {
rootDiv.innerHTML = header;
let content = document.createElement('div');
content.innerHTML = `
<ul>
<li><a onclick="setSubPage('poule')" href="javascript:void(0);">Par poule</a></li>
<li><a onclick="setSubPage('comb')" href="javascript:void(0);">Par combattant</a></li>
<li><a onclick="setSubPage('club')" href="javascript:void(0);">Par club</a></li>
<li><a onclick="setSubPage('all')" href="javascript:void(0);">Tous les combattants</a></li>
</ul>
`
rootDiv.append(content)
}
let loadingAnimationStep = 0;
function startLoading(root) {
const id = "loading" + Math.random().toString(36);
let element = document.createElement('h2');
element.id = id;
const anim = setInterval(() => {
let str = "Chargement";
for (let i = 0; i < loadingAnimationStep; i++) {
str += ".";
}
loadingAnimationStep = (loadingAnimationStep + 1) % 4;
element.innerText = str;
}, 500);
root.append(element)
return {interval: anim, root: root, element: element};
}
function stopLoading(loading) {
clearInterval(loading['interval']);
loading['root'].removeChild(loading['element']);
}
function scoreToString(score) {
const scorePrint = (s1) => {
switch (s1) {
case -997:
return "disc.";
case -998:
return "abs.";
case -999:
return "for.";
case -1000:
return "";
default:
return String(s1);
}
}
return score.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ");
}
function dateToString(date) {
if (date === null || date === undefined)
return "";
const date_ = new Date(date);
const date_2 = new Date(date);
const current = new Date();
current.setHours(0, 0, 0, 0);
date_2.setHours(0, 0, 0, 0);
let d = Math.floor((current - date_2) / (1000 * 60 * 60 * 24));
if (d === 0)
return "Aujourd'hui à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
else if (d === 1)
return "Hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
else if (d === 2)
return "Avant-hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
else
return date_.toLocaleDateString();
}
function buildPouleMenu(isPoule, change_view) {
const menuDiv = document.createElement('div');
menuDiv.id = 'menu';
menuDiv.style.borderBottom = '1px solid #9EA0A1';
menuDiv.style.paddingBottom = '25px';
const ul = document.createElement('ul');
ul.id = 'onglets';
ul.style.position = 'absolute';
ul.style.border = '1px solid transparent';
ul.style.padding = '0';
ul.style.font = 'bold 11px Batang, arial, serif';
ul.style.listStyleType = 'none';
ul.style.left = '50%';
ul.style.marginTop = '0';
ul.style.width = '430px';
ul.style.marginLeft = '-215px';
function createTab(text, isActive, onClickHandler) {
const li = document.createElement('li');
if (isActive) {
li.className = 'active';
li.style.borderBottom = '1px solid #fff';
li.style.backgroundColor = '#fff';
} else {
li.style.backgroundColor = '#F4F9FD';
}
li.style.float = 'left';
li.style.height = '21px';
li.style.margin = '2px 2px 0 2px !important';
li.style.margin = '1px 2px 0 2px';
li.style.border = '1px solid #9EA0A1';
const a = document.createElement('a');
a.href = 'javascript:void(0);';
a.onclick = onClickHandler;
a.textContent = text;
a.style.display = 'block';
a.style.color = '#666';
a.style.textDecoration = 'none';
a.style.padding = '4px';
a.addEventListener('mouseover', function () {
a.style.background = '#fff';
});
a.addEventListener('mouseout', function () {
if (!isActive)
a.style.background = '#F4F9FD';
});
li.appendChild(a);
return li;
}
const li1 = createTab('Poule', isPoule, function () {
change_view(true);
});
ul.appendChild(li1);
const li2 = createTab('Tournois', !isPoule, function () {
change_view(false);
});
ul.appendChild(li2);
menuDiv.appendChild(ul);
return menuDiv;
}
function buildMatchArray(matchs) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 800px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Rouge</th>
<th class="has-text-align-center" data-align="center"></th>
<th class="has-text-align-center" data-align="center">Scores</th>
<th class="has-text-align-center" data-align="center"></th>
<th class="has-text-align-center" data-align="center">Bleu</th>
<th class="has-text-align-center" data-align="center">Date</th>
</tr>
</thead><tbody>`
for (const match of matchs) {
arrayContent += `
<tr>
<td class="has-text-align-right" data-align="right">${match.red}</td>
<td class="has-text-align-center" data-align="center">${match.red_w ? cupImg : ""}</td>
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
<td class="has-text-align-center" data-align="center">${match.blue_w ? cupImg : ""}</td>
<td class="has-text-align-left" data-align="left">${match.blue}</td>
<td class="has-text-align-center" data-align="center">${dateToString((match.red_w || match.blue_w) ? match.date : null)}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
pouleDiv.innerHTML = arrayContent;
return pouleDiv;
}
function buildRankArray(rankArray) {
const arrayDiv = document.createElement('div');
let arrayContent = `<figure class="wp-block-table is-style-stripes" style="font-size: 16px; margin-top: 2em">
<table style="width: 600px;overflow: auto">
<thead>
<tr>
<th class="has-text-align-center" data-align="center">Place</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoire</th>
<th class="has-text-align-center" data-align="center">Ratio</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
</tr>
</thead><tbody>`
for (const row of rankArray) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${row.rank}</td>
<td class="has-text-align-left" data-align="left">${row.name}</td>
<td class="has-text-align-center" data-align="center">${row.win}</td>
<td class="has-text-align-center" data-align="center">${row.pointRate.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${row.pointMake}</td>
<td class="has-text-align-center" data-align="center">${row.pointTake}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
arrayDiv.innerHTML = arrayContent;
return arrayDiv;
}
function buildTree(treeData) {
return drawGraph(initTree(treeData))
}
function poulePage(location) {
rootDiv.innerHTML = header + backButton;
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par poule</h4>';
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
let currentPoule = 0;
const loadPoule = () => {
const loading = startLoading(content);
fetch(`${apiUrlRoot}/poule/data?poule=${currentPoule}&rf=${lastRf}`)
.then(response => {
if (response.status === 204) // No content => no update
return;
response.json().then(poule => {
lastRf = (poule.genTime !== undefined) ? poule.genTime : 0;
// console.log(poule);
if (location.length === 3 && poule.type < 3) {
location.pop();
window.location.hash = location.join('/');
}
dataContainer.replaceChildren();
if (poule.type === 1) {
for (const g in poule.matchs) {
if (Object.keys(poule.matchs).length > 1) {
const text = document.createElement('h4');
text.textContent = `Groupe ${g}`;
text.style.marginTop = '2em';
dataContainer.append(text);
}
dataContainer.append(buildMatchArray(poule.matchs[g]));
dataContainer.append(buildRankArray(poule.rankArray[g]));
}
} else if (poule.type === 2) {
dataContainer.append(buildTree(poule['trees']));
} else {
const change_view = (isPoule) => {
dataContainer.replaceChildren(buildPouleMenu(isPoule, change_view));
if (isPoule) {
for (const g in poule.matchs) {
if (Object.keys(poule.matchs).length > 1) {
const text = document.createElement('h4');
text.textContent = `Groupe ${g}`;
text.style.marginTop = '2em';
dataContainer.append(text);
}
dataContainer.append(buildMatchArray(poule.matchs[g]));
dataContainer.append(buildRankArray(poule.rankArray[g]));
}
} else {
dataContainer.append(buildTree(poule['trees']));
}
location[2] = isPoule ? 1 : 2;
window.location.hash = location.join('/');
}
change_view((location.length === 3) ? location[2] === '1' : true);
}
})
})
.catch(e => {
console.error(e);
dataContainer.replaceChildren(new Text("Erreur de chargement de la poule"));
})
.finally(() => stopLoading(loading));
}
const loading = startLoading(content);
fetch(`${apiUrlRoot}/poule/list`)
.then(response => response.json())
.then(poule => {
const select = document.createElement('select');
select.setAttribute('id', poule.id);
select.innerHTML = `<option value="0">--Sélectionner une poule--</option>`;
for (const pouleKey of Object.keys(poule).sort()) {
select.innerHTML += `<option value="${poule[pouleKey]}">${pouleKey}</option>`;
}
select.addEventListener('change', e => {
location[1] = Object.keys(poule).find(key => poule[key] === Number(e.target.value));
location[1] = encodeURI(location[1]);
window.location.hash = location.join('/');
lastRf = 0;
currentPoule = e.target.value;
loadPoule();
})
content.append(select);
content.appendChild(dataContainer);
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
const tmp = poule[decodeURI(location[1])];
select.value = tmp;
currentPoule = tmp
loadPoule();
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des poules")))
.finally(() => stopLoading(loading));
rfFonction = () => {
if (currentPoule !== 0)
loadPoule();
}
rootDiv.append(content)
}
function buildCombView(comb) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>Info :</h3>
<ul>
<li>Nom Pr&eacute;nom : ${comb.name}</li>
<li>Club : ${comb.club}</li>
<li>Cat&eacute;gorie : ${comb.cat}</li>
</ul>
<h3>Statistique :</h3>
<ul>
<li>Taux de victoire : ${comb.matchs.length === 0 ? "---" : (comb.totalWin / comb.matchs.length * 100).toFixed(0)}% (${comb.totalWin} sur ${comb.matchs.length})</li>
<li>Points marqués : ${comb.pointMake}</li>
<li>Points reçus : ${comb.pointTake}</li>
<li>Ratio du score (point marqu&eacute; / point re&ccedil;u): ${comb.pointRatio.toFixed(3)}</li>
</ul>
<h3>Liste des matchs:</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 700px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Date</th>
<th class="has-text-align-center" data-align="center">Poule</th>
<th class="has-text-align-center" data-align="center">Adversaire</th>
<th class="has-text-align-center" data-align="center">Scores</th>
<th class="has-text-align-center" data-align="center">Ratio</th>
<th class="has-text-align-center" data-align="center"></th>
</tr>
</thead><tbody>`
for (const match of comb.matchs) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${dateToString(match.date)}</td>
<td class="has-text-align-center" data-align="center">${match.poule}</td>
<td class="has-text-align-center" data-align="center">${match.adv}</td>
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
<td class="has-text-align-center" data-align="center">${match.ratio.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${match.win ? cupImg : ""}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
pouleDiv.innerHTML = arrayContent;
return pouleDiv;
}
function combPage(location) {
rootDiv.innerHTML = header + backButton;
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par combattant</h4>';
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
const loadComb = (id) => {
const loading = startLoading(content);
fetch(`${apiUrlRoot}/comb/data?comb=${id}`)
.then(response => response.json())
.then(comb => {
console.log(comb);
dataContainer.replaceChildren(buildCombView(comb));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du combattant")))
.finally(() => stopLoading(loading));
}
const loading = startLoading(content);
fetch(`${apiUrlRoot}/comb/list`)
.then(response => response.json())
.then(combs => {
const select = document.createElement('select');
select.innerHTML = `<option value="0">--Sélectionner un combattant--</option>`;
for (const comb of Object.keys(combs).sort()) {
select.innerHTML += `<option value="${combs[comb]}">${comb}</option>`;
}
select.addEventListener('change', e => {
location[1] = Object.keys(combs).find(key => combs[key] === e.target.value);
location[1] = encodeURI(location[1]);
window.location.hash = location.join('/');
loadComb(e.target.value);
})
content.append(select);
content.appendChild(dataContainer);
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
const tmp = combs[decodeURI(location[1])];
select.value = tmp;
loadComb(tmp);
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des combattants")))
.finally(() => stopLoading(loading));
rootDiv.append(content)
}
function buildClubView(club) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>Info :</h3>
<ul>
<li>Nom : ${club.name}</li>
<li>Nombre d'inscris : ${club.nb_insc}</li>
</ul>
<h3>Statistique :</h3>
<ul>
<li>Nombre de match disput&eacute; : ${club.nb_match}</li>
<li>Nombre de victoires : ${club.match_w}</li>
<li>Ratio de victoires moyen : ${club.ratioVictoire.toFixed(3)}</li>
<li>Points marqués : ${club.pointMake}</li>
<li>Points reçus : ${club.pointTake}</li>
<li>Ratio de points moyen : ${club.ratioPoint.toFixed(3)}</li>
</ul>
<h3>Liste des menbres :</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 800px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Cat&eacute;gorie</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoires</th>
<th class="has-text-align-center" data-align="center">D&eacute;faites</th>
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
<th class="has-text-align-center" data-align="center">Ratio points</th>
</tr>
</thead><tbody>`
for (const comb of club.combs) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${comb.cat}</td>
<td class="has-text-align-center" data-align="center">${comb.name}</td>
<td class="has-text-align-center" data-align="center">${comb.w}</td>
<td class="has-text-align-center" data-align="center">${comb.l}</td>
<td class="has-text-align-center" data-align="center">${comb.ratioVictoire.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${comb.pointMake}</td>
<td class="has-text-align-center" data-align="center">${comb.pointTake}</td>
<td class="has-text-align-center" data-align="center">${comb.ratioPoint.toFixed(3)}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
pouleDiv.innerHTML = arrayContent;
return pouleDiv;
}
function clubPage(location) {
rootDiv.innerHTML = header + backButton;
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par club</h4>';
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
const loadComb = (id) => {
const loading = startLoading(content);
fetch(`${apiUrlRoot}/club/data?club=${id}`)
.then(response => response.json())
.then(club => {
console.log(club);
dataContainer.replaceChildren(buildClubView(club));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du club")))
.finally(() => stopLoading(loading));
}
const loading = startLoading(content);
fetch(`${apiUrlRoot}/club/list`)
.then(response => response.json())
.then(clubs => {
const select = document.createElement('select');
select.innerHTML = `<option value="0">--Sélectionner un club--</option>`;
for (const club of Object.keys(clubs).sort()) {
select.innerHTML += `<option value="${clubs[club]}">${club}</option>`;
}
select.addEventListener('change', e => {
if (e.target.value === '0')
return;
location[1] = Object.keys(clubs).find(key => clubs[key] === Number(e.target.value));
location[1] = encodeURI(location[1]);
window.location.hash = location.join('/');
loadComb(e.target.value);
})
content.append(select);
content.appendChild(dataContainer);
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
const tmp = clubs[decodeURI(location[1])];
select.value = tmp;
loadComb(tmp);
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des clubs")))
.finally(() => stopLoading(loading));
rootDiv.append(content)
}
function buildCombsView(combs) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>Statistique :</h3>
<ul>
<li>Nombre d'inscris : ${combs.nb_insc}</li>
<li>Nombre de match disput&eacute; : ${combs.tt_match}</li>
<li>Points marqués : ${combs.point}</li>
</ul>
<h3>Liste des combattants :</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 1200px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Cat&eacute;gorie</th>
<th class="has-text-align-center" data-align="center">Club</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoires</th>
<th class="has-text-align-center" data-align="center">D&eacute;faites</th>
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
<th class="has-text-align-center" data-align="center">Ratios points</th>
</tr>
</thead><tbody>`
for (const comb of combs.combs) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${comb.cat}</td>
<td class="has-text-align-center" data-align="center">${comb.club}</td>
<td class="has-text-align-center" data-align="center">${comb.name}</td>
<td class="has-text-align-center" data-align="center">${comb.w}</td>
<td class="has-text-align-center" data-align="center">${comb.l}</td>
<td class="has-text-align-center" data-align="center">${comb.ratioVictoire.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${comb.pointMake}</td>
<td class="has-text-align-center" data-align="center">${comb.pointTake}</td>
<td class="has-text-align-center" data-align="center">${comb.ratioPoint.toFixed(3)}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
pouleDiv.innerHTML = arrayContent;
return pouleDiv;
}
function combsPage() {
rootDiv.innerHTML = header + backButton;
const content = document.createElement('div');
content.style.marginTop = '1em';
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
const loading = startLoading(content);
fetch(`${apiUrlRoot}/comb/get_all`)
.then(response => response.json())
.then(combs => {
console.log(combs);
dataContainer.replaceChildren(buildCombsView(combs));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement de la liste")))
.finally(() => stopLoading(loading));
content.append(dataContainer);
rootDiv.append(content)
}
window.addEventListener("load", () => {
let path = document.getElementById('safca_api_script').src;
const urlParams = new URLSearchParams(new URL(path).search);
apiUrlRoot = path.substring(0, path.lastIndexOf('/')) + "/api/public/result/" + urlParams.get("id");
console.log("apiUrlRoot:", apiUrlRoot)
let hash = window.location.hash.substring(1);
if (hash.length === 0)
setSubPage('home');
else
setSubPage(hash);
});
class TreeNode {
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
}
death() {
let dg = 0;
let dd = 0;
if (this.right != null)
dg = this.right.death();
if (this.left != null)
dg = this.left.death();
return 1 + Math.max(dg, dd);
}
}
function initTree(data_in) {
out = [];
for (const din of data_in) {
out.push(parseTree(din));
}
return out;
}
function parseTree(data_in) {
if (data_in?.data == null)
return null;
let node = new TreeNode(data_in.data);
node.left = parseTree(data_in?.left);
node.right = parseTree(data_in?.right);
return node;
}
const max_x = 500;
const size = 24;
function getBounds(root) {
let px = max_x;
let py;
let maxx, minx, miny, maxy
function drawNode(tree, px, py) {
let death = tree.death() - 1
if (death === 0) {
if (miny > py - size - ((size * 1.5 / 2) | 0)) miny = py - size - (size * 1.5 / 2) | 0;
if (maxy < py + size + ((size * 1.5 / 2) | 0)) maxy = py + size + (size * 1.5 / 2) | 0;
} else {
if (miny > py - size * 2 * death - ((size * 1.5 / 2) | 0))
miny = py - size * 2 * death - ((size * 1.5 / 2) | 0);
if (maxy < py + size * 2 * death + ((size * 1.5 / 2) | 0))
maxy = py + size * 2 * death + ((size * 1.5 / 2) | 0);
}
if (minx > px - size * 2 - size * 8) minx = px - size * 2 - size * 8;
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - size * 2 * death);
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
}
if (root != null) {
py = (size * 2 * root.at(0).death() + (((size * 1.5 / 2) | 0) + size) * root.at(0).death()) * 2;
maxx = px;
minx = px;
miny = py - (size * 1.5 / 2) | 0;
maxy = py + (size * 1.5 / 2) | 0;
for (const node of root) {
px = px - size * 2 - size * 8;
if (minx > px) minx = px;
drawNode(node, px, py);
//graphics2D.drawRect(minx, miny, maxx - minx, maxy - miny);
py = maxy + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
px = maxx;
}
} else {
minx = 0;
maxx = 0;
miny = 0;
maxy = 0;
}
return [minx, maxx, miny, maxy];
}
function drawGraph(root = []) {
const canvas = document.createElement('canvas');
canvas.id = "myCanvas";
canvas.style.border = "1px solid grey";
canvas.style.marginTop = "10px";
const ctx = canvas.getContext("2d");
const [minx, maxx, miny, maxy] = getBounds(root);
canvas.width = maxx - minx;
canvas.height = maxy - miny;
ctx.translate(-minx, -miny);
ctx.fillStyle = "#000000"
ctx.lineWidth = 2
ctx.strokeStyle = "#000000"
function printText(s, x, y, width, height, lineG, lineD) {
ctx.save();
ctx.translate(x, y);
let tSize = 17;
let ratioX = height * 1. / 20.;
ctx.font = "100 " + tSize + "px Arial";
let mw = width - (ratioX * 2) | 0;
if (ctx.measureText(s).width > mw) {
let dTextSize = true;
do {
tSize--;
ctx.font = tSize + "px Arial";
} while (ctx.measureText(s).width > mw && tSize > 10)
if (!dTextSize || ctx.measureText(s).width > mw) {
let s = "";
for (const string2 in s.split(" ")) {
if (ctx.measureText(s + string2).width >= mw) {
s += "...";
break;
} else {
s += string2 + " "
}
}
}
}
const text = ctx.measureText(s)
let dx = (width - text.width) / 2;
let dy = ((height - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
ctx.fillText(s, dx, dy, width - dy);
ctx.restore();
ctx.beginPath();
if (lineD) {
ctx.moveTo((ratioX * 2.5 + x + dx + text.width) | 0, y + height / 2);
ctx.lineTo(x + width, y + height / 2)
}
if (lineG) {
ctx.moveTo(x, y + height / 2);
ctx.lineTo((dx + x - ratioX * 2.5) | 0, y + height / 2)
}
ctx.stroke();
}
function printScores(scores, px, py, scale) {
ctx.save();
ctx.translate(px - size * 2, py - size * scale);
ctx.font = "100 " + 14 + "px Arial";
ctx.textBaseline = 'top';
for (let i = 0; i < scores.length; i++) {
const score = scores[i].s1+"-"+scores[i].s2;
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
const text = ctx.measureText(score);
let dx = (size * 2 - text.width) / 2;
let dy = ((size * 2 / div - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
ctx.fillStyle = '#ffffffdd';
ctx.fillRect(dx, size * 2 * scale / div * (i) + dy, text.width, 14);
ctx.fillStyle = "#000000";
ctx.fillText(score, dx, size * 2 * scale / div * (i) + dy, size * 2);
}
ctx.restore();
}
function drawNode(tree, px, py) {
ctx.beginPath()
ctx.moveTo(px, py)
ctx.lineTo(px - size, py)
ctx.stroke();
let death = tree.death() - 1
let match = tree.data
if (death === 0) {
ctx.beginPath()
ctx.moveTo(px - size, py + size)
ctx.lineTo(px - size, py - size)
ctx.moveTo(px - size, py + size)
ctx.lineTo(px - size * 2, py + size)
ctx.moveTo(px - size, py - size)
ctx.lineTo(px - size * 2, py - size)
ctx.stroke();
printScores(match.scores, px, py, 1);
ctx.fillStyle = "#FF0000";
printText((match.c1FullName == null) ? "" : match.c1FullName, px - size * 2 - size * 8, py - size - (size * 1.5 / 2) | 0,
size * 8, (size * 1.5) | 0, false, true)
ctx.fillStyle = "#0000FF";
printText((match.c2FullName == null) ? "" : match.c2FullName, px - size * 2 - size * 8, py + size - (size * 1.5 / 2) | 0,
size * 8, (size * 1.5) | 0, false, true)
if (max_y < py + size + ((size * 1.5 / 2) | 0)) max_y = py + size + (size * 1.5 / 2) | 0;
} else {
ctx.beginPath()
ctx.moveTo(px - size, py)
ctx.lineTo(px - size, py + size * 2 * death)
ctx.moveTo(px - size, py)
ctx.lineTo(px - size, py - size * 2 * death)
ctx.moveTo(px - size, py + size * 2 * death)
ctx.lineTo(px - size * 2, py + size * 2 * death)
ctx.moveTo(px - size, py - size * 2 * death)
ctx.lineTo(px - size * 2, py - size * 2 * death)
ctx.stroke();
printScores(match.scores, px, py, 1.5);
ctx.fillStyle = "#FF0000";
printText((match.c1FullName == null) ? "" : match.c1FullName, px - size * 2 - size * 8, py - size * 2 * death - (size * 1.5 / 2) | 0,
size * 8, (size * 1.5) | 0, true, true)
ctx.fillStyle = "#0000FF";
printText((match.c2FullName == null) ? "" : match.c2FullName, px - size * 2 - size * 8, py + size * 2 * death - (size * 1.5 / 2) | 0,
size * 8, (size * 1.5) | 0, true, true)
if (max_y < py + size * 2 * death + ((size * 1.5 / 2) | 0))
max_y = py + size * 2 * death + ((size * 1.5 / 2) | 0);
}
ctx.stroke();
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - size * 2 * death);
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
}
function win(scores) {
let sum = 0;
for (const score of scores) {
if (score.s1 === -1000 || score.s2 === -1000)
continue;
if (score.s1 > score.s2)
sum++;
else if (score.s1 < score.s2)
sum--;
}
return sum;
}
let px = max_x;
let py;
let max_y
if (root != null) {
py = (size * 2 * root.at(0).death() + (((size * 1.5 / 2) | 0) + size) * root.at(0).death()) * 2;
max_y = py + (size * 1.5 / 2) | 0;
for (const node of root) {
let win_name = "";
if (node.data.end) {
if (win(node.data.scores) > 0)
win_name = (node.data.c1FullName === null) ? "???" : node.data.c1FullName;
else
win_name = (node.data.c2FullName === null) ? "???" : node.data.c2FullName;
}
ctx.fillStyle = "#18A918";
printText(win_name, px - size * 2 - size * 8, py - ((size * 1.5 / 2) | 0),
size * 8, (size * 1.5) | 0, true, false);
px = px - size * 2 - size * 8;
drawNode(node, px, py);
py = max_y + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
px = max_x;
}
}
return canvas;
}