huge commit for advanced stats feature.
broke data out into its own library. may be breaking changes with existing plugins
This commit is contained in:
406
WebfrontCore/wwwroot/js/advanced_stats.js
Normal file
406
WebfrontCore/wwwroot/js/advanced_stats.js
Normal file
@ -0,0 +1,406 @@
|
||||
window.onresize = function () {
|
||||
drawPlayerModel();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.table-slide').click(function () {
|
||||
$(this).siblings().children().children('.hidden-row').slideToggle(0);
|
||||
$(this).children('span').toggleClass('oi-chevron-top oi-chevron-bottom');
|
||||
});
|
||||
setupPerformanceGraph();
|
||||
drawPlayerModel();
|
||||
})
|
||||
|
||||
function setupPerformanceGraph() {
|
||||
const summary = $('#client_stats_summary');
|
||||
if (summary === undefined) {
|
||||
return;
|
||||
}
|
||||
const chart = $('#client_performance_history');
|
||||
const container = $('#client_performance_history_container');
|
||||
chart.attr('height', summary.height());
|
||||
chart.attr('width', container.width());
|
||||
renderPerformanceChart();
|
||||
}
|
||||
|
||||
function drawPlayerModel() {
|
||||
const canvas = document.getElementById('hitlocation_model');
|
||||
if (canvas === null) {
|
||||
return;
|
||||
}
|
||||
const context = canvas.getContext('2d');
|
||||
const container = $('#hitlocation_container');
|
||||
const background = new Image();
|
||||
background.onload = () => {
|
||||
const backgroundRatioX = background.width / background.height;
|
||||
|
||||
canvas.height = container.height() - 28;
|
||||
canvas.width = (canvas.height * backgroundRatioX);
|
||||
|
||||
const scalar = canvas.height / background.height;
|
||||
|
||||
drawHitLocationChart(context, background, scalar, canvas.width, canvas.height);
|
||||
}
|
||||
background.src = '/images/stats/hit_location_model.png';
|
||||
}
|
||||
|
||||
function buildHitLocationPosition() {
|
||||
let hitLocations = {}
|
||||
hitLocations['head'] = {
|
||||
x: 454.5,
|
||||
y: 108.5,
|
||||
width: 157,
|
||||
height: 217
|
||||
}
|
||||
|
||||
hitLocations['torso_upper'] = {
|
||||
x: 457,
|
||||
y: 318,
|
||||
width: 254,
|
||||
height: 202
|
||||
}
|
||||
|
||||
hitLocations['torso_lower'] = {
|
||||
x: 456.50,
|
||||
y: 581,
|
||||
width: 315,
|
||||
height: 324
|
||||
}
|
||||
|
||||
hitLocations['right_leg_upper'] = {
|
||||
x: 527.5,
|
||||
y: 856.7,
|
||||
width: 149,
|
||||
height: 228
|
||||
}
|
||||
|
||||
hitLocations['right_leg_lower'] = {
|
||||
x: 542,
|
||||
y: 1077.6,
|
||||
width: 120,
|
||||
height: 214
|
||||
}
|
||||
|
||||
hitLocations['right_foot'] = {
|
||||
x: 558.5,
|
||||
y: 1253.5,
|
||||
width: 93,
|
||||
height: 138
|
||||
}
|
||||
|
||||
hitLocations['left_leg_upper'] = {
|
||||
x: 382.5,
|
||||
y: 857,
|
||||
width: 141,
|
||||
height: 228
|
||||
}
|
||||
|
||||
hitLocations['left_leg_lower'] = {
|
||||
x: 371.5,
|
||||
y: 1078,
|
||||
width: 119,
|
||||
height: 214
|
||||
}
|
||||
|
||||
hitLocations['left_foot'] = {
|
||||
x: 353,
|
||||
y: 1254,
|
||||
width: 90,
|
||||
height: 138
|
||||
}
|
||||
|
||||
hitLocations['left_arm_upper'] = {
|
||||
p1: {
|
||||
x: 330,
|
||||
y: 218
|
||||
},
|
||||
p2: {
|
||||
x: 330,
|
||||
y: 400
|
||||
},
|
||||
p3: {
|
||||
x: 255,
|
||||
y: 475
|
||||
},
|
||||
p4: {
|
||||
x: 165,
|
||||
y: 375
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
|
||||
hitLocations['right_arm_upper'] = {
|
||||
p1: {
|
||||
x: 584,
|
||||
y: 218
|
||||
},
|
||||
p2: {
|
||||
x: 584,
|
||||
y: 400
|
||||
},
|
||||
p3: {
|
||||
x: 659,
|
||||
y: 475
|
||||
},
|
||||
p4: {
|
||||
x: 749,
|
||||
y: 375
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
|
||||
hitLocations['left_arm_lower'] = {
|
||||
p1: {
|
||||
x: 165,
|
||||
y: 375
|
||||
},
|
||||
p2: {
|
||||
x: 255,
|
||||
y: 475
|
||||
},
|
||||
p3: {
|
||||
x: 121,
|
||||
y: 584
|
||||
},
|
||||
p4: {
|
||||
x: 30,
|
||||
y: 512
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
|
||||
hitLocations['right_arm_lower'] = {
|
||||
p1: {
|
||||
x: 749,
|
||||
y: 375
|
||||
},
|
||||
p2: {
|
||||
x: 659,
|
||||
y: 475
|
||||
},
|
||||
p3: {
|
||||
x: 789,
|
||||
y: 587
|
||||
},
|
||||
p4: {
|
||||
x: 876,
|
||||
y: 497
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
|
||||
hitLocations['left_hand'] = {
|
||||
p1: {
|
||||
x: 30,
|
||||
y: 512
|
||||
},
|
||||
p2: {
|
||||
x: 121,
|
||||
y: 584
|
||||
},
|
||||
p3: {
|
||||
x: 0,
|
||||
y: 669
|
||||
},
|
||||
p4: {
|
||||
x: 0,
|
||||
y: 582
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
|
||||
hitLocations['right_hand'] = {
|
||||
p1: {
|
||||
x: 789,
|
||||
y: 587
|
||||
},
|
||||
p2: {
|
||||
x: 876,
|
||||
y: 497
|
||||
},
|
||||
p3: {
|
||||
x: 905,
|
||||
y: 534
|
||||
},
|
||||
p4: {
|
||||
x: 905,
|
||||
y: 666
|
||||
},
|
||||
type: 'polygon'
|
||||
}
|
||||
return hitLocations;
|
||||
}
|
||||
|
||||
function drawHitLocationChart(context, background, scalar, width, height) {
|
||||
context.drawImage(background, 0, 0, background.width, background.height, 0, 0, width, height);
|
||||
|
||||
const hitLocations = buildHitLocationPosition();
|
||||
|
||||
$.each(hitLocationData, (index, hit) => {
|
||||
let scaledPercentage = hit.percentage / maxPercentage;
|
||||
let red;
|
||||
let green = 255;
|
||||
|
||||
if (scaledPercentage < 0.5) {
|
||||
red = Math.round(scaledPercentage * 255 * 2);
|
||||
} else {
|
||||
red = 255;
|
||||
green = Math.round((1 - scaledPercentage) * 255 * 2);
|
||||
}
|
||||
|
||||
red = red.toString(16).padStart(2, '0');
|
||||
green = green.toString(16).padStart(2, '0');
|
||||
|
||||
const color = '#' + red + green + '0077';
|
||||
const location = hitLocations[hit.name];
|
||||
|
||||
if (location.type === 'polygon') {
|
||||
drawPolygon(context, scalar, location.p1, location.p2, location.p3, location.p4, color);
|
||||
} else {
|
||||
drawRectangle(context, scalar, location.x, location.y, location.width, location.height, color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawRectangle(context, scalar, x, y, width, height, color) {
|
||||
const scaledRectWidth = width * scalar;
|
||||
const scaledRectHeight = height * scalar;
|
||||
const rectX = x * scalar - (scaledRectWidth / 2);
|
||||
const rectY = y * scalar - (scaledRectHeight / 2);
|
||||
context.beginPath();
|
||||
context.fillStyle = color
|
||||
context.fillRect(rectX, rectY, scaledRectWidth, scaledRectHeight);
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
function drawPolygon(context, scalar, p1, p2, p3, p4, color) {
|
||||
|
||||
const points = [p1, p2, p3, p4];
|
||||
|
||||
$.each(points, (index, point) => {
|
||||
point.x = point.x * scalar;
|
||||
point.y = point.y * scalar;
|
||||
});
|
||||
|
||||
context.beginPath();
|
||||
context.fillStyle = color;
|
||||
context.moveTo(p1.x, p1.y);
|
||||
context.lineTo(p2.x, p2.y);
|
||||
context.lineTo(p3.x, p3.y);
|
||||
context.lineTo(p4.x, p4.y);
|
||||
context.fill();
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
function getClosestMultiple(baseValue, value) {
|
||||
return Math.round(value / baseValue) * baseValue;
|
||||
}
|
||||
|
||||
function renderPerformanceChart() {
|
||||
const id = 'client_performance_history';
|
||||
const data = $('#' + id).data('history');
|
||||
|
||||
if (data === undefined) {
|
||||
return;
|
||||
}
|
||||
if (data.length <= 1) {
|
||||
// only 0 perf
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = [];
|
||||
data.forEach(function (item, i) {
|
||||
labels.push(i);
|
||||
});
|
||||
|
||||
const padding = 4;
|
||||
let dataMin = Math.min(...data);
|
||||
const dataMax = Math.max(...data);
|
||||
|
||||
if (dataMax - dataMin === 0) {
|
||||
dataMin = 0;
|
||||
}
|
||||
|
||||
dataMin = Math.max(0, dataMin);
|
||||
|
||||
const min = getClosestMultiple(padding, dataMin - padding);
|
||||
const max = getClosestMultiple(padding, dataMax + padding);
|
||||
|
||||
const chartData = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
pointBackgroundColor: 'rgba(255, 255, 255, 0)',
|
||||
pointBorderColor: 'rgba(255, 255, 255, 0)',
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: 'rgba(255, 255, 255, 1)',
|
||||
}]
|
||||
};
|
||||
|
||||
const options = {
|
||||
defaultFontFamily: '-apple-system, BlinkMacSystemFont, "Open Sans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: false,
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => Math.round(tooltipItem.yLabel) + ' ' + _localization["PLUGINS_STATS_COMMANDS_PERFORMANCE"],
|
||||
title: () => ''
|
||||
},
|
||||
mode: 'nearest',
|
||||
intersect: false,
|
||||
animationDuration: 0,
|
||||
cornerRadius: 0,
|
||||
displayColors: false
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: false
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
fill: false,
|
||||
borderColor: 'rgba(255, 255, 255, 0.75)',
|
||||
borderWidth: 2
|
||||
},
|
||||
point: {
|
||||
radius: 5
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
display: false,
|
||||
}],
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
|
||||
position: 'right',
|
||||
ticks: {
|
||||
callback: function (value, index, values) {
|
||||
if (index === values.length - 1) {
|
||||
return min;
|
||||
} else if (index === 0) {
|
||||
return max;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
fontColor: 'rgba(255, 255, 255, 0.25)'
|
||||
}
|
||||
}]
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 15
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
new Chart(id, {
|
||||
type: 'line',
|
||||
data: chartData,
|
||||
options: options
|
||||
});
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
function getStatsChart(id, width, height) {
|
||||
const data = $('#' + id).data('history');
|
||||
|
||||
if (data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fixedData = [];
|
||||
data.forEach(function (item, i) {
|
||||
fixedData[i] = { x: i, y: Math.floor(item) };
|
||||
@ -12,7 +17,7 @@
|
||||
dataMin = 0;
|
||||
}
|
||||
|
||||
const padding = (dataMax - dataMin) * 0.075;
|
||||
const padding = (dataMax - dataMin) * 0.5;
|
||||
const min = Math.max(0, dataMin - padding);
|
||||
const max = dataMax + padding;
|
||||
let interval = Math.floor((max - min) / 2);
|
||||
@ -27,12 +32,11 @@
|
||||
animationEnabled: false,
|
||||
toolTip: {
|
||||
contentFormatter: function (e) {
|
||||
return Math.round(e.entries[0].dataPoint.y, 1);
|
||||
return `${_localization['WEBFRONT_ADV_STATS_RANKING_METRIC']} ${Math.round(e.entries[0].dataPoint.y, 1)}`;
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: _localization['WEBFRONT_STATS_PERFORMANCE_HISTORY'],
|
||||
fontSize: 14
|
||||
fontSize: 0
|
||||
},
|
||||
axisX: {
|
||||
gridThickness: 0,
|
||||
|
Reference in New Issue
Block a user