/* 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}