1import Highcharts from 'highcharts' 2// import 'highcharts-data' 3 4import 'stats/highcharts-defaults' 5import { groupBy } from 'stats/helpers/collection' 6import { colorize } from 'stats/helpers/colors' 7import { api, q, handleResponse } from 'stats/api' 8 9const queryString = location.search 10const urlParams = new URLSearchParams(queryString) 11const chartId = 'trueskill-development-chart' 12const speed = parseInt(urlParams.get('speed')) || 500 13const playButton = document.getElementById('play-pause-button') 14const stepForwardButton = document.getElementById('step-forward') 15const stepBackwardButton = document.getElementById('step-backward') 16const input = document.getElementById('play-range'); 17const count = 20 18 19const gamesRequest = api('games') 20 .from('games') 21 .select(q(['game_id', 'name', 'title', 'start'])) 22 .order('start') 23 .then(handleResponse()) 24 25const url = location.pathname + '.json' + location.search 26const statsRequest = fetch(url) 27 .then((response) => response.json()) 28 .then((stats) => groupBy(stats, ({ game }) => game.id)) 29 30const [initialData, games] = await Promise.all([statsRequest, gamesRequest]) 31 32Object.keys(initialData).forEach((gameId) => { 33 initialData[gameId] = initialData[gameId].sort((a, b) => b.mu - a.mu) 34}) 35 36const ladderGames = games.filter(({ game_id }) => initialData[game_id]) 37 38input.setAttribute('max', ladderGames.length - 1) 39 40// initialData holds only changes in trueskills. If a player didn't participate in a game, he is missing 41// currentData holds last known trueskills 42const currentTrueskills = {} 43 44/** 45 * Calculate the data output 46 */ 47 48function getData(step) { 49 console.log('step', step) 50 const game = ladderGames[step] 51 console.log('game', game) 52 53 initialData[game.game_id].forEach((trueskill) => { 54 currentTrueskills[trueskill.user.id] = trueskill 55 }) 56 57 const ts = Object.values(currentTrueskills) 58 ts.sort((a, b) => b.mu - a.mu) 59 60 const output = ts.map(({ mu, user }) => 61 ({ 62 name: user.first_name + ' ' + user.last_name, 63 y: mu || 0.0, 64 color: colorize(user.id) 65 })) 66 67 return ([game, output.slice(0, count)]) 68} 69 70const chart = Highcharts.chart(chartId, { 71 chart: { 72 animation: { duration: speed - 50 }, 73 events: { 74 render() { 75 // Responsive input 76 input.style.width = this.plotWidth - this.legend.legendWidth + this.plotLeft / 2 - 10 + 'px' 77 // where 10 is a padding 78 } 79 }, 80 marginRight: 50 81 }, 82 plotOptions: { 83 series: { 84 animation: false, 85 groupPadding: 0, 86 pointPadding: 0.1, 87 borderWidth: 0 88 } 89 }, 90 title: { 91 useHTML: true, 92 text: `Vývoj žebÅÃÄku <b>${getData(0)[0].title}</b>` 93 }, 94 95 legend: { 96 align: 'right', 97 verticalAlign: 'bottom', 98 itemStyle: { 99 fontWeight: 'bold', 100 fontSize: '50px' 101 }, 102 symbolHeight: 0.001, 103 symbolWidth: 0.001, 104 symbolRadius: 0.001 105 }, 106 xAxis: { 107 type: 'category' 108 }, 109 yAxis: [{ opposite: true, title: { text: 'KI' }, tickAmount: 5 }], 110 series: [{ 111 colorByPoint: true, 112 dataSorting: { enabled: true, matchByName: true }, 113 // dataSorting: { enabled: true }, 114 type: 'bar', 115 dataLabels: [{ 116 format: '{point.y:,.1f}', 117 enabled: true 118 }], 119 name: getData(10)[0].start.substring(0, 4), 120 data: getData(10)[1] 121 }] 122}) 123 124/** 125 * Update the chart. This happens either on updating (moving) the range input, 126 * or from a timer when the timeline is playing. 127 */ 128function update(increment) { 129 if (increment) { 130 input.value = parseInt(input.value) + increment 131 } 132 if (parseInt(input.value) + 1 >= ladderGames.length) { 133 pause(playButton) 134 } 135 136 const data = getData(input.value) 137 chart.update({ 138 title: { 139 useHTML: true, 140 text: `<div>Vývoj žebÅÃÄku <b>${data[0].title}</b></span></div>` 141 } 142 }, false, false, false) 143 144 chart.series[0].update({ name: data[0].start.substring(0, 4), data: data[1] }) 145} 146 147/** 148 * Play the timeline. 149 */ 150function play(button) { 151 button.title = 'Zastavit' 152 button.className = 'fa fa-pause' 153 button.innerText = '⸠' 154 chart.sequenceTimer = setInterval(() => { update(1) }, speed) 155} 156 157/** 158 * Pause the timeline, either when the range is ended, or when clicking the 159 * pause button. Pausing stops the timer and resets the button to play mode. 160 */ 161function pause(button) { 162 button.title = 'PÅehrát' 163 button.innerText = ' â¶ ' 164 clearTimeout(chart.sequenceTimer) 165 chart.sequenceTimer = undefined 166} 167 168playButton.addEventListener('click', function() { 169 if (chart.sequenceTimer) { 170 pause(this) 171 } else { 172 play(this) 173 } 174}) 175 176/** 177 * Trigger the update on the range bar click. 178 */
179input.addEventListener('click', update) 180stepBackwardButton.addEventListener('click', () => { update(-1) }) 181stepForwardButton.addEventListener('click', () => { update(1) })
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.