PageSourceSearch

https://sifrovacky.cz/assets/stats/views/charts/trueskill-development-dc0a9250.js

js sifrovacky.cz collected 2026-10-05 02:17:43 UTC 4,783 bytes, 181 lines download raw bytes

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.