lila/public/javascripts/chart/movetime.js

305 lines
10 KiB
JavaScript

function toBlurArray(player) {
return player.blurs && player.blurs.bits ? player.blurs.bits.split('') : [];
}
function formatClock(centis) {
let result = '';
if (centis >= 60 * 60 * 100) result += Math.floor(centis / 60 / 6000) + ':';
result +=
Math.floor((centis % (60 * 6000)) / 6000)
.toString()
.padStart(2, '0') + ':';
const secs = (centis % 6000) / 100;
if (centis < 6000) result += secs.toFixed(2).padStart(5, '0');
else result += Math.floor(secs).toString().padStart(2, '0');
return result;
}
lichess.movetimeChart = function (data, trans, hunter) {
if (!data.game.moveCentis) return; // imported games
lichess.loadScript('javascripts/chart/common.js').then(function () {
lichess.loadScript('javascripts/chart/division.js').then(function () {
lichess.chartCommon('highchart').then(function () {
lichess.movetimeChart.render = function () {
$('#movetimes-chart:not(.rendered)').each(function () {
$(this).addClass('rendered');
const highlightColor = '#3893E8';
const xAxisColor = '#cccccc99';
const whiteAreaFill = hunter ? Highcharts.theme.lichess.area.white : 'rgba(255, 255, 255, 0.2)';
const whiteColumnFill = 'rgba(255, 255, 255, 0.9)';
const whiteColumnBorder = '#00000044';
const blackAreaFill = hunter ? Highcharts.theme.lichess.area.black : 'rgba(0, 0, 0, 0.4)';
const blackColumnFill = 'rgba(0, 0, 0, 0.9)';
const blackColumnBorder = '#ffffff33';
const moveSeries = {
white: [],
black: [],
};
const totalSeries = {
white: [],
black: [],
};
const labels = [];
const tree = data.treeParts;
let ply = 0,
maxMove = 0,
maxTotal = 0,
showTotal = !hunter;
const logC = Math.pow(Math.log(3), 2);
const blurs = [toBlurArray(data.player), toBlurArray(data.opponent)];
if (data.player.color === 'white') blurs.reverse();
data.game.moveCentis.forEach(function (centis, x) {
const node = tree[x + 1];
ply = node ? node.ply : ply + 1;
const san = node ? node.san : '-';
const turn = (ply + 1) >> 1;
const color = ply & 1;
const colorName = color ? 'white' : 'black';
const y = Math.pow(Math.log(0.005 * Math.min(centis, 12e4) + 3), 2) - logC;
maxMove = Math.max(y, maxMove);
let label = turn + (color ? '. ' : '... ') + san;
const movePoint = {
x,
y: color ? y : -y,
};
if (blurs[color].shift() === '1') {
movePoint.marker = {
symbol: 'square',
radius: 3,
lineWidth: '1px',
lineColor: highlightColor,
fillColor: color ? '#fff' : '#333',
};
label += ' [blur]';
}
const seconds = (centis / 100).toFixed(centis >= 200 ? 1 : 2);
label += '<br />' + trans('nbSeconds', '<strong>' + seconds + '</strong>');
moveSeries[colorName].push(movePoint);
let clock = node ? node.clock : undefined;
if (clock == undefined) {
if (x < data.game.moveCentis.length - 1) showTotal = false;
else if (data.game.status.name === 'outoftime') clock = 0;
else if (data.clock) {
const prevClock = tree[x - 1] ? tree[x - 1].clock : undefined;
if (prevClock) clock = prevClock + data.clock.increment - centis;
}
}
if (clock != undefined) {
label += '<br />' + formatClock(clock);
maxTotal = Math.max(clock, maxTotal);
totalSeries[colorName].push({
x,
y: color ? clock : -clock,
});
}
labels.push(label);
});
const disabled = {
enabled: false,
};
const noText = {
text: null,
};
const clickableOptions = {
cursor: 'pointer',
events: {
click: event => {
if (event.point) {
const x = event.point.x;
const p =
this.highcharts.series[(showTotal ? 4 : 0) + (((tree[x] ? tree[x].ply : undefined) || x) % 2)]
.data[x >> 1];
if (p) p.select(true);
lichess.pubsub.emit('analysis.chart.click', x);
}
},
},
};
const foregrondLineOptions = {
...clickableOptions,
color: highlightColor,
lineWidth: hunter ? 1 : 2,
states: {
hover: {
lineWidth: hunter ? 1 : 2,
},
},
marker: {
radius: 1,
states: {
hover: {
radius: 3,
lineColor: highlightColor,
fillColor: 'white',
},
select: {
radius: 4,
lineColor: highlightColor,
fillColor: 'white',
},
},
},
};
this.highcharts = Highcharts.chart(this, {
credits: disabled,
legend: disabled,
series: [
...(showTotal
? [
{
name: 'White Clock Area',
type: 'area',
yAxis: 1,
data: totalSeries.white,
},
{
name: 'Black Clock Area',
type: 'area',
yAxis: 1,
data: totalSeries.black,
},
]
: []),
{
name: 'White Move Time',
type: hunter ? 'area' : 'column',
yAxis: 0,
data: moveSeries.white,
borderColor: whiteColumnBorder,
},
{
name: 'Black Move Time',
type: hunter ? 'area' : 'column',
yAxis: 0,
data: moveSeries.black,
borderColor: blackColumnBorder,
},
...(showTotal
? [
{
name: 'White Clock Line',
type: 'line',
yAxis: 1,
data: totalSeries.white,
},
{
name: 'Black Clock Line',
type: 'line',
yAxis: 1,
data: totalSeries.black,
},
]
: []),
],
chart: {
alignTicks: false,
spacing: [2, 0, 2, 0],
animation: false,
},
tooltip: {
formatter: function () {
return labels[this.x];
},
},
plotOptions: {
series: {
animation: false,
},
area: {
...(hunter
? foregrondLineOptions
: {
...clickableOptions,
lineWidth: 0,
states: {
hover: {
lineWidth: 0,
},
},
marker: disabled,
}),
trackByArea: true,
fillColor: whiteAreaFill,
negativeFillColor: blackAreaFill,
},
line: foregrondLineOptions,
column: {
...clickableOptions,
color: whiteColumnFill,
negativeColor: blackColumnFill,
grouping: false,
groupPadding: 0,
pointPadding: 0,
states: {
hover: disabled,
select: {
enabled: !showTotal,
color: highlightColor,
borderColor: highlightColor,
},
},
},
},
title: noText,
xAxis: {
title: noText,
labels: disabled,
lineWidth: 0,
tickWidth: 0,
plotLines: lichess.divisionLines(data.game.division, trans),
},
yAxis: [
{
title: noText,
min: -maxMove,
max: maxMove,
labels: disabled,
gridLineWidth: 0,
plotLines: [
{
color: xAxisColor,
width: 1,
value: 0,
zIndex: 10,
},
],
},
{
title: noText,
min: -maxTotal,
max: maxTotal,
labels: disabled,
gridLineWidth: 0,
},
],
});
this.highcharts.selectPly = ply => {
const white = ply % 2 !== 0;
const serie = (white ? 0 : 1) + (showTotal ? 4 : 0);
const turn = Math.floor((ply - 1 - data.game.startedAtTurn) / 2);
const point = this.highcharts.series[serie].data[turn];
if (point) point.select(true);
else this.highcharts.getSelectedPoints().forEach(point => point.select(false));
};
});
lichess.pubsub.emit('analysis.change.trigger');
};
lichess.movetimeChart.render();
});
});
});
};