/* autogenerated output of all modules in the /analysis/modules folder */
//Bar
class Bar {
container;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container bar");
}
formatData(teams, dataset) {
const values = this.moduleConfig.options.bars.map((bar) => {
const summed = teams
.map((team) => getPath(dataset.teams[team], bar.path))
.flat()
.reduce((acc, i) => acc + i, 0);
if (bar.aggrMethod == "sum") {
//optionally summed
return summed;
} else {
//default is average
return summed / teams.length;
}
});
const data = [
{
x: this.moduleConfig.options.bars.map((bar) => bar.name),
y: values,
type: "bar",
marker: {
color: "#30a2ff",
},
},
];
return data;
}
setData(data) {
const layout = {
margin: {
pad: 12,
},
title: {
text: this.moduleConfig.name,
font: {
size: 32,
// color: "#ff6030"
},
yanchor: "middle",
},
xaxis: {
tickfont: {
size: 20,
},
},
yaxis: {
tickfont: {
size: 16,
},
},
font: {
family: "Cairo, sans-serif",
},
paper_bgcolor: "#FEFEFE",
plot_bgfcolor: "#FEFEFE",
};
const config = {
responsive: true,
modeBarButtonsToRemove: ["zoom2d", "pan2d", ""],
};
Plotly.purge(this.container);
Plotly.newPlot(this.container, data, layout, config);
}
}
//BubbleSheetGraph
class BubbleSheetGraph {}
//ColumnDisplay
class ColumnDisplay {
container;
header;
displaysContainer;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container column-display");
this.header = createDOMElement("div", "header");
this.displaysContainer = createDOMElement("div", "displays-container");
this.container.appendChild(this.header);
this.container.appendChild(this.displaysContainer);
}
formatData(teams, dataset) {
const data = [];
for (const team of teams) {
let formattedDisplay = getPath(
dataset.teams[team],
this.moduleConfig.options.path,
this.moduleConfig.options.string ? "—" : 0
);
formattedDisplay = this.applyModifiers(formattedDisplay);
let statRank;
let totalRanked;
if (
!this.moduleConfig.options.string &&
(isNaN(formattedDisplay) ||
formattedDisplay == this.moduleConfig.options.hideIfValue)
) {
formattedDisplay = "—";
} else {
if (
this.moduleConfig.options.sort !== 0 &&
this.moduleConfig.options.sort !== undefined
) {
const filteredTeams = Object.keys(dataset.teams).filter((team) => {
let teamStatToFilter = getPath(
dataset.teams[team],
this.moduleConfig.options.path,
0
);
teamStatToFilter = this.applyModifiers(teamStatToFilter);
return (
!isNaN(teamStatToFilter) &&
teamStatToFilter != this.moduleConfig.options.hideIfValue
);
});
totalRanked = filteredTeams.length;
const rankedTeams = filteredTeams.sort(
(a, b) =>
(this.applyModifiers(
getPath(dataset.teams[b], this.moduleConfig.options.path, 0)
) -
this.applyModifiers(
getPath(dataset.teams[a], this.moduleConfig.options.path, 0)
)) *
this.moduleConfig.options.sort
);
statRank = rankedTeams.indexOf(team) + 1;
}
if (this.moduleConfig.options.decimals !== undefined) {
formattedDisplay = formattedDisplay.toFixed(
this.moduleConfig.options.decimals
);
}
if (this.moduleConfig.options.unit) {
formattedDisplay += this.moduleConfig.options.unit;
}
}
data.push({
team: team,
value: formattedDisplay,
rank: statRank,
totalRanked: totalRanked,
});
}
return data;
}
applyModifiers(value) {
if (this.moduleConfig.options.multiplier !== undefined) {
value *= this.moduleConfig.options.multiplier;
}
if (this.moduleConfig.options.addend !== undefined) {
value += this.moduleConfig.options.addend;
}
return value;
}
setData(data) {
this.header.innerText = this.moduleConfig.name;
clearDiv(this.displaysContainer);
for (const teamData of data) {
const teamDisplayContainer = createDOMElement(
"div",
"team-display-container"
);
const teamValue = createDOMElement("div", "team-value");
teamValue.innerText = teamData.value;
const teamNum = createDOMElement("div", "team-num");
teamNum.innerText = teamData.team;
teamDisplayContainer.appendChild(teamValue);
teamDisplayContainer.appendChild(teamNum);
if (teamData.rank) {
let rankClass = "top100";
if (teamData.rank <= Math.round(teamData.totalRanked * 0.05)) {
rankClass = "top5";
} else if (teamData.rank <= Math.round(teamData.totalRanked * 0.25)) {
rankClass = "top25";
} else if (teamData.rank <= Math.round(teamData.totalRanked * 0.5)) {
rankClass = "top50";
}
const teamRank = createDOMElement("div", `team-rank ${rankClass}`);
teamRank.innerText = `#${teamData.rank}`;
teamDisplayContainer.appendChild(teamRank);
}
this.displaysContainer.appendChild(teamDisplayContainer);
}
}
}
//Divider
class Divider {
container;
title;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container divider");
this.title = createDOMElement("div", "title");
this.container.appendChild(this.title);
}
async formatData() {}
async setData() {
this.title.innerText = this.moduleConfig.name;
}
}
//Grid
class Grid {
container;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container grid");
this.container.innerHTML =
'
No Team Selected
';
}
formatData(teams, dataset) {
const data = {};
data.title = this.moduleConfig.name;
data.cols = this.moduleConfig.options.cols;
data.rows = this.moduleConfig.options.rows;
let mapped = this.moduleConfig.options.cells.map((cell) => {
let newObj = {
x: cell.x,
y: cell.y,
data: teams
.map((team) => {
let data = getPath(dataset.teams[team], cell.path, 0).toFixed(
this.moduleConfig.options.decimals
);
return data;
})
.reduce((acc, i) => acc + parseFloat(i), 0)
.toFixed(this.moduleConfig.options.decimals),
hex: cell.hex,
};
return newObj;
});
data.cells = mapped;
return data;
}
/*
*/
setData(data) {
this.container.innerHTML = "";
var title = createDOMElement("div", "header");
title.innerHTML = data.title;
this.container.appendChild(title);
var gridContainer = createDOMElement("div", "table");
gridContainer.style.display = "grid";
gridContainer.style.gridTemplateRows = `repeat(${data.rows},1fr)`;
gridContainer.style.gridTemplateColumns = `repeat(${data.cols},1fr)`;
this.container.appendChild(gridContainer);
var slope =
225 / (this.moduleConfig.options.max - this.moduleConfig.options.min);
for (let cell of data.cells) {
var opacity = Math.min(
30 + slope * (cell.data - this.moduleConfig.options.min),
255
);
let newHex = cell.hex + Math.floor(opacity).toString(16);
var cellDiv = createDOMElement("div", "cell");
cellDiv.style.backgroundColor = newHex;
cellDiv.innerHTML = cell.data;
cellDiv.style.gridArea = `${cell.y}/${cell.x}/${cell.y + 1}/${
cell.x + 1
}`;
gridContainer.appendChild(cellDiv);
}
}
}
//HeatmapScatterPlot
class HeatmapScatterPlot {
container;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container plot");
this.switcher = createDOMElement("div", "switcher");
}
async formatData(teams, dataset) {
let actionGroups = this.moduleConfig.options.actionGroups;
let actions = actionGroups.reduce((acc, action) => {
acc.push(...action.actions);
return acc;
}, []);
const data = actions.reduce((acc, actionId) => {
const filteredActionQueue = teams
.map((team) =>
getPath(
dataset.teams[team],
this.moduleConfig.options.aggregatedActionsPath
)
)
.flat()
.filter((a) => a.id == actionId);
// console.log(this.moduleConfig.options.actionLabels[actionId])
if (filteredActionQueue.length) {
acc.push({
mode: "markers",
type: "scatter",
showlegend: true,
name: this.moduleConfig.options.actionLabels[actionId],
x: filteredActionQueue.map(
(a) => getPath(a, this.moduleConfig.options.coordinatePath).x
),
y: filteredActionQueue.map(
(a) => getPath(a, this.moduleConfig.options.coordinatePath).y
),
marker: {
size: 16,
line: {
color: "white",
width: 1,
},
},
opacity: 0.6,
});
}
return acc;
}, []);
// console.log(data)
const filteredAllActionQueue = teams
.map((team) =>
getPath(
dataset.teams[team],
this.moduleConfig.options.aggregatedActionsPath
)
)
.flat()
.filter((a) => actionGroups[0].actions.includes(a.id));
data.push({
type: "histogram2dcontour",
name: "Heatmap",
showlegend: true,
x: filteredAllActionQueue.map(
(a) => getPath(a, this.moduleConfig.options.coordinatePath).x
),
y: filteredAllActionQueue.map(
(a) => getPath(a, this.moduleConfig.options.coordinatePath).y
),
xaxis: "x",
yaxis: "y",
opacity: 0.7,
nbinsx: 10,
nbinsy: 10,
// xbins: {
// start: 0,
// size: 5,
// end: 100
// },
// ybins: {
// start: 0,
// size: 5,
// end: 100
// },
zmin: 0,
showscale: false,
colorscale: [
[0, "rgba(255,255,255,0)"],
[0.1, "rgba(255, 147, 115, 50)"],
[1, "rgba(255, 59, 0, 255)"],
],
});
return data;
}
async setData(data) {
const fieldImg = await getSvgDataPng(
window.location.href + this.moduleConfig.options.imgPath
);
const layout = {
dragmode: false,
autosize: true,
margin: {
pad: 12,
// b: 40
},
title: {
text: this.moduleConfig.name,
font: {
size: 32,
// color: "#ff6030"
},
yanchor: "middle",
},
legend: {
font: {
size: 20,
},
orientation: "h",
itemsizing: "trace",
y: -3,
},
xaxis: {
range: [0, 100],
showgrid: false,
showticklabels: false,
zeroline: false,
showline: false,
},
yaxis: {
range: [100, 0],
showticklabels: false,
showgrid: false,
scaleanchor: "x",
scaleratio: fieldImg.h / fieldImg.w,
zeroline: false,
showline: false,
},
font: {
family: "Cairo, sans-serif",
},
paper_bgcolor: "#FEFEFE",
plot_bgcolor: "#FEFEFE",
images: [
{
source: fieldImg.src,
xref: "x",
yref: "y",
x: 0,
y: 0,
sizex: 100,
sizey: 100,
sizing: "fill",
opacity: 1,
layer: "below",
},
],
};
const config = {
responsive: true,
showAxisDragHandles: false,
modeBarButtonsToRemove: ["zoom2d", "pan2d"],
};
Plotly.purge(this.container);
Plotly.newPlot(this.container, data, layout, config);
}
}
async function getSvgDataPng(url) {
const img = document.createElement("img");
img.src = url;
return new Promise((r) => {
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width * 4;
canvas.height = img.height * 4;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width * 4, img.height * 4);
r({ src: canvas.toDataURL(), w: img.width, h: img.height });
};
});
}
//PerformanceTimePlot
class PerformanceTimePlot {
container;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container line");
//this.container.innerHTML = 'No Team Selected
'
}
async formatData(teams, dataset) {
let trackedStats = this.moduleConfig.options.trackedStats;
if (teams.length > 1)
throw new Error("Can't track multiple multiple teams!");
let teamTmps = dataset.tmps.filter((x) => x.robotNumber == teams[0]);
let trackedStatTraces = {};
for (let stat of trackedStats) {
trackedStatTraces[stat] = {
name: stat,
x: [...Array(teamTmps.length).keys()],
y: [],
mode: "lines+markers",
};
}
for (let tmp of teamTmps) {
for (let stat of trackedStats) {
trackedStatTraces[stat].y.push(getPath(tmp, stat));
}
}
return Object.values(trackedStatTraces);
}
async setData(data) {
const layout = {
dragmode: false,
autosize: true,
margin: {
pad: 12,
// b: 40
},
title: {
text: this.moduleConfig.name,
font: {
size: 32,
// color: "#ff6030"
},
yanchor: "middle",
},
legend: {
font: {
size: 20,
},
orientation: "h",
itemsizing: "trace",
y: -3,
},
font: {
family: "Cairo, sans-serif",
},
paper_bgcolor: "#FEFEFE",
plot_bgcolor: "#FEFEFE",
};
const config = {
responsive: true,
showAxisDragHandles: false,
modeBarButtonsToRemove: ["zoom2d", "pan2d"],
};
Plotly.purge(this.container);
Plotly.newPlot(this.container, data, layout, config);
}
}
async function getSvgDataPng(url) {
const img = document.createElement("img");
img.src = url;
return new Promise((r) => {
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width * 4;
canvas.height = img.height * 4;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width * 4, img.height * 4);
r({ src: canvas.toDataURL(), w: img.width, h: img.height });
};
});
}
//Pie
class Pie {
container;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container pie");
//this.container.innerHTML = 'No Team Selected
'
}
formatData(teams, dataset) {
console.log(`pie teams recieved: ${teams}`);
let filteredTeams = teams.filter((team) => team != "|");
const values = this.moduleConfig.options.slices.map((slice) => {
const summed = filteredTeams
.map((team) => {
let data = getPath(dataset.teams[team], slice.path);
console.log(`${slice.path}: ${data}`);
return data;
})
.flat()
.reduce((acc, i) => acc + i, 0);
if (slice.aggrMethod == "sum") {
//optionally summed
return summed;
} else {
//default is average
return (summed / teams.length).toFixed(2);
}
});
const data = [
{
labels: this.moduleConfig.options.slices.map((slice) => slice.name),
values: values.map((value) => Math.max(0, value)),
type: "pie",
hole: 0.4,
textfont: {
size: 20,
},
textinfo: "value",
textposition: "inside",
},
];
return data;
}
setData(data) {
const layout = {
margin: {
pad: 12,
b: 30,
},
title: {
text: this.moduleConfig.name,
font: {
size: 32,
// color: "#ff6030"
},
yanchor: "middle",
},
legend: {
font: {
size: 20,
},
x: 0.85,
},
font: {
family: "Cairo, sans-serif",
},
paper_bgcolor: "#FEFEFE",
plot_bgfcolor: "#FEFEFE",
};
const config = {
responsive: true,
modeBarButtonsToRemove: ["zoom2d", "pan2d", ""],
};
Plotly.purge(this.container);
Plotly.newPlot(this.container, data, layout, config);
}
}
//SingleDisplay
class SingleDisplay {
container;
header;
display;
moduleConfig;
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container single-display");
this.header = createDOMElement("div", "header");
this.display = createDOMElement("div", "display");
this.container.appendChild(this.header);
this.container.appendChild(this.display);
}
formatData(teams, dataset) {
// teams = [b1,b2,b3,|,r1,r3,r3,|]
let teamsArray = teams;
let summed;
let formattedDisplay;
if (this.moduleConfig.wholeMatch) {
let indexOfPipe = teamsArray.indexOf("|");
let alliance1 = teamsArray.slice(0, indexOfPipe);
// alliance 1 = [b1,b2,b3]
let alliance2 = teamsArray.slice(indexOfPipe + 1, teamsArray.length);
alliance2 = alliance2.filter((team) => team != "|");
// alliance 2 = [r1,r2,r3]
if (this.moduleConfig.options.aggrMethod == "percentChanceOfWinning") {
//optionally percent chance of winning
formattedDisplay = this.compareAlliances(alliance1, alliance2, dataset);
formattedDisplay = (formattedDisplay * 100).toFixed(2).toString() + "%";
} else {
//default is undefined
formattedDisplay = "0%";
}
} else {
if (teams.length > 1) {
summed = teams
.map((team) =>
getPath(dataset.teams[team], this.moduleConfig.options.path, 0)
)
.flat()
.reduce((acc, i) => acc + i, 0);
} else {
summed = getPath(
dataset.teams[teams[0]],
this.moduleConfig.options.path,
0
);
}
if (this.moduleConfig.options.aggrMethod == "sum") {
//optionally summed
formattedDisplay = summed;
} else {
//default is average
formattedDisplay = summed / teams.length;
}
}
formattedDisplay = this.applyModifiers(formattedDisplay);
if (
isNaN(formattedDisplay) ||
formattedDisplay == this.moduleConfig.options.hideIfValue
) {
if (!this.moduleConfig.wholeMatch) {
formattedDisplay = "—";
}
} else {
if (this.moduleConfig.options.decimals !== undefined) {
formattedDisplay = formattedDisplay.toFixed(
this.moduleConfig.options.decimals
);
}
if (this.moduleConfig.options.unit) {
formattedDisplay += this.moduleConfig.options.unit;
}
}
return formattedDisplay;
}
applyModifiers(value) {
if (this.moduleConfig.options.multiplier !== undefined) {
value *= this.moduleConfig.options.multiplier;
}
if (this.moduleConfig.options.addend !== undefined) {
value += this.moduleConfig.options.addend;
}
return value;
}
setData(data) {
this.header.innerText = this.moduleConfig.name;
this.display.innerText = data;
}
/**
*
* @param {*an allaicne of any length*} alliance1
* @param {*an alliance to compare to of any length*} alliance2
* @param {*the data set that holds the infomation of the teams*} dataset
* @returns {*the avg difference in score between allaicne 1 and allaicne 2*}
*/
matchAverage(alliance1, alliance2, dataset) {
let alliance1Avg = 0;
for (const a of alliance1) {
alliance1Avg += getPath(dataset.teams[a], "averageScores.total", 0);
}
let alliance2Avg = 0;
for (const a of alliance2) {
alliance2Avg += getPath(dataset.teams[a], "averageScores.total", 0);
}
return alliance1Avg - alliance2Avg;
}
/**
*
* @param {*an allaicne of any length*} alliance1
* @param {*an alliance to compare to of any length*} alliance2
* @param {*the data set that holds the infomation of the teams*} dataset
* @returns {*the standard deveation of the given match*}
*/
matchStandardDeviation(alliance1, alliance2, dataset) {
let alliance1SD = 0;
for (const a of alliance1) {
let data = getPath(dataset.teams[a], "standardDeviation", 0);
alliance1SD += Math.pow(data, 2);
}
alliance1SD = Math.sqrt(alliance1SD);
let alliance2SD = 0;
for (const a of alliance2) {
let data = getPath(dataset.teams[a], "standardDeviation", 0);
alliance2SD += Math.pow(data, 2);
}
alliance2SD = Math.sqrt(alliance2SD);
let zscore = Math.sqrt(Math.pow(alliance1SD, 2) + Math.pow(alliance2SD, 2));
if (zscore < 0.01) {
zscore = 0.01;
}
return zscore;
}
/**
*
* @param {*an allaicne of any length*} alliance1
* @param {*an alliance to compare to of any length*} alliance2
* @param {*the data set that holds the infomation of the teams*} dataset
* @returns {*the percent chance that alliance1 will win this match*}
*/
compareAlliances(alliance1, alliance2, dataset) {
let zscore = ss.zScore(
0,
this.matchAverage(alliance1, alliance2, dataset),
this.matchStandardDeviation(alliance1, alliance2, dataset)
);
let probAlliance2Wins = ss.cumulativeStdNormalProbability(zscore);
return 1 - probAlliance2Wins;
}
}
//Stats
class Stats {
container;
header;
list;
moduleConfig;
autoStats;
hasValue;
// There is probably a function that doesn't change anything if if can't find the object
// Find that function and set the stats box to only say "NA"
constructor(moduleConfig) {
this.moduleConfig = moduleConfig;
this.container = createDOMElement("div", "container stats");
this.header = createDOMElement("div", "header");
this.list = createDOMElement("div", "list");
this.container.appendChild(this.header);
this.container.appendChild(this.list);
this.header.innerHTML = "No Team Selected
";
}
formatData(teams, dataset) {
const data = [];
for (const stat of this.moduleConfig.options.list) {
let hasValue = true; // if each individual stat has a value
let formattedStat;
let summed;
if (teams.length > 1) {
summed = teams
.map((team) => getPath(dataset.teams[team], stat.path, 0))
.flat()
.reduce((acc, i) => acc + i, 0);
} else {
summed = getPath(dataset.teams[teams[0]], stat.path, 0);
}
if (stat.aggrMethod == "sum") {
//optionally summed
formattedStat = summed;
} else {
//default is average
formattedStat = summed / teams.length;
}
formattedStat = this.applyModifiers(stat, formattedStat);
let statRank;
let totalRanked;
if (isNaN(formattedStat) || formattedStat == stat.hideIfValue) {
formattedStat = "—";
hasValue = false;
} else {
if (stat.sort !== 0 && stat.sort !== undefined && teams.length == 1) {
const filteredTeams = Object.keys(dataset.teams).filter((team) => {
let teamStatToFilter = getPath(dataset.teams[team], stat.path, 0);
teamStatToFilter = this.applyModifiers(stat, teamStatToFilter);
return (
!isNaN(teamStatToFilter) && teamStatToFilter != stat.hideIfValue
);
});
totalRanked = filteredTeams.length;
const rankedTeams = filteredTeams.sort(
(a, b) =>
(this.applyModifiers(
stat,
getPath(dataset.teams[b], stat.path, 0)
) -
this.applyModifiers(
stat,
getPath(dataset.teams[a], stat.path, 0)
)) *
stat.sort
);
statRank = rankedTeams.indexOf(teams[0]) + 1;
// console.log(stat.name)
// console.log(rankedTeams.map(t => `${t}: ${this.applyModifiers(stat, getPath(dataset.teams[t], stat.path, 0))}`))
}
if (stat.decimals !== undefined) {
formattedStat = formattedStat.toFixed(stat.decimals);
}
if (stat.unit) {
formattedStat += stat.unit;
}
}
data.push({
name: stat.name,
value: formattedStat,
rank: statRank,
totalRanked: totalRanked,
hasValue: hasValue,
});
}
return data;
}
applyModifiers(stat, value) {
if (stat.multiplier !== undefined) {
value *= stat.multiplier;
}
if (stat.addend !== undefined) {
value += stat.addend;
}
return value;
}
setData(data) {
this.header.innerHTML = this.moduleConfig.name;
clearDiv(this.list);
for (const stat of data) {
// add all data to stats div
if (stat.hasValue == false) {
// if no stats exist, display "No Data"
const NAElement = createDOMElement("div", "stat");
NAElement.innerHTML = `${stat.name}: No Data`;
this.list.appendChild(NAElement);
} else {
const statElement = createDOMElement("div", "stat"); // different rank classes display different colors
let rankClass = "top100";
if (stat.rank <= Math.round(stat.totalRanked * 0.05)) {
rankClass = "top5";
} else if (stat.rank <= Math.round(stat.totalRanked * 0.25)) {
rankClass = "top25";
} else if (stat.rank <= Math.round(stat.totalRanked * 0.5)) {
rankClass = "top50";
}
const rank = stat.rank
? `#${stat.rank} `
: ""; // create individual stat display
statElement.innerHTML = `${rank}${stat.name}: ${stat.value}`;
this.list.appendChild(statElement);
}
}
}
}
//mapping
const moduleClasses = {Bar,
BubbleSheetGraph,
ColumnDisplay,
Divider,
Grid,
HeatmapScatterPlot,
PerformanceTimePlot,
Pie,
SingleDisplay,
Stats}