PageSourceSearch

https://www.realbiathlon.com/visualisation.js?161604

js realbiathlon.com collected 2026-10-02 20:28:03 UTC 312,657 bytes, 7,378 lines download raw bytes

1function table1(div1, data1, cols, headers, type, min, sortindex, reverse, value1key, value2key, value3key, value4key,
2                tableClass, searchable, highlightplaces) {
3    var outercontainer = document.createElement('div')
4    outercontainer.classList.add('outercontainer')
5    var container = document.createElement('div')
6    container.classList.add('newtablediv', 'table-responsive-xl')
7    outercontainer.appendChild(container)
8    var xNationColumn = document.getElementsByClassName("nationcolumn")[0];
9    const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
10    const isDesktop = !('ontouchstart' in window || navigator.maxTouchPoints);
11    var headersData = []
12    console.log(data1)
13    var div00 = d3.select("body").append("div")
14        .attr("class", 'tooltipsmallstats')
15        .style("opacity", 0);
16
17
18    if (headers.length === 0) {
19        if (type === 113) {
20            data1.forEach(function (h) {
21                for (var index in h) {
22                    if (!headersData.includes(index))
23                        headersData.push(index);
24                }
25            })
26        } else if (highlightplaces === "highlight") {
27            for (var index in data1[data1.length - 1]) {
28                if (!headersData.includes(index) && !index.includes("_Id"))
29                    headersData.push(index);
30            }
31            data1.forEach(function (h) {
32                for (var index in h) {
33                    if (!headersData.includes(index) && !index.includes("_Id"))
34                        headersData.push(index);
35                }
36            })
37        } else {
38            var columns = 0
39            for (var index in data1[0]) {
40                columns++;
41                if (columns < cols && index !== "ibuid" && index !== "bibcolor" && !index.includes("Target") && !index.includes("interval") &&
42                    !index.includes("min") && !index.includes("currentscore"))
43                    headersData.push(index);
44            }
45        }
46    } else
47        headersData = headers
48
49    if (type === 111)
50        headersData = ["Season", "Races", "Wins", "Podiums", "Top5s", "Top10s", "Top20s", "Top30s", "Top40s"]
51    var headersboolean = []
52
53
54    let headersreverselist = ["standingPercent", "totalPercent", "points", "Points", "pronePercent", "wcpoints", "averagePoints", "dnf", "dns", "dsq", "lap", "races"
55        , "outsideTop40", "podiums", "secondplaces", "thirdplaces", "top5s", "top10s", "top20s", "top30s", "top40s", "wins", "winPercent", "podiumPercent", "Win%",
56        , "Races", "Wins", "Podiums", "Top5s", "Top10s", "Top20s", "Top30s", "Top40s", "Win%", "Podium%", "2nd", "3rd", "Other", "Top10%"]
57    headers = headersData;
58
59    headers.forEach(function (e, i) {
60        if (headersreverselist.includes(e))
61            headersboolean.push(true)
62        else
63            headersboolean.push(false)
64    })
65    if (div1 === undefined)
66        div1 = d3.select("body")
67    var data = [];
68
69    if (type === 555 || type === 556) {
70        data1.forEach(function (e, i) {
71            for (var property in e) {
72                if (e.hasOwnProperty(property)) {
73                    if (e[property] == null) {
74                        e[property] = "";
75                    }
76                    if (e[property] === "NaN") {
77                        e[property] = "";
78                    }
79                }
80            }
81            var numberOfTotalRaces = 0;
82
83            if (e.worldCupScores !== undefined) {
84                var total = 0;
85                var all = [];
86                for (const [key, value] of Object.entries(e.worldCupScores)) {
87                    numberOfTotalRaces++;
88                    e[key] = value;
89                    if (value !== null && typeof value.rank !== "undefined" && value.rank !== null) {
90                        if (value.rank === 10001) value.rank = "LAP";
91                        if (value.rank === 10002) value.rank = "DNF";
92                        if (value.rank === 10008) value.rank = "DSQ";
93                    }
94                    total++;
95                    if (value > 0)
96                        all.push(value);
97                    if (!headersData.includes(key))
98                        headersData.push(key);
99                    const index = headersData.indexOf("worldCupScores");
100                    if (index > -1) {
101                        headersData.splice(index, 1);
102                    }
103                    let id = headersData.indexOf("ibuid");
104                    if (id > -1) {
105                        headersData.splice(id, 1);
106                    }
107                    id = headersData.indexOf("score");
108                    if (id > -1) {
109                        headersData.splice(id, 1);
110                    }
111                    id = headersData.indexOf("u25");
112                    if (id > -1) {
113                        headersData.splice(id, 1);
114                    }
115                    id = headersData.indexOf("calculatedScore");
116                    if (id > -1) {
117                        headersData.splice(id, 1);
118                    }
119                    id = headersData.indexOf("numberOfRaces");
120                    if (id > -1) {
121                        headersData.splice(id, 1);
122                    }
123                    id = headersData.indexOf("doneRaces");
124                    if (id > -1) {
125                        headersData.splice(id, 1);
126                    }
127                    id = headersData.indexOf("rankDiff");
128                    if (id > -1) {
129                        headersData.splice(id, 1);
130                    }
131                    const id11 = headersData.indexOf("total");
132                    if (id11 > -1) {
133                        headersData.splice(id11, 1);
134                    }
135                    if (type === 555) {
136                        const id2 = headersData.indexOf("country");
137                        if (id2 > -1) {
138                            headersData.splice(id2, 1);
139                        }
140                    }
141                    if (type === 556) {
142                        const id2 = headersData.indexOf("familyName");
143                        if (id2 > -1) {
144                            headersData.splice(id2, 1);
145                        }
146                        id = headersData.indexOf("u25");
147                        if (id > -1) {
148                            headersData.splice(id, 1);
149                        }
150                        const id3 = headersData.indexOf("givenName");
151                        if (id3 > -1) {
152                            headersData.splice(id3, 1);
153                        }
154
155                    }
156                }
157
158            }
159            if (e.ncscores !== undefined) {
160                var total = 0;
161                var all = [];
162                for (const [key, value] of Object.entries(e.ncscores)) {
163                    numberOfTotalRaces++;
164                    e[key] = value;
165                    total++;
166                    if (value > 0)
167                        all.push(value);
168                    if (!headersData.includes(key))
169                        headersData.push(key);
170                    const index = headersData.indexOf("ncscores");
171                    if (index > -1) {
172                        headersData.splice(index, 1);
173                    }
174                    let id = headersData.indexOf("ibuid");
175                    if (id > -1) {
176                        headersData.splice(id, 1);
177                    }
178                    id = headersData.indexOf("score");
179                    if (id > -1) {
180                        headersData.splice(id, 1);
181                    }
182                    id = headersData.indexOf("calculatedScore");
183                    if (id > -1) {
184                        headersData.splice(id, 1);
185                    }
186                    id = headersData.indexOf("numberOfRaces");
187                    if (id > -1) {
188                        headersData.splice(id, 1);
189                    }
190                    id = headersData.indexOf("rankDiff");
191                    if (id > -1) {
192                        headersData.splice(id, 1);
193                    }
194                    const id2 = headersData.indexOf("year");
195                    if (id2 > -1) {
196                        headersData.splice(id2, 1);
197                    }
198                    const id11 = headersData.indexOf("total");
199                    if (id11 > -1) {
200                        headersData.splice(id11, 1);
201                    }
202                    id = headersData.indexOf("doneRaces");
203                    if (id > -1) {
204                        headersData.splice(id, 1);
205                    }
206
207                }
208
209            }
210            e.seasonWCpoints = "(" + e.calculatedScore + ")";
211            if (e.score !== undefined && e.score !== null && e.score !== "") e.seasonWCpoints = e.score;
212            if (year > 2020 && eliminateDropped === "drop" && numberOfTotalRaces !== e.numberOfRaces) e.seasonWCpoints = e.calculatedScore;
213            if (year > 2020 && eliminateDropped === "drop" && numberOfTotalRaces !== e.doneRaces) e.seasonWCpoints = e.calculatedScore;
214
215            if (year > 1993 && disciplineString === "NonTeam" && showU25 == "true") {
216                if (e.u25 === true)
217                    data.push(e);
218            } else if (year > 1993 && disciplineString === "NonTeam" && showU25 == "false") data.push(e);
219            else data.push(e);
220        })
221    } else if (type === 999 || type == 878 || type == 1119 || type == 9858) {
222        data1.forEach(function (e, i) {
223            for (var property in e) {
224                if (e.hasOwnProperty(property)) {
225                    if (e[property] == null) {
226                        e[property] = "";
227                    }
228                    if (e[property] === 'null') {
229                        e[property] = "";
230                    }
231                    if (e[property] === "NaN") {
232                        e[property] = "";
233                    }
234                }
235            }
236            let id11 = headersData.indexOf("Image 1");
237            if (id11 > -1) {
238                headersData.splice(id11, 1);
239            }
240            id11 = headersData.indexOf("Image 2");
241            if (id11 > -1) {
242                headersData.splice(id11, 1);
243            }
244            id11 = headersData.indexOf("Image 3");
245            if (id11 > -1) {
246                headersData.splice(id11, 1);
247            }
248            id11 = headersData.indexOf("Image 4");
249            if (id11 > -1) {
250                headersData.splice(id11, 1);
251            }
252            //: "2019-11-30T12:10:00Z"
253            if (e.startTime !== undefined && e.startTime.length > 16) {
254                var b = e.startTime.split(/\D/);
255                // Subtract 1 from month and hour
256                var d = new Date(Date.UTC(b[0], b[1] - 1, b[2], b[3], b[4]));
257
258
259                if (localtime === "true") {
260                    e.Date = d.getFullYear() + "-" + lead_zero(d.getMonth() + 1) + "-" + lead_zero(d.getDate());
261                    e.Time = lead_zero(d.getHours()) + ":" + lead_zero(d.getMinutes());
262
263                } else {
264                    e.Date = e.startTime.substring(0, 10);
265                    e.Time = d.getUTCHours() === 23 ? "" : lead_zero(d.getUTCHours() + 1) + ":" + lead_zero(d.getUTCMinutes());
266                }
267
268            }
269            if (type === 999 && e.disciplineId !== undefined)
270                e.Dis = e.disciplineId;
271            if (e.year !== undefined)
272                e.Season = (e.year < 2000) ? e.year - 1 + "–" + (e.year.toString().slice(-2)) : e.year - 1 + "–" + (e.year.toString().slice(-2));
273
274            if (type === 9858) {
275                '<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>'
276                e["Full race"] = "dd"
277                e["Interviews"] = "dd"
278                e["Hightlights"] = "dd"
279                e["Zeroing"] = "dd"
280                data.push(e);
281
282            } else if (e.rank === undefined || e.rank <= slidervalue)
283                data.push(e);
284        })
285    } else if (type === 50 || type === 170 || type === 5088 || type === 170170) {
286
287        data1.forEach(function (e, i) {
288            if (e.rank !== undefined && e.rank > 999)
289                e.rank = e.resultString;
290            for (var property in e) {
291                if (e.hasOwnProperty(property)) {
292                    if (e[property] == null) {
293                        e[property] = "";
294                    }
295                    if (e[property] === "NaN") {
296                        e[property] = "";
297                    }
298                }
299            }
300            e.RaceRank = e.rank;
301            if (type === 50 && typeof e.resultString !== "undefined" && (e.rank === undefined || e.rank === null || e.rank === ""))
302                e.RaceRank = e.resultString
303            if (type === 170 && typeof e.resultString !== "undefined" && e.resultString === "DSQ" && (e.rank === undefined || e.rank === null || e.rank === ""))
304                e.rank = e.resultString
305
306
307            if (type === 50 && typeof e.discipline !== undefined)
308                e.Discipline = e.discipline
309            if (type === 50 && typeof e.ageAtRace !== undefined && e.ageAtRace !== null){
310                let age = Math.floor((e.ageAtRace / 365.2425) * 10) / 10
311                if (age < 12 || isNaN(age)) e.Age = ""
312                else e.Age = (age).toFixed(1)
313            }
314            e.Dis = e.discipline
315            if ((type === 50 || type === 5088) && e.time !== undefined)
316                e.Date = toDateString(e.time)
317            if ((type === 50 || type === 5088) && e.seasonId !== undefined)
318                e.Season = getSeason(e.seasonId)
319            if ((type === 50) && e.levelId !== undefined)
320                e.Level = getLevel(e.levelId)
321            if ((type === 50) && e.level !== undefined && e.raceId !== undefined)
322                e.Event = getEvent(e.level, e.raceId)
323            if ((type === 5088) && e.level !== undefined)
324                e.Level = e.level.replace("wch", "WCH").replace("wc", "WC").replace("olympics", "Olympics")
325            if (type === 5088 && e.organizerNat !== undefined)
326                e.nat = e.organizerNat
327            if (type === 5088 && e.weather !== undefined && e.weather.afterStart !== undefined && e.weather.afterStart !== null && e.weather.afterStart.airTemperature !== null)
328                e.Weather = e.weather.afterStart.airTemperature + "°C, " + e.weather.afterStart.sky + ", " + e.weather.afterStart.wind;
329
330            if (type === 170170) {
331                data.push(e);
332            } else if (typeof isTeam !== "undefined" && isTeam && type === 170 && i < slidervalue * 5) {
333                data.push(e);
334            } else if (type === 5088) {
335                if (!(cat === "podiums" && (e.dis === "Median:\u2063" || e.dis === "Average:\u2063"))) data.push(e);
336            } else if (type === 170 && i < slidervalue || Number(slidervalue) === 120) {
337
338                data.push(e);
339            } else if (type === 50 && ((e.rank <= slidervalue && e.rank !== "") || Number(slidervalue) === 120 || (typeof e.rank !== "undefined" && e.rank.toString().indexOf('\u2063') >= 0))) {
340                data.push(e);
341            }
342
343
344        })
345    } else if (type === 111 || type === 112) {
346        let counter = 0;
347        data1.forEach(function (e, i) {
348            if ((e.Races !== undefined && e.Races > 0) || (e.races !== undefined && e.races > 0) || (e.resultString !== undefined && e.resultString !== "")
349                || (typeString !== undefined && typeString === "career")) {
350                for (var property in e) {
351                    if (e.hasOwnProperty(property)) {
352                        if (e[property] == null) {
353                            e[property] = "";
354                        }
355                        if (e[property] === "NaN") {
356                            e[property] = "";
357                        }
358                    }
359                }
360                if (window.location.href.includes("search.html")) {
361                    if (counter < slidervalue) {
362                        data.push(e);
363                        counter++;
364                    }
365                } else {
366                    data.push(e);
367                }
368
369            }
370
371        })
372    } else if (type === 113) {
373        data1.forEach(function (e, i) {
374            for (var property in e) {
375                if (e.hasOwnProperty(property)) {
376                    if (e[property] == null) {
377                        e[property] = "";
378                    }
379                    if (e[property] === "NaN") {
380                        e[property] = "";
381                    }
382                }
383            }
384            if (cat === "statsPerRace") {
385                let seasonstring = "";
386                let seasonId = e.name.substring(2, 6)
387                if (e.name.substring(2, 3) > 4)
388                    seasonstring = "19" + e.name.substring(2, 4) + "–" + e.name.substring(4, 6);
389                else
390                    seasonstring = "20" + e.name.substring(2, 4) + "–" + e.name.substring(4, 6);
391
392                e.Event = e.name.substring(6, 14).replace("__", "").replace("SWRLCP0", "World Cup ")
393                    .replace("SWRLCP1", "World Cup 1")
394                    .replace("JIBUCP0", "IBU Junior Cup ")
395                    .replace("SIBUCP0", "IBU Cup ")
396                    .replace("SCEUCP0", "European Cup ")
397                    .replace("SWRLCH", "World Championship")
398                    .replace("JCEUCH", "Junior European Championship")
399                    .replace("SCEUCH", "European Championship")
400                    .replace("JWRLCH", "Junior World Championship")
401                    .replace("YWRLCH", "Youth World Championship")
402                    .replace("SWRLOG", "Winter Olympics")
403
404                if (e.name.includes("__Y")) e.Event = e.Event.replace("Junior", "Youth")
405                if (seasonId === "0809" || seasonId === "0910" || seasonId === "1011" || seasonId === "1112") e.Event = e.Event.replace("European Cup", "IBU Cup")
406                if (e.name.includes("J")) e.Event = e.Event.replace("European Cup", "European Junior Cup")
407                e.Season = seasonstring
408                e.Discipline = e.name.slice(-2)
409            }
410            data.push(e);
411        })
412
413    } else if (type === 174) {
414        data1.forEach(function (e, i) {
415            for (var property in e) {
416                if (e.hasOwnProperty(property)) {
417                    if (e[property] == null) {
418                        e[property] = "";
419                    }
420                    if (e[property] === "NaN") {
421                        e[property] = "";
422                    }
423                }
424            }
425            e.Country = e.familyName;
426
427            if (e.overallTitles + e.overall2nd + e.overall3rd + e.disciplineTitles + e.discipline2nd + e.discipline3rd > 0)
428                data.push(e);
429        })
430
431    } else if (type === 11210) {
432        data1.forEach(function (e, i) {
433            for (var property in e) {
434                if (e.hasOwnProperty(property)) {
435                    if (e[property] == null) {
436                        e[property] = "";
437                    }
438                    if (e[property] === "NaN") {
439                        e[property] = "";
440                    }
441                }
442            }
443
444
445            if (e.rank < 11)
446                data.push(e);
447        })
448
449    } else if (type === 5235) {
450        data1.forEach(function (e, i) {
451            for (var property in e) {
452                if (e.hasOwnProperty(property)) {
453                    if (e[property] == null) {
454                        e[property] = "";
455                    }
456                    if (e[property] === "NaN") {
457                        e[property] = "";
458                    }
459                }
460            }
461
462            data.push(e);
463        })
464
465    } else {
466        data1.forEach(function (e, i) {
467            var push = true;
468            for (var property in e) {
469                if (e.hasOwnProperty(property)) {
470                    if (e[property] == null) {
471                        e[property] = "";
472                    }
473                    if (e[property] === "NaN") {
474                        e[property] = "";
475                        push = false;
476                    }
477                }
478            }
479
480            if (typeof typeString !== "undefined" && typeString === "teams" && e.familyName !== undefined) {
481                e.Country = e.familyName;
482                var index = headersData.indexOf("familyName");
483                if (index !== -1) {
484                    headersData[index] = "Country";
485                }
486            }
487            if (typeof typeString !== "undefined" && typeString === "teams" && e.familyName !== undefined) {
488                e.gender = e.givenName;
489                var index = headersData.indexOf("givenName");
490                if (index !== -1) {
491                    headersData[index] = "gender";
492                }
493            }
494
495            if (e.timeLoss !== undefined)
496                e.rangeTimePlusPenaltyTime = e.timeLoss + 4.0
497
498            if ((e.races >= min && push) || e.raceRank <= slidervalue || e.rank <= slidervalue)
499                data.push(e);
500        })
501    }
502    if (vw < 1200) {
503        const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
504        const idx = headersData.indexOf("bib") === -1 ? headersData.indexOf("Bib") : headersData.indexOf("bib");
505        if (idx > -1) {
506            headersData.splice(idx, 1);
507        }
508    }
509
510
511    if (window.location.href.includes("seasons.html") || window.location.href.includes("seasons2.html")) {
512        if (typeof periodString !== "undefined" && periodString === "Season" && typeof levelString !== "undefined" && levelString === "WorldCup") {
513            if (typeof disciplineString !== "undefined" && disciplineString !== "Team" && disciplineString !== "Total")
514                if (genderString !== "X" && genderString !== "A")
515                    headersData.splice(5, 0, "wcrank");
516        }
517    }
518
519    if (type === 555) {
520        if (!headersData.includes('total'))
521            headersData.push('total');
522        if (!headersData.includes('seasonWCpoints'))
523            headersData.splice(4, 0, 'seasonWCpoints');
524        if (!headersData.includes('rankDiff'))
525            headersData.splice(4, 0, 'rankDiff');
526    }
527    if (type == 556) {
528
529        if (!headersData.includes('total'))
530            headersData.push('total');
531        if (!headersData.includes('seasonWCpoints'))
532            headersData.splice(3, 0, 'seasonWCpoints');
533        if (!headersData.includes('rankDiff'))
534            headersData.splice(3, 0, 'rankDiff');
535
536    } else if (type === 5 || type === 100) {
537        data.sort((a, b) => (a.wins - b.wins) || (a.secondplaces - b.secondplaces) || (a.thirdplaces - b.thirdplaces) ||
538            (a.top5s - b.top5s) || (a.top10s - b.top10s) || (a.top20s - b.top20s) || (a.top30s - b.top30s) || (a.top40s - b.top40s));
539        data.reverse();
540    } else if (type === 174) {
541        data.sort((a, b) => (a.overallTitles - b.overallTitles) || (a.disciplineTitles - b.disciplineTitles) || (a.overall2nd - b.overall2nd) ||
542            (a.overall3rd - b.overall3rd) || (a.discipline2nd - b.discipline2nd) || (a.discipline3rd - b.discipline3rd));
543        data.reverse();
544    } else {
545        let re = 1;
546        if (sortindex.startsWith("-")) {
547            sortindex = sortindex.substring(1);
548            re = -1;
549
550        }
551        data.sort(function (a, b) {
552            if (a[sortindex] < b[sortindex]) {
553                return -1 * reverse * re;
554            }
555            if (a[sortindex] > b[sortindex]) {
556                return 1 * reverse * re;
557            }
558            return 0;
559        })
560    }
561    if (type === 170) {
562        function isEmptyValue(value) {
563            if (value === undefined || value === null) return true;
564            let valueString = value.toString().trim();
565            return valueString === "" || valueString === "\u2063" || valueString === "null" || valueString === "undefined";
566        }
567
568        function isDsqRow(row) {
569            if (row === undefined || row === null) return false;
570            let resultString = row.resultString === undefined || row.resultString === null ? "" : row.resultString.toString().trim().toUpperCase();
571            if (resultString === "DSQ") return true;
572            let rankString = row.rank === undefined || row.rank === null ? "" : row.rank.toString().trim().toUpperCase();
573            if (rankString === "DSQ") return true;
574            let raceRankString = row.RaceRank === undefined || row.RaceRank === null ? "" : row.RaceRank.toString().trim().toUpperCase();
575            return raceRankString === "DSQ";
576        }
577
578        function dsqRowCompleteness(row) {
579            if (row === undefined || row === null) return 0;
580            let ignoredKeys = {rank: true, RaceRank: true, resultString: true, bib: true};
581            let score = 0;
582            Object.keys(row).forEach(function (key) {
583                if (ignoredKeys[key]) return;
584                if (!isEmptyValue(row[key])) score++;
585            });
586            return score;
587        }
588
589        let dedupedRows = [];
590        let dsqIndexByBib = {};
591        data.forEach(function (row) {
592            if (!isDsqRow(row)) {
593                dedupedRows.push(row);
594                return;
595            }
596            let bib = row !== undefined && row !== null && row.bib !== undefined && row.bib !== null ? row.bib.toString().trim() : "";
597            if (bib === "") {
598                dedupedRows.push(row);
599                return;
600            }
601            if (dsqIndexByBib[bib] === undefined) {
602                dsqIndexByBib[bib] = dedupedRows.length;
603                dedupedRows.push(row);
604                return;
605            }
606            let existingIndex = dsqIndexByBib[bib];
607            let existingRow = dedupedRows[existingIndex];
608            if (dsqRowCompleteness(row) > dsqRowCompleteness(existingRow))
609                dedupedRows[existingIndex] = row;
610        });
611        data = dedupedRows;
612
613        let nonDsqRows = [];
614        let dsqRows = [];
615        data.forEach(function (row) {
616            if (isDsqRow(row)) dsqRows.push(row);
617            else nonDsqRows.push(row);
618        });
619        data = nonDsqRows.concat(dsqRows);
620    }
621    if (window.location.href.includes("projections.html")) {
622        data.forEach(function (e, i) {
623            e.rank = i + 1;
624        })
625    }
626
627    if (window.location.href.includes("search.html")) {
628        try {
629            searchheadersdata = headersData
630        } catch (e) {
631        }
632    }
633
634    if (type === 5235 && data.length > slidervalue) data.length = slidervalue;
635
636
637    if (typeof limit !== "undefined" && limit < data.length) data.splice(limit - data.length, 2000)
638
639    var value1 = [];
640    var value2 = [];
641    var value3 = [];
642    var value4 = [];
643    var value1reverse = false;
644    var value2reverse = false;
645    var value3reverse = false;
646    var value4reverse = false;
647    if (value1key.startsWith("-")) {
648        value1key = value1key.substring(value1key.indexOf("-") + 1)
649        value1reverse = true;
650    }
651    if (value2key.startsWith("-")) {
652        value2key = value2key.substring(value2key.indexOf("-") + 1)
653        value2reverse = true;
654    }
655    if (value3key.startsWith("-")) {
656        value3key = value3key.substring(value3key.indexOf("-") + 1)
657        value3reverse = true;
658    }
659    if (value4key.startsWith("-")) {
660        value4key = value4key.substring(value4key.indexOf("-") + 1)
661        value4reverse = true;
662    }
663
664    data.forEach(function (e, i) {
665        e.No = i + 1;
666        if (e[value1key] !== undefined && e[value1key] !== null && e[value1key] !== "" && e[value1key] !== 0) value1.push(e[value1key])
667        if (e[value2key] !== undefined && e[value2key] !== null && e[value2key] !== "" && e[value2key] !== 0) value2.push(e[value2key])
668        if (e[value3key] !== undefined && e[value3key] !== null && e[value3key] !== "" && e[value3key] !== 0) value3.push(e[value3key])
669        if (e[value4key] !== undefined && e[value4key] !== null && e[value4key] !== "" && e[value4key] !== 0) value4.push(e[value4key])
670        if (type === 100) {
671            e.winPercent = e.wins / e.races * 100;
672            e.podiumPercent = e.podiums / e.races * 100;
673        }
674    })
675
676    var q = [1, 0.833, 0.666, 0.5, 0.333, 0.166, 0];
677    let divider = (document.getElementById("darkSwitch").checked) ? 0.1 : 0.23;
678    var color1 = d3.scaleLinear()
679        .range(['rgba(79,243,194,' + divider + ')', 'rgba(62,223,182,' + divider + ')', 'rgba(47,202,169,' + divider + ')', 'rgba(38,166,153,' + divider + ')',
680            'rgba(30,131,137,' + divider + ')', 'rgba(21,96,121,' + divider + ')', 'rgba(32,46,53,' + divider + ')'])
681        .interpolate(d3.interpolateHcl);
682    if (!value1reverse) {
683        color1.domain([quartile(value1, q[6]), quartile(value1, q[5]), quartile(value1, q[4]), quartile(value1, q[3]), quartile(value1, q[2]), quartile(value1, q[1]), quartile(value1, q[0])])
684    } else {
685        color1.domain([quartile(value1, q[0]), quartile(value1, q[1]), quartile(value1, q[2]), quartile(value1, q[3]), quartile(value1, q[4]), quartile(value1, q[5]), quartile(value1, q[6])])
686    }
687
688    var color2 = d3.scaleLinear()
689        .range(['rgba(79,243,194,' + divider + ')', 'rgba(62,223,182,' + divider + ')', 'rgba(47,202,169,' + divider + ')', 'rgba(38,166,
689153,' + divider + ')',
690            'rgba(30,131,137,' + divider + ')', 'rgba(21,96,121,' + divider + ')', 'rgba(32,46,53,' + divider + ')'])
691        .interpolate(d3.interpolateHcl);
692    if (!value2reverse) {
693        color2.domain([quartile(value2, q[6]), quartile(value2, q[5]), quartile(value2, q[4]), quartile(value2, q[3]), quartile(value2, q[2]), quartile(value2, q[1]), quartile(value2, q[0])])
694    } else {
695        color2.domain([quartile(value2, q[0]), quartile(value2, q[1]), quartile(value2, q[2]), quartile(value2, q[3]), quartile(value2, q[4]), quartile(value2, q[5]), quartile(value2, q[6])])
696    }
697    var color3 = d3.scaleLinear()
698        .range(['rgba(79,243,194,' + divider + ')', 'rgba(62,223,182,' + divider + ')', 'rgba(47,202,169,' + divider + ')', 'rgba(38,166,153,' + divider + ')',
699            'rgba(30,131,137,' + divider + ')', 'rgba(21,96,121,' + divider + ')', 'rgba(32,46,53,' + divider + ')'])
700        .interpolate(d3.interpolateHcl);
701    if (!value3reverse) {
702        color3.domain([quartile(value3, q[6]), quartile(value3, q[5]), quartile(value3, q[4]), quartile(value3, q[3]), quartile(value3, q[2]), quartile(value3, q[1]), quartile(value3, q[0])])
703    } else {
704        color3.domain([quartile(value3, q[0]), quartile(value3, q[1]), quartile(value3, q[2]), quartile(value3, q[3]), quartile(value3, q[4]), quartile(value3, q[5]), quartile(value3, q[6])])
705    }
706    var color4 = d3.scaleLinear()
707        .range(['rgba(79,243,194,' + divider + ')', 'rgba(62,223,182,' + divider + ')', 'rgba(47,202,169,' + divider + ')', 'rgba(38,166,153,' + divider + ')',
708            'rgba(30,131,137,' + divider + ')', 'rgba(21,96,121,' + divider + ')', 'rgba(32,46,53,' + divider + ')'])
709        .interpolate(d3.interpolateHcl);
710    if (!value4reverse) {
711        color4.domain([quartile(value4, q[6]), quartile(value4, q[5]), quartile(value4, q[4]), quartile(value4, q[3]), quartile(value4, q[2]), quartile(value4, q[1]), quartile(value4, q[0])])
712    } else {
713        color4.domain([quartile(value4, q[0]), quartile(value4, q[1]), quartile(value4, q[2]), quartile(value4, q[3]), quartile(value4, q[4]), quartile(value4, q[5]), quartile(value4, q[6])])
714    }
715
716    // console.log(data)
717    function newtable() {
718        var table = d3.select(container)
719            .append('table').attr("y", 390).attr('class', tableClass).attr('id', "thistable")
720        var headers = table.append('thead').append('tr')
721            .selectAll('th')
722            .data(headersData).enter()
723            .append('th')
724            .html(function (d) {
725                if (d.includes("Compare"))
726                    return "Change"
727                if (d === "overallRaces")
728                    return "Overall<br>Races";
729                if (d === "overallScore")
730                    return "Overall<br>Score";
731                if (d === "overallRank")
732                    return "Overall<br>Rank";
733                if (d === "inraces")
734                    return "IN<br>Races";
735                if (d === "inscore")
736                    return "IN<br>Score";
737                if (d === "inrank")
738                    return "IN<br>Rank";
739                if (d === "spraces")
740                    return "SP<br>Races";
741                if (d === "spscore")
742                    return "SP<br>Score";
743                if (d === "sprank")
744                    return "SP<br>Rank";
745                if (d === "puraces")
746                    return "PU<br>Races";
747                if (d === "puscore")
748                    return "PU<br>Score";
749                if (d === "purank")
750                    return "PU<br>Rank";
751                if (d === "msraces")
752                    return "MS<br>Races";
753                if (d === "msscore")
754                    return "MS<br>Score";
755                if (d === "msrank")
756                    return "MS<br>Rank";
757                if (d === "rlraces")
758                    return "RL<br>Races";
759                if (d === "rlscore")
760                    return "RL<br>Score";
761                if (d === "rlrank")
762                    return "RL<br>Rank";
763                if (d === "mxraces")
764                    return "MX<br>Races";
765                if (d === "mxscore")
766                    return "MX<br>Score";
767                if (d === "mxrank")
768                    return "MX<br>Rank";
769                if (d === "ncraces")
770                    return "NC<br>Races";
771                if (d === "ncscore")
772                    return "NC<br>Score";
773                if (d === "ncrank")
774                    return "NC<br>Rank";
775                if (d === "rankDiff")
776                    return "";
777                if (d === "firstYear")
778                    return "First<br>Year";
779                if (d === "lastYear")
780                    return "Last<br>Year";
781                //
782                if (d === "winPercent")
783                    return "Win<br>%";
784                if (d === "podiumPercent")
785                    return "Podium<br>%";
786                if (d === "firstYear")
787                    return "First<br>Year";
788                if (d === "lastYear")
789                    return "Last<br>Year";
790                if (d === "shootingRank_Rank")
791                    return "Shooting<br>Rank";
792
793                if (d === "Non-Team Races")
794                    return "Non-Team<br>Races";
795
796                if (d === "World Cup Scores")
797                    return "World Cup<br>Scores";
798                if (d === "Team Races")
799                    return "Team/Relay<br>Races";
800                if (d === "Bibs")
801                    return "Yellow/Red<br>Bibs";
802
803                if (d === "averageShootingRank")
804                    return "Shooting<br>Rank (avg)";
805                if (d === "averageHitRateRank")
806                    return "Hit Rate<br>Rank (avg)";
807
808
809                if (d === "status")
810                    return "Status";
811                if (d === "compareCategory")
812                    return "";
813                if (d === "RaceRank")
814                    return "Rank";
815                if (d === "place")
816                    return "Place";
817                if (d === "coursedata")
818                    return "";
819                if (d === "seasonWCpoints")
820                    return "Score";
821
822                if (window.location.href.includes("search.html")) {
823                    let itemUp = d.charAt(0).toUpperCase() + d.slice(1).replace("2nd", "Second Place").replace("3rd", "Third Place").replace("op30SD", "sdsdsd")
824                    if (d.match(/[A-Z][a-z]+|[0-9]+/g) === null)
825                        return itemUp.replace("Wcpoints", "World Cup Points").replace("Tsdsdsd", "Top 30 SD");
826                    else {
827                        return itemUp.match(/[A-Z][a-z]+|[0-9]+/g).join(" ")
828                            .replace("Score", "zScore").replace("Second Place", "2nd place")
829                            .replace("Third Place", "3rd place").replace("Tsdsdsd", "Top 30 SD");
830                    }
831                }
832
833
834                if (d === "Time gain/loss per 1km")
835                    return "Time gain/loss<br>per 1km (in s)";
836                if (d === "Time gain/loss per 2.5km")
837                    return "Time gain/loss<br>per 2.5km (in s)";
838
839                if (window.location.href.includes("trends.html") && d === "name")
840                    return "Season";
841
842                if (d === "rangeTimePlusPenaltyTime")
843                    return "Range Time +<br>Penalty Time";
844                if (d === "seasons")
845                    return "Total<br>Seasons";
846                if (d === "overallTitles")
847                    return "Overall<br>Titles";
848                if (d === "firstSeason")
849                    return "First<br>Season";
850                if (d === "speed")
851                    return "Speed<br>(in km/h)";
852                if (d === "lastSeason")
853                    return "Last<br>Season";
854                if (d === "disciplineTitles")
855                    return "Discipline<br>Titles";
856                if (d === "overall2nd")
857                    return "2nd";
858                if (d === "overall3rd")
859                    return "3rd";
860                if (d === "discipline2nd")
861                    return "2nd";
862                if (d === "discipline3rd")
863                    return "3rd";
864
865                if (type === 113) {
866                    let tableheader = "";
867                    let itemUp = d.charAt(0).toUpperCase() + d.slice(1)
868                    if (d.match(/[A-Z][a-z]+|[0-9]+/g) === null)
869                        tableheader = itemUp.replace("Wcpoints", "World Cup Points");
870                    else
871                        tableheader = itemUp.match(/[A-Z][a-z]+|[0-9]+/g).join(" ").replace("Score", "zScore");
872                    return tableheader;
873                }
874
875                if (type === 5088) {
876
877                    if (d === "familyNameWinner")
878                        return "Winner<br>";
879                    if (d === "familyName2nd")
880                        return "2nd Place<br>";
881                    if (d === "familyName3rd")
882                        return "3rd Place<br>";
883
884                    if (d === "medianSpeed")
885                        return "Median<br>Speed<br>(in km/h)";
886                    if (d === "top10MedianSpeed")
887                        return "Top 10<br>Median<br>Speed<br>(in km/h)";
888                    if (d === "top30MedianSpeed")
889                        return "Top 30<br>Median<br>Speed<br>(in km/h)";
890                    if (d === "top30MedianBackWinner")
891                        return "Top 30 <br>Median back<br>Winner (in %)";
892                    if (d === "top30MedianBack2nd")
893                        return "Top 30 <br>Median back<br>2nd (in %)";
894                    if (d === "top30MedianBack3rd")
895                        return "Top 30 <br>Median back<br>3rd (in %)";
896
897                    if (d === "speedWinner")
898                        return "Speed<br>Winner<br>(in km/h)";
899                    if (d === "skiSpread15")
900                        return "Athletes <br>outside<br>15 sec of <br>Top 30 median";
901                    if (d === "skiSpread30")
902                        return "Athletes <br>outside<br>30 sec of <br>Top 30 median";
903
904
905                    if (d.startsWith("givenName"))
906                        return;
907                    if (d === "natWinner" || d === "nat2nd" || d === "nat3rd")
908                        return "Nat";
909
910                    if (d === "shootingPercentQuartile1")
911                        return "Lower<br>Quartile";
912                    if (d === "shootingPercentQuartile2")
913                        return "Median";
914                    if (d === "shootingPercentQuartile3")
915                        return "Upper<br>Quartile";
916
917                    if (d === "proneMedianShootingTime")
918                        return "Prone<br>Median<br>(in sec)";
919                    if (d === "standingMedianShootingTime")
920                        return "Standing<br>Median<br>(in sec)";
921
922                    if (d === "shootingTimeQuartile2")
923                        return "Median<br>Shooting Time<br>(in sec)";
924
925
926                }
927
928                if (type === 50 && d === "wcpoints")
929                    return "World<br>Cup<br>Points";
930                if (d === "wcpoints")
931                    return "World Cup Points"
932                if (type === 50 && d === "skiRank")
933                    return "Ski<br>Rank";
934                if (type === 50 && d === "shootingTimeRank")
935                    return "Shooting<br>Time<br>Rank";
936                if (type === 50 && d === "rangeTimeRank")
937                    return "Range<br>Time<br>Rank";
938
939                if (d === "totalRangeTime")
940                    return "Range<br>Time avg<br>(in sec)";
941                if (d === "totalShootingTime")
942                    return "Shooting<br>Time avg<br>(in sec)";
943
944                if (d === "hitRateMedianBack")
945                    return "back from<br>median<br>hit Rate<br>(in %)";
946                if (d === "hitRateAverageBack")
947                    return "back from<br>average<br>hit Rate<br>(in %)";
948                if (d === "hitRateZScore")
949                    return "hit Rate<br>zScore";
950                if (d === "PenaltyLoopAvg")
951                    return "Penalty<br>Loop<br>(avg)";
952                if (d.includes("BT")) {
953                    return d.substring(10, 14).replace("__", "").replace("CP10", '<span class="w10">W10</span>')
954                        .replace("CP0", "W") + "<br>" + d.slice(-2);
955                }
956                if (d.includes("_"))
957                    return d.replace("_", "<br>").replace("_", "<br>");
958
959                if (d.includes("Back")) {
960                    d = "back from<br>" + d.replace("Back", "")
961                        .replace("Median", " median")
962                        .replace("Average", " average") + "<br>(in %)";
963                }
964                if (d.includes("standing") && d.includes("Time"))
965                    d = "Standing<br>Time<br>(in sec)"
966                if (d.includes("prone") && d.includes("Time"))
967                    d = "Prone<br>Time<br>(in sec)"
968                // if (d.includes("total") && d.includes("Time") )
969                //     d =  "Total<br>Time<br>(in sec)"
970                if (typeof cat !== "undefined" && cat === "statsPerRace" && type === 113 && d === "name")
971                    return "Race";
972                if (typeof cat !== "undefined" && cat === "statsPerSeason" && type === 113 && d === "name")
973                    return "Season";
974                if (d.includes("Lane"))
975                    return "Lane"
976
977
978                if (d === "calculatedScore")
979                    return "cScore";
980                if (d === "score")
981                    return "Score";
982                if (d === "country")
983                    return "Country";
984                if (d.includes("Sh Time"))
985                    return "Time"
986                if (d === "hits")
987                    return "Total<br>hits";
988                if (d === "shots")
989                    return "Total<br>shots";
990                if (d === "skiRank" && type !== 113)
991                    return "Ski Rank<br>(avg)";
992                if (d === "shootingTimeRank" && type === 10)
993                    return "Sh Time<br>Rank<br>(avg)";
994                if (d === "rangeTimeRank" && type === 10)
995                    return "Rg Time<br>Rank<br>(avg)";
996                if (d === "averageRank")
997                    return "Rank<br>(avg)";
998
999                if (d === "projectedCourseTime" && type === 10)
1000                    return "Projected<br>Course Time";
1001                if (d === "projectedTimeLoss" && type === 10)
1002                    return "Projected<br>Time Loss<br>Shooting";
1003                if (d === "projectedRaceTime" && type === 10)
1004                    return "Projected<br>Total<br>Race Time";
1005
1006                if (type === 5088) {
1007
1008                    if (d === "familyNameWinner")
1009                        return "Winner<br>";
1010                    if (d === "familyName2nd")
1011                        return "2nd Place<br>";
1012                    if (d === "familyName3rd")
1013                        return "3rd Place<br>";
1014
1015                    if (d === "medianSpeed")
1016                        return "Median<br>Speed<br>(in km/h)";
1017                    if (d === "top10MedianSpeed")
1018                        return "Top 10<br>Median<br>Speed<br>(in km/h)";
1019                    if (d === "top30MedianSpeed")
1020                        return "Top 30<br>Median<br>Speed<br>(in km/h)";
1021                    if (d === "top30MedianBackWinner")
1022                        return "Top 30 <br>Median back<br>Winner<br(in %)";
1023                    if (d === "top30MedianBack2nd")
1024                        return "Top 30 <br>Median back<br>2nd<br>(in %)";
1025                    if (d === "top30MedianBack3rd")
1026                        return "Top 30 <br>Median back<br>3rd<br>(in %)";
1027
1028                    if (d === "speedWinner")
1029                        return "Speed<br>Winner<br>(in km/h)";
1030                    if (d === "skiSpread15")
1031                        return "Athletes outside<br>15 sec of<br>Top 30 median";
1032                    if (d === "skiSpread30")
1033                        return "Athletes outside<br>30 sec of<br>Top 30 median";
1034                    if (d === "top30SD")
1035                        return "Top 30 <br>Course<br>Time<br>SD";
1036
1037                    if (d.startsWith("givenName"))
1038                        return;
1039                    if (d === "natWinner" || d === "nat2nd" || d === "nat3rd")
1040                        return "Nat";
1041
1042                    if (d === "shootingPercentQuartile1")
1043                        return "Lower<br>Quartile";
1044                    if (d === "shootingPercentQuartile2")
1045                        return "Median";
1046                    if (d === "shootingPercentQuartile3")
1047                        return "Upper<br>Quartile";
1048
1049                    if (d === "proneMedianShootingTime")
1050                        return "Prone<br>Median<br>(in sec)";
1051                    if (d === "standingMedianShootingTime")
1052                        return "Standing<br>Median<br>(in sec)";
1053
1054                    if (d === "shootingTimeQuartile2")
1055                        return "Median<br>Shooting Time<br>(in sec)";
1056
1057
1058                }
1059
1060                if (d === "wcrank")
1061                    return "World Cup<br>Rank";
1062                if (d === "averagePoints")
1063                    return "Points<br>(avg)";
1064                if (d === "hitRateScore")
1065                    return "Hit Rate<br>Score";
1066                if (d === "skiSpeedScore")
1067                    return "Ski Speed<br>Score";
1068                if (d === "organizer")
1069                    return "Venue";
1070                if (d === "averageShootingTime")
1071                    return "Shooting<br>Time<br>(in sec)";
1072
1073                if (d === "rangeTimeScore")
1074                    return "Range Time<br>Score";
1075                if (d === "totalScore")
1076                    return "Total<br>Performance<br>Score";
1077                if (d === "points")
1078                    return "Points<br>(avg)";
1079                if (d === "shootingTime")
1080                    return "Shooting Time";
1081                if (d === "rangeTime")
1082                    return "Range Time";
1083                if (d === "weather")
1084                    return "";
1085                if (d === "raceRank")
1086                    return "Rank";
1087                if (d === "hitRate")
1088                    return "Hit Rate";
1089                if (d === "rank")
1090                    return "Rank";
1091                if (d === "bib")
1092                    return "Bib";
1093                if (d === "behind")
1094                    return "Behind";
1095                if (d === "totalTime")
1096                    return "Total Time";
1097                if (d === "sectorrank")
1098                    return "Sector Rank";
1099                if (d === "shootings")
1100                    return "Shootings";
1101                if (d === "total")
1102                    return "Total";
1103                if (d === "timeLoss")
1104                    return "Time Loss";
1105                if (d === "disciplineId")
1106                    return "Discipline";
1107                if (d === "shortDescription")
1108                    return "Description";
1109                if (d === "nat")
1110                    return "Nation";
1111                if (d === "gender")
1112                    return "Gender";
1113                if (d === "penaltyLoop")
1114                    return "Penalty Loop";
1115                if (d === "timeLossSprint")
1116                    return "Time Loss<br>Sprint";
1117                if (d === "timeLossPursuitMassStart")
1118                    return "Time Loss<br>Pursit/Mass Start";
1119                if (d === "timeLossIndividual")
1120                    return "Time Loss<br>Individual";
1121                if (type === 878 && d === "Rank")
1122                    return "Rank";
1123
1124
1125                if (d === "timeLossSprint")
1126                    return "Time Loss<br>Sprint";
1127                if (d === "30 Minutes before Start")
1128                    return "30 min.<br>before Start";
1129                if (d === "30 Minutes after Start")
1130                    return "30 min.<br>after Start";
1131                if (d === "At Start Time")
1132                    return "at<br>Start Time";
1133                if (d === "At End Time")
1134                    return "at<br>End Time";
1135
1136
1137                d = d.replace("skiing", "")
1138                    .replace("hitsProne", "Prone<br>hits")
1139                    .replace("shotsProne", "Prone<br>shots")
1140                    .replace("pronePercent", "Prone<br>hit rate<br>(in %)")
1141                    .replace("hitsStanding", "Standing<br>hits")
1142                    .replace("shotsStanding", "Standing<br>shots")
1143                    .replace("standingPercent", "Standing<br>hit rate<br>(in %)")
1144                    .replace("totalPercent", "Total<br>hit rate<br>(in %)")
1145                    .replace("shootingPercent", "")
1146                    .replace("shootingPercent", "")
1147                    .replace("ShootingTime", "")
1148                    .replace("RangeTime", "")
1149                    .replace("shootingTime", "")
1150                    .replace("wins", "Wins")
1151                    .replace("secondplaces", "2nd")
1152                    .replace("thirdplaces", "3rd")
1153                    .replace("podiums", "Podiums")
1154                    .replace("top5s", "Top5s")
1155                    .replace("top10s", "Top10s")
1156                    .replace("top20s", "Top20s")
1157                    .replace("top30s", "Top30s")
1158                    .replace("top40s", "Top40s")
1159                    .replace("outsideTop40", ">40")
1160                    .replace("lap", "LAP")
1161                    .replace("dnf", "DNF")
1162                    .replace("dsq", "DSQ")
1163                    .replace("rangeTime", "")
1164                    .replace("Percent", "")
1165                return d.replace("nation", "Nation")
1166                    .replace("races", "Races")
1167                    .replace("familyName", "Family\xa0Name")
1168                    .replace("givenName", "Given Name")
1169                    .replace("Rank", "Rank<br>(avg)")
1170                    .replace("ZScore", "Z-score")
1171            })
1172            .style("border-right", function (d) {
1173                if (d === "races" || d.name === "overallRank") {
1174                    return '1px solid #a8a8a8';
1175                }
1176            })
1177            .on('click', function (d, i) {
1178                if (typeof fixedTableHeader !== "undefined" && fixedTableHeader === true && typeof fixedTableColums !== "undefined" && fixedTableColums === true) return;
1179                headers.attr('class', 'header');
1180                if (headersboolean[headersData.indexOf(d)]) {
1181                    rows.sort(function (a, b) {
1182                        if (a[d] !== null && a[d] !== undefined) a[d] = a[d].toString().includes("__") ? a[d].substr(0, a[d].indexOf("__")) : a[d];
1183                        if (b[d] !== null && b[d] !== undefined) b[d] = b[d].toString().includes("__") ? b[d].substr(0, b[d].indexOf("__")) : b[d];
1184                        let result = 0;
1185                        if (isNaN(Number(a[d]))) {
1186                            if (b[d] < a[d])
1187                                result = -1;
1188                            else if (b[d] > a[d])
1189                                result = 1;
1190                        } else {
1191                            if (Number(b[d]) < Number(a[d]))
1192                                result = -1;
1193                            else if (Number(b[d]) > Number(a[d]))
1194                                result = 1;
1195                        }
1196                        if (a[d] === undefined || a[d] === "" || a[d] === "null__+null__0" || a[d] === "__+__undefined" || a[d] === "null__+null" || a[d] === "null__null" || a[d] === "null" || a[d] === "__" || a[d] === "LAP" || a[d] === "DNF" || a[d] === "DNS" || a[d] === "DSQ" || a[d].toString().indexOf('\u2063') >= 0 || a[d] === "") result = -1;
1197                        if (b[d] === undefined || b[d] === "" || b[d] === "null__+null__0" || b[d] === "__+__undefined" || b[d] === "null__+null" || b[d] === "null__null" || b[d] === "null" || b[d] === "__" || b[d] === "LAP" || b[d] === "DNF" || b[d] === "DNS" || b[d] === "DSQ" || b[d].toString().indexOf('\u2063') >= 0 || b[d] === "") result = 1;
1198                        return result;
1199                    });
1200                    headersboolean[headersData.indexOf(d)] = false;
1201                    this.className = 'aes';
1202                } else {
1203                    rows.sort(function (a, b) {
1204                        if (a[d] !== null && a[d] !== undefined) a[d] = a[d].toString().includes("__") ? a[d].substr(0, a[d].indexOf("__")) : a[d];
1205                        if (b[d] !== null && b[d] !== undefined) b[d] = b[d].toString().includes("__") ? b[d].substr(0, b[d].indexOf("__")) : b[d];
1206                        let result = 0;
1207                        if (isNaN(Number(a[d]))) {
1208                            if (b[d] < a[d])
1209                                result = 1;
1210                            else if (b[d] > a[d])
1211                                result = -1;
1212
1213                        } else {
1214                            if (Number(b[d]) < Number(a[d]))
1215                                result = 1;
1216                            else if (Number(b[d]) > Number(a[d]))
1217                                result = -1;
1218                        }
1219                        if (a[d] === undefined || a[d] === "" || a[d] === "null__+null__0" || a[d] === "__+__undefined" || a[d] === "null__+null" || a[d] === "null__null" || a[d] === "null" || a[d] === "__" || a[d] === "LAP" || a[d] === "DNF" || a[d] === "DNS" || a[d] === "DSQ" || a[d].toString().indexOf('\u2063') >= 0 || a[d] === "") result = 1;
1220                        if (b[d] === undefined || b[d] === "" || b[d] === "null__+null__0" || b[d] === "__+__undefined" || b[d] === "null__+null" || b[d] === "null__null" || b[d] === "null" || b[d] === "__" || b[d] === "LAP" || b[d] === "DNF" || b[d] === "DNS" || b[d] === "DSQ" || b[d].toString().indexOf('\u2063') >= 0 || b[d] === "") result = -1;
1221                        return result;
1222                    });
1223                    headersboolean[headersData.indexOf(d)] = true;
1224                    this.className = 'des';
1225                }
1226                fixedTableColumns()
1227
1228            })
1229            .attr('style', function (d) {
1230                if (d === "Shooting 1" || d === "Shooting 2" || d === "Shooting 3" || d === "Shooting 4")
1231                    return "text-align: center;";
1232            })
1233        return table;
1234    }
1235
1236    var table = newtable();
1237    filteredRaces = function (search, keys, data) {
1238        var lowSearch = search.toLowerCase();
1239        return data.filter(function (race) {
1240            return keys.some(key =>
1241                String(race[key]).toLowerCase().startsWith(lowSearch)
1242            );
1243        });
1244    }
1245
1246    function searchfunction(searchbar) {
1247        /**  search functionality **/
1248        d3.select("#SearchBar" + searchbar)
1249            .on("change input", function () {
1250                if (cat === "loops")
1251                    changeURL();
1252                else
1253                    filterfunction();
1254
1255            })
1256    }
1257
1258
1259    function filterfunction() {
1260
1261        let l = 0;
1262        let j = (type > 100 || cat === "analysis") ? 1 : 0;
1263        if (window.location.href.includes("scores.html")) j = 0;
1264        if (window.location.href.includes("races.html") || window.location.href.includes("scores.html") || window.location.href.includes("records.html")) {
1265            if (headersData.indexOf("nat") !== -1) l = headersData.indexOf("nat") - 3;
1266            if (headersData.indexOf("nation") !== -1) l = headersData.indexOf("nation") - 3;
1267            if (headersData.indexOf("Nation") !== -1) l = headersData.indexOf("nation") - 3;
1268        }
1269        var filter1;
1270        if ($("#SearchBarFamilyName").val() !== undefined && !window.location.href.includes("athletes.html") ) {
1271             filter1 = filteredRaces($("#SearchBarFamilyName").val(), [headers[1 + j]], data)
1272        } else
1273            filter1 = data
1274
1275        var filter2 = filter1;
1276
1277        if (window.location.href.includes("races.html")) l = l - 1;
1278        if (searchable === "searchable2") {
1279            if (window.location.href.includes("venues.html")) {
1280                let ind = headers.indexOf("Season")
1281                filter2 = filteredRaces($("#SearchBarNation").val(), [headers[ind]], filter1)
1282
1283            } else{
1284                console.log(headers[3 + j + l])
1285                filter2 = filteredRaces($("#SearchBarNation").val(), [headers[3 + j + l]], filter1)
1286            }
1287        }
1288        var searched_data = filter2;
1289        searched_data.sort(function (a, b) {
1290            return a.No - b.No;
1291        });
1292        table.remove()
1293        var table2 = newtable();
1294        rows = newrows(table2, searched_data);
1295        table = table2;
1296
1297        try{
1298            if (window.location.href.includes("athletes.html") && cat === "results"){
1299                $("#thistable").find("tbody tr").eq(filter2.length-2).addClass("bottomrow")
1300                $("#thistable").find("tbody tr").eq(filter2.length-1).addClass("bottomrow")
1301                $( ".bottomrow" ).children().css( "background", "none" )
1302            }
1303        }catch (e) { }
1304    }
1305
1306    searchfunction("FamilyName");
1307    if (searchable === "searchable2")
1308        searchfunction("Nation");
1309
1310    function newrows(table, data) {
1311        var rows = table.append('tbody').selectAll('tr')
1312            .data(data, function (d) {
1313                return d.RaceId;
1314            }).enter().append('tr')
1315            .attr("class", function (d) {
1316            });
1317        var row_entries = rows.selectAll('td')
1318            .data(function (d, i) {
1319                return headersData.map(function (k) {
1320                    let code = d.nation === undefined ? d.nat : d.nation;
1321                    if (d.nation === "" && d.nat !== "") code = d.nat;
1322                    if (type === 50) {
1323                        return {
1324                            'value': d[k],
1325                            'name': k,
1326                            'firstname': d.givenName,
1327                            'nat': code,
1328                            ibuId: d.ibuid,
1329                            raceId: d.raceId,
1330                            bottomrow: data.length - i < 3
1331                        };
1332                    } else if (type === 5088) {
1333                        return {
1334                            'value': d[k], 'name': k, 'firstname': d.givenName, 'nat': code, venue: d.organizer,
1335                            ibuId1: d.ibuidWinner, ibuId2: d.ibuid2nd, ibuId3: d.ibuid3rd,
1336                            raceId: d.raceId, bottomrow: data.length - i < 3
1337                        };
1338                    } else if (type === 113) {
1339                        return {'value': d[k], 'name': k, 'firstname': d.givenName, 'nat': code, raceId: d.name,};
1340                    } else if (type === 112) {
1341                        return {
1342                            'value': d[k],
1343                            'name': k,
1344                            'firstname': d.givenName,
1345                            'nat': code,
1346                            'no': d.No,
1347                            ibuId: d.ibuid,
1348                            raceId: d.raceId,
1349                            color: d.bibcolor,
1350                            isRelay: d.country
1351                        };
1352                    } else if (highlightplaces === "highlight") {
1353                        if (typeof currentGender !== "undefined") {
1354                            if (window.location.href.includes("athletes.html") || window.location.href.includes("athletes.html"))
1355                                return {
1356                                    'value': d[k],
1357                                    'name': k,
1358                                    'firstname': d.givenName,
1359                                    'nat': code,
1360                                    'no': d.No,
1361                                    raceId: d[k + "_Id"],
1362                                    'year': d.year + "&gender=" + athletedata.gender + "&&type=athletes&discipline=" +
1363                                        k.substring(0, 2).toUpperCase().replace("OV", "NonTeam")
1364                                };
1365                        }
1366                        if (typeof genderString !== "undefined") {
1367                            if (window.location.href.includes("teams.html"))
1368                                return {
1369                                    'value': d[k],
1370                                    'name': k,
1371                                    'firstname': d.givenName,
1372                                    'nat': code,
1373                                    'no': d.No,
1374                                    raceId: d[k + "_Id"],
1375                                    'year': d.year + "&gender=" + genderString + "&&type=teams&discipline=" +
1376                                        k.substring(0, 2).toUpperCase()
1377                                };
1378                        } else
1379                            return {
1380                                'value': d[k],
1381                                'name': k,
1382                                'firstname': d.givenName,
1383                                'nat': code,
1384                                'no': d.No,
1385                                raceId: d[k + "_Id"]
1386                            };
1387                    } else if (type === 555 || type === 556) {
1388                        if (disciplineString !== undefined && disciplineString === "NC" && d[k] !== undefined && d[k] !== null && d[k].athlete1 !== undefined) {
1389                            let cellvalue = d[k];
1390                            try {
1391                                if (showRanks === "rank") {
1392                                    if (d[k].score3 !== undefined && d[k].score3 !== null) {
1393                                        let scoresstring = d[k].rank1 + "##" + d[k].rank2 + "##" + d[k].rank3
1394                                        cellvalue = scoresstring;
1395                                    } else if (d[k].score2 !== undefined && d[k].score2 !== null) {
1396                                        let scoresstring = d[k].rank1 + "##" + d[k].rank2
1397                                        cellvalue = scoresstring;
1398                                    } else if (d[k].score1 !== undefined && d[k].score1 !== null) {
1399                                        let scoresstring = d[k].rank1
1400                                        cellvalue = scoresstring;
1401                                    }
1402                                } else {
1403                                    if (d[k].score !== undefined) cellvalue = d[k].score;
1404
1405                                }
1406                            } catch (e) {
1407                            }
1408
1409
1410                            return {
1411                                'value': cellvalue,
1412                                'name': k,
1413                                'firstname': d.givenName,
1414                                'nat': code,
1415                                'no': d.No,
1416                                ibuId: d.ibuid,
1417                                raceId: k,
1418                                'athlete1': d[k].athlete1,
1419                                'athlete1name': d[k].athlete1name,
1420                                'score1': d[k].score1,
1421                                'rank1': d[k].rank1,
1422                                'athlete2': d[k].athlete2,
1423                                'athlete2name': d[k].athlete2name,
1424                                'score2': d[k].score2,
1425                                'rank2': d[k].rank2,
1426                                'athlete3': d[k].athlete3,
1427                                'athlete3name': d[k].athlete3name,
1428                                'score3': d[k].score3,
1429                                'rank3': d[k].rank3,
1430                                min1: d.ncmin1,
1431                                min2: d.ncmin2,
1432                                min3: d.ncmin3,
1433                                min4: d.ncmin4,
1434                                min5: d.ncmin5,
1435                                min6: d.ncmin6,
1436                                min1Id: d.ncmin1Id,
1437                                min2Id: d.ncmin2Id,
1438                                min3Id: d.ncmin3Id,
1439                                min4Id: d.ncmin4Id,
1440                                min5Id: d.ncmin5Id,
1441                                min6Id: d.ncmin6Id
1442
1443                            };
1444                        } else if (d[k] !== undefined) {
1445                            let cellvalue = d[k];
1446                            try {
1447                                if (d[k].score !== undefined) cellvalue = d[k][showRanks];
1448                            } catch (e) {
1449                            }
1450                            return {
1451                                'value': cellvalue,
1452                                'name': k,
1453                                'firstname': d.givenName,
1454                                'nat': code,
1455                                'no': d.No,
1456                                ibuId: d.ibuid,
1457                                raceId: k,
1458                                color: d.bibcolor,
1459                                min1: d.min1,
1460                                min2: d.min2,
1461                                min3: d.min3,
1462                                min4: d.min4,
1463                                min5: d.min5,
1464                                min6: d.min6,
1465                                min1Id: d.min1Id,
1466                                min2Id: d.min2Id,
1467                                min3Id: d.min3Id,
1468                                min4Id: d.min4Id,
1469                                min5Id: d.min5Id,
1470                                min6Id: d.min6Id
1471                            };
1472                        }
1473                        ;
1474
1475
1476                    } else if (type === 878 || type == 170170) {
1477                        let classname = d.givenName === undefined ? d.country !== undefined : d.givenName.substring(0, 1) + d.familyName;
1478                        return {
1479                            'value': d[k],
1480                            'name': k,
1481                            'firstname': d.givenName,
1482                            'nat': code,
1483                            'no': d.No,
1484                            ibuId: d.ibuid,
1485                            raceId: d.raceId,
1486                            color: d.bibcolor,
1487                            isRelay: d.country,
1488                            target1: d['Target 1'],
1489                            target2: d['Target 2'],
1490                            target3: d['Target 3'],
1491                            target4: d['Target 4'],
1492                            rank: d.rank,
1493                            image1: d['Image 1'],
1494                            image2: d['Image 2'],
1495                            image3: d['Image 3'],
1496                            image4: d['Image 4'],
1497                            shtime1: d['Sh Time 1'],
1498                            shtime2: d['Sh Time 2'],
1499                            shtime3: d['Sh Time 3'],
1500                            shtime4: d['Sh Time 4'],
1501                            class: classname,
1502                            interval1: d.interval1,
1503                            interval2: d.interval2
1504                        };
1505                    } else if (type === 9858) {
1506                        return {
1507                            'value': d[k],
1508                            'name': k,
1509                            'firstname': d.givenName,
1510                            'nat': code,
1511                            'no': d.No,
1512                            videos: videos[d.raceId],
1513                            ibuId: d.ibuid,
1514                            raceId: d.raceId,
1515                            color: d.bibcolor,
1516                            isRelay: d.country
1517                        };
1518                    } else if ((type === 555 || type === 556) && k.includes("BT")) {
1519                        return {
1520                            'value': d[k],
1521                            'name': k,
1522                            'firstname': d.givenName,
1523                            'nat': code,
1524                            'no': d.No,
1525                            ibuId: d.ibuid,
1526                            raceId: k,
1527                            color: d.bibcolor,
1528                            min1: d.min1,
1529                            min2: d.min2,
1530                            isRelay: d.country
1531                        };
1532                    } else {
1533                        return {
1534                            'value': d[k],
1535                            'name': k,
1536                            'firstname': d.givenName,
1537                            'nat': code,
1538                            'no': d.No,
1539                            ibuId: d.ibuid,
1540                            raceId: d.raceId,
1541                            color: d.bibcolor,
1542                            isRelay: d.country
1543                        };
1544                    }
1545                });
1546            }).enter()
1547            .append('td')
1548            .attr('data-th', function (d) {
1549                if (d !== undefined)
1550                    return d.name;
1551            })
1552            .attr('class', function (d) {
1553                if (d === undefined) return;
1554                if (highlightplaces === "highlight") {
1555                    if (!d.bottomrow && (d.value === "1" || d.value === "=1." || d.value === "2" || d.value === "=2." || d.value === "3" || d.value === "=3."
1556                        || ((d.name === "RaceRank") || (d.name === "overallRank") || d.name.includes("rank")) && (d.value === 1 || d.value === 2 || d.value === 3)))
1557                        return "tablecell championshipmedal";
1558                }
1559                if (d.name === "rankDiff" && d.value !== undefined && d.value < 0)
1560                    return "tablecell diffcolumnUp";
1561                if (d.name === "rankDiff" && d.value !== undefined && d.value > 0)
1562                    return "tablecell diffcolumnDown";
1563                if (d.name === "rankDiff" && d.value !== undefined && d.value == 0)
1564                    return "tablecell diffcolumn0";
1565                if (d.name === "nat" || d.name === "nation" || d.name === "natWinner" || d.name === "nat2nd" || d.name === "nat3rd")
1566                    return "tablecell nationcolumn";
1567                if (d.name === "Shooting 1")
1568                    return "tablecell targetimage target" + d.target1;
1569                if (d.name === "Shooting 2" && (racedata.disciplineId === 'PU' || racedata.disciplineId === 'MS' || racedata.disciplineId === 'M6'))
1570                    return "tablecell targetimage target" + d.target2;
1571                if (d.name === "Shooting 2" && (racedata.disciplineId !== 'PU' && racedata.disciplineId !== 'MS' && racedata.disciplineId !== 'M6'))
1572                    return "tablecell targetimage targets" + d.target2;
1573                if (d.name === "Shooting 3" && (racedata.disciplineId === 'IN'))
1574                    return "tablecell targetimage target" + d.target3;
1575                if (d.name === "Shooting 3" && (racedata.disciplineId !== 'IN'))
1576                    return "tablecell targetimage targets" + d.target3;
1577                if (d.name === "Shooting 4")
1578                    return "tablecell targetimage targets" + d.target4;
1579                if (d.name === "bib" && (d.color === "o" || d.color === "yr"))
1580                    return "tablecell redyellow";
1581
1582                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target11") return "tablecell targetimage target" + sh1["Target 1"];
1583                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target12") return "tablecell targetimage target" + sh2["Target 1"];
1584                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target21") return "tablecell targetimage target" + sh1["Target 2"];
1585                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target22") return "tablecell targetimage target" + sh2["Target 2"];
1586                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target31") return "tablecell targetimage target" + sh1["Target 3"];
1587                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target32") return "tablecell targetimage target" + sh2["Target 3"];
1588                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target41") return "tablecell targetimage target" + sh1["Target 4"];
1589                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target42") return "tablecell targetimage target" + sh2["Target 4"];
1590                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target51") return "tablecell targetimage target" + sh1["Target 5"];
1591                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target52") return "tablecell targetimage target" + sh2["Target 5"];
1592                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target61") return "tablecell targetimage target" + sh1["Target 6"];
1593                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target62") return "tablecell targetimage target" + sh2["Target 6"];
1594                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target71") return "tablecell targetimage target" + sh1["Target 7"];
1595                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target72") return "tablecell targetimage target" + sh2["Target 7"];
1596                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target81") return "tablecell targetimage target" + sh1["Target 8"];
1597                if (d.value !== undefined && d.value !== null && d.value.toString() === "-__Target82") return "tablecell targetimage target" + sh2["Target 8"];
1598
1599                if (d.isRelay !== undefined && type === 55)
1600                    return "tablecell countryrow";
1601                if ((type === 555 || type === 556 || type === 878 || type === 1) && typeof relativeathlete !== "undefined" && d.ibuId === relativeathlete) {
1602                    return "tablecell highlightselected"
1603                }
1604                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min1Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1605                    return "tablecell strikeoutscore"
1606                }
1607                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min2Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1608                    return "tablecell strikeoutscore"
1609                }
1610                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min3Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1611                    return "tablecell strikeoutscore"
1612                }
1613                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min4Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1614                    return "tablecell strikeoutscore"
1615                }
1616                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min5Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1617                    return "tablecell strikeoutscore"
1618                }
1619                if ((type === 555 || type === 556) && eliminateDropped === "drop" && d.name === d.min6Id && typeof cat !== undefined && cat === "table" && !isNaN(d.value)) {
1620                    return "tablecell strikeoutscore"
1621                } else
1622                    return "tablecell"
1623            })
1624            .html(function (d) {
1625                if (d === undefined || d.value === undefined || d.value === null || d.value === "" || d.value === "\u2063" || d.value === "__+" ||
1626                    d.value === "__" || d.value === "null__+null" ||  d.value === "__+__undefined" || d.value === "____undefined" ||  d.value === "null__+null__0" || d.value === "0__+0" || d.value.toString().startsWith("-__"))
1627                    return;
1628                if (d.value !== undefined && d.value !== null && d.value.toString().includes("s\xa0\xa0")) {
1629                    return;
1630                }
1631
1632                if (d.name.includes("Day") && (typeof d.value === 'string'))
1633                    return d.value;
1634
1635                if (d.name === "rankDiff") {
1636                    if (d.value == 0)
1637                        return;
1638                    if (d.value.toString().includes("+"))
1639                        return d.value.toString().replace("+", "")
1640                    if (d.value.toString().includes("-"))
1641                        return d.value.toString().replace("-", "")
1642                    else
1643                        return d.value;
1644                }
1645                if (type === 174 || type === 11210) {
1646                    if (d.value != 0)
1647                        return d.value;
1648                    else
1649                        return
1650                }
1651                if (d.name === "rank" && !isNaN(d.value) && d.value > 999)
1652                    return "";
1653                if (d.name === "rank" && type === 10)
1654                    return d.value;
1655                if (window.location.href.includes("comparisons.html")) {
1656                    if (d.name.includes("Compare")) {
1657                        let value = d.name.includes("Rank") || d.name.includes("Percent") ? d.value.toFixed(1) : d.value.toFixed(2);
1658                        if (cat === "shootingEfficiency") value = d.value.toString().toHHMMSS()
1659                        if (d.value > 0)
1660                            return "+" + value;
1661                        else
1662                            return value;
1663                    }
1664                }
1665                if (type === 5088) {
1666                    if (d.name.includes("organizer") || d.name.includes("year") || d.name.includes("Year") || d.name.includes("races") || d.name.includes("hits")
1667                        || d.name.includes("shots"))
1668                        return d.value;
1669                    if (!isNaN(d.value)) {
1670                        return Number(d.value).toFixed(1)
1671                    } else return d.value;
1672                }
1673                if (type === 9858) {
1674                    let baseUrl = "https://www.eurovisionsports.tv/ibu/#"
1675                    let base = "footprint"
1676                    if (embeddedvideos) {
1677                        base = "frameUrl"
1678                        baseUrl = ""
1679                    }
1680                    if (d.name == "Full race") {
1681                        let url = d.videos[base + "Replay"];
1682                        if (url === null) return ""
1683                        baseUrl = url.length > 11 ? "" : baseUrl;
1684                        return '<div class="videolinkdiv"><b><a href="' + baseUrl + url + '" target="_blank">Race</a></b></div>';
1685                    }
1686
1687                    if (d.name == "Interviews") {
1688                        let url = d.videos[base + "Press"];
1689                        if (url === null) return ""
1690                        return '<div class="videolinkdiv"><a href="' + baseUrl + url + '" target="_blank">Interviews</a></div>';
1691                    }
1692                    if (d.name == "Hightlights") {
1693                        let url = d.videos[base + "Highlights"];
1694                        if (url === null) return ""
1695                        return '<div class="videolinkdiv"><a href="' + baseUrl + url + '" target="_blank">Hightlights</a></div>';
1696                    }
1697                    if (d.name == "Zeroing") {
1698                        let url = d.videos[base + "Zeroing"];
1699                        if (url === null) return ""
1700                        return '<div class="videolinkdiv"><a href="' + baseUrl + url + '" target="_blank">Zeroing</a></div>';
1701                    } else
1702                        return d.value;
1703                }
1704                if (type === 5235) {
1705                    if (!isNaN(d.value) && !d.name.includes("Year")) {
1706                        return d.value.toLocaleString()
1707                    } else return d.value;
1708                }
1709                if (type === 1) {
1710                    if (d.name === "skiRank")
1711                        return d.value.toFixed(1);
1712                }
1713                if (d.name === "wcrank")
1714                    return d.value.toFixed(0);
1715                if (d.name === "familyName")
1716                    return '<a href="/athletes.html?ibuId=' + d.ibuId + '">' + d.value + '</a>';
1717                if (d.name === "country" || d.name === "Country") {
1718                    let type = typeof racedata !== "undefined" ? racedata.catId.substring(1, 2) : genderString
1719                    return '<a href="/teams.html?ibuId=BT' + d.ibuId.substring(2, 5) + "-" + type + '">' + d.value + '</a>';
1720                }
1721                if (d.name === "speed" || (d.name === "km/h" && typeof relativeathlete !== "undefined" && relativeathlete === "total"))
1722                    return Number(d.value).toFixed(2);
1723                if (type === 113) {
1724
1725                    if (d.name === "Event" || d.name === "Discipline" || d.name === "Season" || d.name === "No")
1726                        return d.value;
1727                    if (d.name === "name" && cat !== undefined && cat === "statsPerSeason" && d.value.toString().includes(".")) {
1728                        let thisyear = d.value.toString().substring(0, 4);
1729                        let seasonstring = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
1730                        if (d.value.toString().endsWith(".01")) return seasonstring + " - Tri. 1";
1731                        if (d.value.toString().endsWith(".25")) return seasonstring + " - Tri. 2";
1732                        if (d.value.toString().endsWith(".5")) return seasonstring + " - Champ.";
1733                        if (d.value.toString().endsWith(".75")) return seasonstring + " - Tri. 3";
1734                    }
1735                    if (cat !== undefined && cat !== "statsPerRace" && d.name === "name")
1736                        return (d.value < 2000) ? d.value - 1 + "–" + (d.value.toString().slice(-2)) : d.value - 1 + "–" + (d.value.toString().slice(-2));
1737                    if (d.name === "name" && cat !== undefined && cat === "statsPerSeason")
1738                        return (d.value < 2000) ? d.value - 1 + "–" + (d.value.toString().slice(-2)) : d.value - 1 + "–" + (d.value.toString().slice(-2));
1739                    if (cat !== undefined && cat === "statsPerRace") {
1740                        if (d.name === "name") {
1741                            let seasonstring = "";
1742                            if (d.value.substring(2, 3) > 4)
1743                                seasonstring = "19" + d.value.substring(2, 4) + "–" + d.value.substring(4, 6);
1744                            else
1745                                seasonstring = "20" + d.value.substring(2, 4) + "–" + d.value.substring(4, 6);
1746                            return seasonstring + ": " + d.value.substring(10, 14).replace("__", "").replace("CP0", "WC ") +
1747                                " " + d.value.slice(-2);
1748                        }
1749                        if (loopstable.includes("points") || loopstable.includes("rank") || loopstable.includes("Rank") || loopstable.includes("total")) {
1750                            if ((!isNaN(d.value) && (d.value - Math.floor(d.value)) !== 0) || d.name.includes("ShootingTime")
1751                                || d.name.includes("RangeTime") || d.name.includes("totalPercent") || d.name.includes("pronePercent") || d.name.includes("standingPercent"))
1752                                return d.value.toFixed(1);
1753                            if (!isNaN(d.value))
1754                                return d.value.toFixed(0);
1755                        }
1756                    }
1757                    if ((!d.name.includes("Rank") && !d.name.includes("rank")) || cat === "statsPerSeason")
1758                        return d.value.toFixed(1);
1759                    if (!isNaN(d.value) && (d.value - Math.floor(d.value)) !== 0)
1760                        return d.value.toFixed(1);
1761                    if (!isNaN(d.value))
1762                        return d.value.toFixed(0);
1763                }
1764
1765                if (type === 4) {
1766                    if (d.name === "averagePoints" || d.name === "averageRank")
1767                        return d.value.toFixed(1);
1768                    if (d.name === "wcrank")
1769                        return d.value.toFixed(0);
1770                    if (typeof d.value !== 'string' && d.name !== "No" && d.name !== "races")
1771                        return d.value.toFixed(2);
1772
1773                }
1774                if (type === 111) {
1775                    if (d.name === "Season" && cat !== undefined && cat === "resultsSeason" && d.value.toString().includes(".")) {
1776                        let thisyear = d.value.toString().substring(0, 4);
1777                        let seasonstring = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
1778                        if (d.value.toString().endsWith(".01")) return seasonstring + " - Tri. 1";
1779                        if (d.value.toString().endsWith(".25")) return seasonstring + " - Tri. 2";
1780                        if (d.value.toString().endsWith(".5")) return seasonstring + " - Champ.";
1781                        if (d.value.toString().endsWith(".75")) return seasonstring + " - Tri. 3";
1782                    }
1783                    if (type === 111 && d.name === "Season")
1784                        return (d.value < 2000) ? d.value - 1 + "–" + (d.value.toString().slice(-2)) : d.value - 1 + "–" + (d.value.toString().slice(-2));
1785                }
1786                if (type === 112 && d.name.includes("%"))
1787                    return d.value.toFixed(1).toString() + "%";
1788                if (type === 112 && d.name.includes("age")){
1789
1790                    let age = Math.floor((d.value / 365.2425) * 10) / 10
1791                    if (age < 12 || isNaN(age)) e.Age = ""
1792                    return d.value + " (" + age.toFixed(1) + "y)"
1793                }
1794
1795
1796                let teamloopchart = typeof teamloopdata !== "undefined" && teamloopdata === true;
1797                let sel = d3.select(this).attr("class").includes("highlightselected") || teamloopchart;
1798
1799                if (d.name === "gender" && d.value === "A")
1800                    return "All";
1801                if (type === 111 || type === 112 || type === 113)
1802                    return d.value;
1803                if (d.value === "LAP")
1804                    return d.value;
1805                if (d.name === "behind" && d.value > 0)
1806                    return "+" + d.value.toString().toHHMMSS();
1807                if (d.name === "Total Time" && d.value > 0 && typeof relativeathlete !== "undefined" && relativeathlete !== "total" && !sel)
1808                    return "+" + d.value.toString().toHHMMSS();
1809                if (d.name === "Total Time" && d.value < 0 && typeof relativeathlete !== "undefined" && relativeathlete !== "total")
1810                    return "-" + d.value.toString().replace("-", "").toHHMMSS();
1811                if (d.name === "behind" && d.value === 0)
1812                    return;
1813                if (d.name === 'Bib' || d.name === "raceRank")
1814                    return d.value;
1815                if (d.name === 'gender')
1816                    if (d.value === 'W')
1817                        return 'Women';
1818                if (d.value === 'M')
1819                    return 'Men';
1820                if (d.value === 'X')
1821                    return 'Mixed';
1822                if (type === 50) {
1823                    if (d.name.includes("Percent") || d.name.includes("Back") || d.name.includes("total"))
1824                        return d.value.toFixed(1);
1825                    else
1826                        return d.value.toString().replace("BTSWRLCP", "WC").replace("BTSWRLCH", "WCH").replace("BTSWRLOG", "Olympics");
1827                }
1828                if (d.name.includes("zScore"))
1829                    return d.value.toFixed(2);
1830                if (d.value.toString().includes("__")) {
1831                    if (showBehind === "Behind") {
1832                        let timearray = d.value.toString().split("__")
1833                        if (Number(timearray[1]) === 0)
1834                            return "<b>" + timearray[0].toHHMMSS() + "<b>";
1835                        else
1836                            return "+" + timearray[1].toHHMMSS();
1837                    }
1838                    if (showBehind !== "Behind") {
1839                        let timearray = d.value.toString().split("__")
1840                        if (Number(timearray[1]) === 0)
1841                            return "<b>" + timearray[0].toHHMMSS() + "<b>";
1842                        else
1843                            return timearray[0].toHHMMSS();
1844                    }
1845                }
1846                if (d.value.toString().includes("##")) {
1847                    let timearray = d.value.toString().split("##")
1848                    if (timearray.length === 3)
1849                        return timearray[0] + "<br>" + timearray[1] + "<br>" + timearray[2];
1850                    else
1851                        return timearray[0] + "<br>" + timearray[1];
1852                }
1853                if (type === 878) {
1854                    if (d.name.includes("sectorrank"))
1855                        return sel ? d.value : (relativeathlete !== "total" && d.value > 0) ? "+" + d.value : d.value;
1856                    if (d.name.includes("h Time"))
1857                        return d.value.toString().toHHMMSS();
1858                    if ((d.name.includes("Time") || d.name === "Behind") && d.value.toString().includes("-"))
1859                        return "-" + d.value.toString().replace("-", "").toHHMMSS();
1860                    if ((d.name.includes("Time") || d.name === "Behind") && (typeof d.value === 'string') && d.value.includes("+"))
1861                        return "+" + d.value.toString().replace("+", "").toHHMMSS();
1862                    if (d.name === "Behind" && Number(d.value) > 0)
1863                        return sel ? "0.0" : "+" + d.value.toString().replace("+", "").toHHMMSS();
1864                    if (d.name.includes("Sector") && relativeathlete === "total")
1865                        return d.value.toString().replace("+", "").toHHMMSS();
1866                    if (type === 878 && d.name.includes("Time")){
1867                        return sel ? d.value.toString().replace("+", "").toHHMMSS() : (relativeathlete !== "total" && d.value > 0) ? "+" + d.value.toString().replace("+", "").toHHMMSS() : d.value.toString().replace("+", "").toHHMMSS();
1868
1869                    }
1870                    if (d.name.includes("Time") && (typeof d.value === 'string'))
1871                        return d.value.toString().replace("+", "").toHHMMSS();
1872                    if (!d3.select(this).attr("class").includes("highlightselected") && d.name.includes("Time") && d.value > 0)
1873                        return "+" + d.value.toString().replace("-", "").toHHMMSS();
1874                    if (d.name === "Behind" && (typeof d.value === 'string'))
1875                        return "+" + d.value.toString().replace("+", "").toHHMMSS();
1876                    if ((d.name.includes("Time") || (d.name === "Behind")))
1877                        return d.value.toString().toHHMMSS();
1878                    if (d.name.includes("Time"))
1879                        return d.value.toString().toHHMMSS();
1880                }
1881
1882
1883                if (type === 170 && d.name.includes("Isolated"))
1884                    return d.value.toString().toHHMMSS();
1885
1886                if (type === 170 || type === 17170 || type === 555 || type === 556 || type === 999 || type === 878 || type === 1119)
1887                    return d.value;
1888                if (d.name === "rangeTime" || d.name === "shootingTime" || d.name.includes("imeLoss") || d.name === "penaltyLoop")
1889                    return d.value.toString().toHHMMSS();
1890                if (d.name === "PenaltyLoopAvg") {
1891                    if (d.value === 0)
1892                        return
1893                    else
1894                        return (d.value).toFixed(1);
1895                }
1896                if (d.name === "wins" || d.name === "podiums")
1897                    return d.value;
1898                if (d.name === "hitRate")
1899                    return (d.value * 100).toFixed(2);
1900                if (type === 1 && !isNaN(d.value) && d.name.includes("Rank"))
1901                    return d.value.toFixed(0);
1902                if (!isNaN(d.value) && d.name.includes("Rank") || d.name === "averagePoints" || d.name === "winPercent" || d.name === "podiumPercent")
1903                    return d.value.toFixed(1);
1904                if ((type === 5 || type === 100) && !d.name.includes("averageRank"))
1905                    return d.value;
1906                if (d.name.includes("Time") && !d.name.includes("Back") && !d.name.includes("ZScore"))
1907                    return d.value.toString().toHHMMSS();
1908                if (d.name.includes("hits") || d.name.includes("shots"))
1909                    return d.value;
1910                if (d.name === "pronePercent" || d.name === "standingPercent" || d.name === "totalPercent")
1911                    return (d.value * 100).toFixed(2);
1912                if (d.name === "No" || d.name === "races")
1913                    return d.value;
1914                if (!isNaN(d.value) && d.value > 0 && d.value !== "" && d.name !== "seasonId" && d.name !== "leg" && d.name !== "resultString")
1915                    return "+" + d.value.toFixed(2);
1916                if (!isNaN(d.value) && d.value !== "" && d.name !== "seasonId" && d.name !== "leg" && d.name !== "resultString")
1917                    return d.value.toFixed(2);
1918                else
1919                    return d.value;
1920            })
1921            .style("background", function (d) {
1922                if (d === undefined || d.value === undefined || d.value === null || d.value === "")
1923                    return;
1924
1925                if (d.value.toString().startsWith("-__")) {
1926                    let intwidth = racedata.relayTeams.length > 0 ? 235 : 160;
1927                    let maxtime = racedata.relayTeams.length > 0 ? 70 : 50;
1928
1929
1930                    if (d.value === "-__Target11" && sh1.interval1 !== undefined) {
1931                        d.value = sh1["Shooting 1"]
1932                        shootingintervalchart(d3.select(this), sh1.interval1, d, sh1["Image 1"], sh1["Sh Time 1"], intwidth, 34, maxtime);
1933                    }
1934                    if (d.value === "-__Target12" && sh2.interval1 !== undefined) {
1935                        d.value = sh2["Shooting 1"]
1936                        shootingintervalchart(d3.select(this), sh2.interval1, d, sh2["Image 1"], sh2["Sh Time 1"], intwidth, 34, maxtime);
1937                    }
1938                    if (d.value === "-__Target21" && sh1.interval2 !== undefined) {
1939                        d.value = sh1["Shooting 2"]
1940                        shootingintervalchart(d3.select(this), sh1.interval2, d, sh1["Image 2"], sh1["Sh Time 2"], intwidth, 34, maxtime);
1941                    }
1942                    if (d.value === "-__Target22" && sh2.interval2 !== undefined) {
1943                        d.value = sh2["Shooting 2"]
1944                        shootingintervalchart(d3.select(this), sh2.interval2, d, sh2["Image 2"], sh2["Sh Time 2"], intwidth, 34, maxtime);
1945                    }
1946                    if (d.value === "-__Target31" && sh1.interval3 !== undefined) {
1947                        d.value = sh1["Shooting 3"]
1948                        shootingintervalchart(d3.select(this), sh1.interval3, d, sh1["Image 3"], sh1["Sh Time 3"], intwidth, 34, maxtime);
1949                    }
1950                    if (d.value === "-__Target32" && sh2.interval3 !== undefined) {
1951                        d.value = sh2["Shooting 3"]
1952                        shootingintervalchart(d3.select(this), sh2.interval3, d, sh2["Image 3"], sh2["Sh Time 3"], intwidth, 34, maxtime);
1953                    }
1954                    if (d.value === "-__Target41" && sh1.interval4 !== undefined) {
1955                        d.value = sh1["Shooting 4"]
1956                        shootingintervalchart(d3.select(this), sh1.interval4, d, sh1["Image 4"], sh1["Sh Time 4"], intwidth, 34, maxtime);
1957                    }
1958                    if (d.value === "-__Target42" && sh2.interval4 !== undefined) {
1959                        d.value = sh2
1959["Shooting 4"]
1960                        shootingintervalchart(d3.select(this), sh2.interval4, d, sh2["Image 4"], sh2["Sh Time 4"], intwidth, 34, maxtime);
1961                    }
1962                    if (d.value === "-__Target51" && sh1.interval5 !== undefined) {
1963                        d.value = sh1["Shooting 5"]
1964                        shootingintervalchart(d3.select(this), sh1.interval5, d, sh1["Image 5"], sh1["Sh Time 5"], intwidth, 34, maxtime);
1965                    }
1966                    if (d.value === "-__Target52" && sh2.interval4 !== undefined) {
1967                        d.value = sh2["Shooting 5"]
1968                        shootingintervalchart(d3.select(this), sh2.interval5, d, sh2["Image 5"], sh2["Sh Time 5"], intwidth, 34, maxtime);
1969                    }
1970                    if (d.value === "-__Target61" && sh1.interval6 !== undefined) {
1971                        d.value = sh1["Shooting 6"]
1972                        shootingintervalchart(d3.select(this), sh1.interval6, d, sh1["Image 6"], sh1["Sh Time 6"], intwidth, 34, maxtime);
1973                    }
1974                    if (d.value === "-__Target62" && sh2.interval4 !== undefined) {
1975                        d.value = sh2["Shooting 6"]
1976                        shootingintervalchart(d3.select(this), sh2.interval6, d, sh2["Image 6"], sh2["Sh Time 6"], intwidth, 34, maxtime);
1977                    }
1978                    if (d.value === "-__Target71" && sh1.interval7 !== undefined) {
1979                        d.value = sh1["Shooting 7"]
1980                        shootingintervalchart(d3.select(this), sh1.interval7, d, sh1["Image 7"], sh1["Sh Time 7"], intwidth, 34, maxtime);
1981                    }
1982                    if (d.value === "-__Target72" && sh2.interval4 !== undefined) {
1983                        d.value = sh2["Shooting 7"]
1984                        shootingintervalchart(d3.select(this), sh2.interval7, d, sh2["Image 7"], sh2["Sh Time 7"], intwidth, 34, maxtime);
1985                    }
1986                    if (d.value === "-__Target81" && sh1.interval8 !== undefined) {
1987                        d.value = sh1["Shooting 8"]
1988                        shootingintervalchart(d3.select(this), sh1.interval8, d, sh1["Image 8"], sh1["Sh Time 8"], intwidth, 34, maxtime);
1989                    }
1990                    if (d.value === "-__Target82" && sh2.interval4 !== undefined) {
1991                        d.value = sh2["Shooting 8"]
1992                        shootingintervalchart(d3.select(this), sh2.interval8, d, sh2["Image 8"], sh2["Sh Time 8"], intwidth, 34, maxtime);
1993                    }
1994
1995                }
1996
1997                if (d.name.includes("Shooting ") && window.location.href.includes("races.html")) {
1998                    let intwidth = racedata.relayTeams.length > 0 ? 235 : 160;
1999                    let maxtime = racedata.relayTeams.length > 0 ? 70 : 50;
2000                    if (d.name === "Shooting 1" && d.interval1 !== undefined)
2001                        shootingintervalchart(d3.select(this), d.interval1, d, d.image1, d.shtime1, intwidth, 34, maxtime);
2002                    if (d.name === "Shooting 2" && d.interval2 !== undefined)
2003                        shootingintervalchart(d3.select(this), d.interval2, d, d.image2, d.shtime2, intwidth, 34, maxtime);
2004                    if (d.name === "Shooting 3" && racedata.athletes[d.rank - 1] !== undefined && racedata.athletes[d.rank - 1].individualShots[2] !== undefined)
2005                        shootingintervalchart(d3.select(this), racedata.athletes[d.rank - 1].individualShots[2].shotIntervals, d,
2006                            d.image3, d.shtime3, 150, 34, maxtime);
2007                    if (d.name === "Shooting 4" && racedata.athletes[d.rank - 1] !== undefined && racedata.athletes[d.rank - 1].individualShots[2] !== undefined)
2008                        shootingintervalchart(d3.select(this), racedata.athletes[d.rank - 1].individualShots[3].shotIntervals, d,
2009                            d.image4, d.shtime4, 150, 34, maxtime);
2010                }
2011
2012                if ((d.name === "nation" || d.name === "nat" || d.name === "natWinner" || d.name === "nat2nd" || d.name === "nat3rd") && $(window).width() > 768) {
2013                    const flag = new CountryFlag(this);
2014                    flag.selectByAlpha3(d.value);
2015                }
2016                if (type === 50 && d.name === "RaceRank") {
2017                    if (d.value === 1)
2018                        return "gold";
2019                    if (d.value === 2)
2020                        return "silver";
2021                    if (d.value === 3)
2022                        return "#cd7f32";
2023                }
2024                if (highlightplaces === "highlight") {
2025                    if (d.value === "1" || d.value === "=1.")
2026                        return "gold";
2027                    if (d.value === "2" || d.value === "=2.")
2028                        return "silver";
2029                    if (d.value === "3" || d.value === "=3.")
2030                        return "#cd7f32";
2031                }
2032                if (type === 1119 && d.name.includes("Rank")) {
2033                    if (d.value === 1)
2034                        return "gold";
2035                    if (d.value === 2)
2036                        return "silver";
2037                    if (d.value === 3)
2038                        return "#cd7f32";
2039                }
2040                if (type === 1119 && d.name.includes("rank")) {
2041                    if (d.value === 1)
2042                        return "rgba(255,215,0,0.5)";
2043                    if (d.value === 2)
2044                        return "rgba(192,192,192,0.5)";
2045                    if (d.value === 3)
2046                        return "rgba(205,127,50,0.5)";
2047                }
2048                if (d.name === value1key)
2049                    if (d.name.includes("prone"))
2050                        return pSBC(0.4, color1(d.value), false, true);
2051                    else
2052                        return color1(d.value);
2053                if (d.name === value2key)
2054                    if (d.name.includes("standing"))
2055                        return pSBC(0.4, color2(d.value), false, true);
2056                    else
2057                        return color2(d.value);
2058                if (d.name === value3key)
2059                    return color3(d.value);
2060                if (d.name === value4key)
2061                    return color4(d.value);
2062            })
2063
2064            .style("width", function (d) {
2065                if (d !== undefined && (d.name === "nation" || d.name == "nat" || d.name === "natWinner" || d.name === "nat2nd" || d.name === "nat3rd") && $(window).width() > 768) {
2066                    return '90px';
2067                }
2068            })
2069            .style("border-right", function (d) {
2070                if (d !== undefined && (d === "races" || d.name === "overallRank" || d.name === "inrank" || d.name === "sprank" || d.name === "purank" || d.name === "rlrank")) {
2071                    return '1px solid #a8a8a8';
2072                }
2073            })
2074            .style("font-weight", function (d) {
2075
2076                if (window.location.href.includes("comparisons.html")) {
2077                    if (d.name.includes("Compare") || d.name === "projectedRaceTime" || d.name === "Net Time")
2078                        return;
2079                    if (d.name === "familyName" || d.name === "country" || d.name === "Country" || d.name.includes("timeLoss")
2080                        || d.name === "skiingAverageBack" || d.name === "skiingTop10MedianBack" || d.name === "skiingTop30MedianBack"
2081                        || d.name === "skiingMedianBack" || d.name === "skiRank" || d.name === "averageShootingRank"
2082                        || d.name === "pronePercent" || d.name === "standingPercent" || d.name === "totalPercent" || d.name === "rangeTimeRank" || d.name === "shootingTimeRank"
2083                        || d.name.includes("prone") || d.name.includes("standing") || d.name.includes("total"))
2084                        return "bold";
2085
2086                }
2087                if (window.location.href.includes("search.html")) {
2088
2089                    if (d.name === "familyName" || d.name.includes(loopstable))
2090                        return "bold";
2091                    else
2092                        return;
2093
2094                }
2095
2096                if (d === undefined || d.value === undefined || d.value === null || (typeof cat !== "undefined" && (cat === "statsPerRace" || cat === "statsPerSeason")))
2097                    return;
2098
2099                if (type === 50 || (d !== undefined && d.value !== null && d.value.toString().includes("("))) {
2100                    if (d.name === "RaceRank")
2101                        return "bold";
2102                    if (d.name === "familyName")
2103                        return "bold"
2104                    else if (typeof cat !== "undefined" && cat !== "meta")
2105                        return;
2106                }
2107                if (highlightplaces === "highlight") {
2108                    if (d.value === "1" || d.value === "=1.")
2109                        return "bold";
2110                    if (d.value === "2" || d.value === "=2.")
2111                        return "bold";
2112                    if (d.value === "3" || d.value === "=3.")
2113                        return "bold";
2114                }
2115                if (type === 1119) {
2116                    if (d.name.includes("Rank") || d.name.includes("rank") || d.name.includes("Season"))
2117                        return "bold";
2118                    else return;
2119                }
2120                if (typeof cat !== "undefined" && cat === "Combined" && d.name !== "familyName")
2121                    return;
2122                if (d.name === "familyName" || d.name === "country" || d.name === "Country" || d.name === "projectedRaceTime" || d.name === "Net Time" || (d.name.includes("total") && !window.location.href.includes("projections.html"))
2123                    || d.name.includes("score") || d.name === "Year" || d.name === "timeLossSprint" || d.name === "skiingMedianBack" || d.name === "Total Time" || d.name === "organizer" || d.name === "compareCategory" || d.name === "averageHitRateRank"
2124                    || d.name === "overallTitles" || d.name === "disciplineTitles" || d.name === "wins" || d.name === "median" || d.name === "weather" || d.name === "coursedata" || d.name === "seasonWCpoints"
2125                    || d.name === "shootingRank_Rank")
2126                    return "bold";
2127                if (d.name === "bib" && (d.color === "o" || d.color === "yr" || d.color === "y" || d.color === "r" || d.color === "b"))
2128                    return "bold";
2129            })
2130            .style("font-style", function (d) {
2131                if (d !== undefined && (d.name === "winPercent" || d.name === "podiumPercent" || d.name === "Win%" || d.name === "Podium%" || d.name === "Top10%"))
2132                    return "italic"
2133            })
2134
2135            .style("color", function (d) {
2136                if (d !== undefined && (d.name.toLowerCase() === "bib" && d.color === "y"))
2137                    return "#a5a500";
2138                if (d !== undefined && (d.name.toLowerCase() === "bib" && d.color === "r"))
2139                    return "#800000";
2140                if (d !== undefined && (d.name.toLowerCase() === "bib" && d.color === "b"))
2141                    return "#0011e2";
2142            })
2143
2144            .on('mouseenter', function (d) {
2145
2146
2147                let ignore = ["pronePercent", "standingPercent", "totalPercent", "gender", "rangeTimeRank", "natWinner", "nat2nd", "nat3rd", "givenNameWinner", "givenName2nd", "givenName3rd",
2148                    "skiRank", "skiingMedianBack", "skiingTop10MedianBack", "skiingTop30MedianBack", "totalShootingTime", "shootingTimeRank", "totalRangeTime", "wcpoints", "Age"]
2149
2150                if (d.value === undefined || d.bottomrow || d.value === null || (ignore.indexOf(d.name) > -1 && typeof cat !== "undefined" && cat !== "statsPerRace"))
2151                    return;
2152
2153                if ((vw <= 1200 && (xNationColumn !== undefined && xNationColumn.offsetWidth <= 75)) || isDesktop || vw > 1200) {
2154                    if (d.name === "country" || d.name === "Country") {
2155                        let type = typeof racedata !== "undefined" ? racedata.catId.substring(1, 2) : genderString
2156                        d3.select(this).html('<a href="teams.html?ibuId=BT' + d.ibuId.substring(2, 5) + "-" + type + '">' + d.value + '</a>');
2157                    }
2158                    if (d.name === "familyName" || d.name === "lastname") {
2159                        d3.select(this).html('<a href="/athletes.html?ibuId=' + d.ibuId + '">' + d.value + '</a>');
2160
2161                        createPopUp(d, d3.event.pageX, d3.event.pageY)
2162                    }
2163                    if (d.name === "Range Time" || d.name === "Shooting Time" || d.name === "Course Time" ||
2164                        d.name === "Cumulative Time" || d.name === "Loop Time" || d.name === "Ski Time" ){
2165                        if (d.value !== undefined && d.value.includes("__")){
2166                            let splitArray = d.value.split("__")
2167                            div00.style("opacity", .8);
2168
2169                            if (splitArray[2] !== undefined && splitArray[2] !== "undefined")
2170                                div00.html('<table class="smallstatstable"><tr><td>Rank</td><td>' + splitArray[2] + '</td></tr><tr><td>Time</td><td>' + splitArray[0].toHHMMSS() + '</td></tr><tr><td>Behind</td><td>+' + splitArray[1].toHHMMSS() + '</td></tr></table>');
2171                            else
2172                                div00.html('<table class="smallstatstable"><td>Time</td><td>' + splitArray[0].toHHMMSS() + '</td></tr><tr><td>Behind</td><td>+' + splitArray[1].toHHMMSS() + '</td></tr></table>');
2173                        }
2174                    }
2175                }
2176
2177                if (highlightplaces === "highlight" && (d.name !== "Year" && d.name !== "Place" && (d.name !== "nation") && d.name !== "Season" && (d.name !== "nat"))) {
2178                    d3.select(this).html('<a href="/races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2179                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2180                }
2181
2182                if (type === 1119 && (d.name !== "Season" && d.name !== "nat")) {
2183                    d3.select(this).html('<a href="scores.html?year=' + d.year + '">' + d.value + '</a>');
2184                }
2185
2186                if (window.location.href.includes("search.html")) {
2187                    if (d.name === "nat" || d.name === "" || d.name.startsWith("Season"))
2188                        return;
2189                    else if (typeString === "races" && (d.name === "Date" || d.name === "Level" || d.name === "Discipline" || d.name === "place" || d.name === loopstable)) {
2190                        d3.select(this).html('<a href="/races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2191                        createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2192                    }
2193
2194                }
2195
2196                if (window.location.href.includes("venues.html")) {
2197                    if (d.name === "nat" || d.name === "" || d.name.startsWith("Season"))
2198                        return;
2199                    else if (d.name === "familyNameWinner")
2200                        d.ibuId1.length < 8 ? d3.select(this).html('<a href="/teams.html?ibuId=' + d.ibuId1 + '">' + d.value + '</a>') : d3.select(this).html('<a href="/athletes.html?ibuId=' + d.ibuId1 + '">' + d.value + '</a>');
2201                    else if (d.name === "familyName2nd")
2202                        d.ibuId2.length < 8 ? d3.select(this).html('<a href="/teams.html?ibuId=' + d.ibuId2 + '">' + d.value + '</a>') : d3.select(this).html('<a href="/athletes.html?ibuId=' + d.ibuId2 + '">' + d.value + '</a>');
2203                    else if (d.name === "familyName3rd")
2204                        d.ibuId3.length < 8 ? d3.select(this).html('<a href="/teams.html?ibuId=' + d.ibuId3 + '">' + d.value + '</a>') : d3.select(this).html('<a href="/athletes.html?ibuId=' + d.ibuId3 + '">' + d.value + '</a>');
2205                    else if (d.name === "Date" || d.name === "Level" || d.name === "Dis" || d.name === "Gender") {
2206                        d3.select(this).html('<a href="/races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2207                        //createRacePopUp(d.raceId,d3.event.pageX, d3.event.pageY)
2208                    } else if (!isNaN(d.value) && !d.name.includes("Year") && !d.name.includes("its") && !d.name.includes("hots") && !d.name.includes("races")) {
2209                        if (!isNaN(d.value)) d3.select(this).html(d.value.toFixed(1));
2210                    } else
2211                        d3.select(this).html('<a href="venues.html?venue=' + d.venue + '">' + d.value + '</a>');
2212
2213                }
2214
2215                if (type === 113 && cat === "statsPerRace") {
2216                    if (d.name === "speed") {
2217                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value.toFixed(2) + '</a>');
2218                        createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2219                    } else if ((!isNaN(d.value) && (d.value - Math.floor(d.value)) !== 0) || d.name.includes("ShootingTime")
2220                        || d.name.includes("RangeTime") || d.name.includes("totalPercent") || d.name.includes("pronePercent") || d.name.includes("standingPercent")) {
2221                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value.toFixed(1) + '</a>');
2222                        createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2223                    } else {
2224                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2225                        createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2226                    }
2227                }
2228                if (type === 50) {
2229                    if (d.name === "familyName")
2230                        d3.select(this).html('<a href="/athletes.html?ibuId=' + d.ibuId + '">' + d.value + '</a>');
2231                    else {
2232                        d3.select(this).html('<a href="/races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2233                        createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2234                    }
2235
2236                }
2237                if ((type === 555 || type === 556) && d.name.includes("BT")) {
2238                    d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2239                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2240
2241                }
2242                if (type === 9858 && d.raceId !== undefined && d.name === "shortDe
2242scription") {
2243                    d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2244                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2245                }
2246                if (type === 999 && d.raceId !== undefined && typeof localtime !== "undefined" && typeof levelString !== "undefined") {
2247                    d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '&localtime=' + localtime + '&level=' + levelString + '">' + d.value + '</a>');
2248                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2249                } else if (type === 999 && d.raceId !== undefined) {
2250                    d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2251                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2252                }
2253                if (d.value.toString().includes("##")) {
2254                    let timearray = d.value.toString().split("##")
2255                    if (timearray.length === 3)
2256                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + timearray[0] + '</a>' + "<br>" +
2257                            '<a href="races.html?raceId=' + d.raceId + '">' + timearray[1] + '</a>' + "<br>" +
2258                            '<a href="races.html?raceId=' + d.raceId + '">' + timearray[2] + '</a>');
2259                    else
2260                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + timearray[0] + '</a>' + "<br>" +
2261                            '<a href="races.html?raceId=' + d.raceId + '">' + timearray[1] + '</a>');
2262
2263                    createRacePopUp(d.raceId, d3.event.pageX, d3.event.pageY)
2264                }
2265            })
2266            .on("mousemove", function (d) {
2267                div00
2268                    .style("left", (d3.event.pageX + 15) + "px")
2269                    .style("top", (d3.event.pageY + 15) + "px")
2270            })
2271            .on('mouseleave ', function (d) {
2272
2273                if (d.name.toLowerCase() === 'gender') {
2274                    if (d.value === 'W')
2275                        return 'Women';
2276                    if (d.value === 'M')
2277                        return 'Men';
2278                    if (d.value === 'X')
2279                        return 'Mixed';
2280                    if (d.value === 'A')
2281                        return 'All';
2282                }
2283                if (d.name === "rankDiff") return
2284
2285                let ignore = ["pronePercent", "standingPercent", "totalPercent", "rangeTimeRank", "natWinner", "nat2nd", "nat3rd",
2286                    "skiRank", "skiingMedianBack", "skiingTop10MedianBack", "skiingTop30MedianBack", "totalShootingTime", "shootingTimeRank", "totalRangeTime", "wcpoints"]
2287
2288
2289                if ((typeof cat !== "undefined" && cat !== "statsPerRace") || d.name !== "nat " && d.name !== "nation" && d.value !== undefined
2290                    && ignore.indexOf(d.name) === -1) {
2291
2292                    if (window.location.href.includes("venues.html") && !isNaN(d.value) && !d.name.includes("Year") && !d.name.includes("its") && !d.name.includes("hots") && !d.name.includes("races")) {
2293                        d3.select(this).html(d.value.toFixed(1));
2294                        return;
2295                    }
2296
2297                    if (highlightplaces === "highlight" && (d.name !== "Year" && d.name !== "Place" && (d.name !== "nation") && d.name !== "Season" && (d.name !== "nat")
2298                        && (d.name !== "natWinner") && (d.name !== "nat2nd") && (d.name !== "nat3rd"))) {
2299                        d3.select(this).html(d.value);
2300                    }
2301                    if (d.name === "familyName" || d.name === "lastname" || d.name === "country" || d.name === "Country") {
2302                        destroyPopUp();
2303                        d3.select(this).html(d.value);
2304                    }
2305                    if ((type === 555 || type === 556) && (d.name.includes("score") || d.name.includes("givenName"))) {
2306                        d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value + '</a>');
2307                        destroyRacePopUp()
2308
2309                    }
2310                    if ((type === 50 || type === 555 || type === 556 || (type === 113 && cat === "statsPerRace")) && d.name !== "nation") {
2311                        if (d.name === "speed")
2312                            d3.select(this).html('<a href="races.html?raceId=' + d.raceId + '">' + d.value.toFixed(2) + '</a>');
2313                        else if ((!isNaN(d.value) && (d.value - Math.floor(d.value)) !== 0) || d.name.includes("ShootingTime")
2314                            || d.name.includes("RangeTime") || d.name.includes("totalPercent") || d.name.includes("pronePercent") || d.name.includes("standingPercent"))
2315                            d3.select(this).html(d.value.toFixed(1));
2316                        else
2317                            d3.select(this).html(d.value);
2318
2319                        if (!(d.name === "Date" || d.name === "Level" || d.name === "Dis" || d.name === "place" || d.name === "Event"))
2320                            destroyRacePopUp();
2321                    }
2322
2323                    if (type === 999 && d.raceId !== undefined) {
2324                        d3.select(this).html(d.value);
2325                    }
2326                    if (d.value !== undefined && d.value !== null && d.value.toString().includes("##")) {
2327                        let timearray = d.value.toString().split("##")
2328                        if (timearray.length === 3)
2329                            d3.select(this).html(timearray[0] + "<br>" + timearray[1] + "<br>" + timearray[2]);
2330                        else
2331                            d3.select(this).html(timearray[0] + "<br>" + timearray[1]);
2332                    }
2333
2334                    div00.style("opacity", 0);
2335
2336
2337                }
2338            })
2339            .on('click', function (d) {
2340                var x = window.screenX + 130;
2341                var y = window.screenY + 130;
2342                if (d.name !== "StartInfo" && d.shortName !== undefined && d.shortName !== null) {
2343                    loadFirstLastName(d.FullName, d.shortName, 1)
2344                } else if (d.RaceId !== undefined && d.RaceId !== null) {
2345                    //if (d.preRace === undefined)
2346                    //window.close();
2347                    var id = d.RaceId;
2348                    var newWin = window.open('', "mywindow" + Math.random().toString(), "menubar=1,resizable=1,width=800,height=600, top=" + x + "+, left=" + y);
2349                    newWin.document.write(
2350                        '<head><title>Race: ' + id + '</title><link rel="stylesheet" href="styles.css"><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script><script type="text/javascript" src="visualisation.js"></script><script src="https://d3js.org/d3.v4.js"></script> </head><body><script> raceResults("' + id + '")<\/script></body>'
2351                    );
2352                }
2353                if (d.CupDis !== undefined && d.CupYear !== undefined) {
2354                    //window.close();
2355                    var id = d.CupDis;
2356                    var season = d.CupYear;
2357                    var gender = d.gender;
2358                    var newWin = window.open('', "mywindow" + Math.random().toString(), "menubar=1,resizable=1,width=800,height=600, top=" + x + "+, left=" + y);
2359                    newWin.document.write(
2360                        '<head><title>Race: ' + id + '</title><link rel="stylesheet" href="styles.css"><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script><script type="text/javascript" src="visualisation.js"></script><script src="https://d3js.org/d3.v4.js"></script> </head><body><script> cupResults("' + season + '","' + id + '","' + gender + '")<\/script></body>'
2361                    );
2362                }
2363            })
2364        return rows
2365    }
2366
2367    var rows = newrows(table, data);
2368
2369    filterfunction();
2370
2371    div1.node().appendChild(outercontainer)
2372
2373    $('.newtablediv').on('scroll', function () {
2374        $(window).trigger('resize.stickyTableHeaders');
2375    });
2376    $('.tablecell').mousemove(function (e) {
2377        let space = window.innerHeight + window.pageYOffset - e.pageY
2378
2379        let offset = space < 300 ? 300 : 0;
2380        $("#namepopover").css({left: e.pageX - 20, top: e.pageY + 40});
2381        $("#racepopup").css({left: e.pageX - 20, top: e.pageY + 40});
2382
2383    });
2384    $('table').mouseleave(function (e) {
2385        setTimeout(function () {
2386            $("#namepopover").remove()
2387            $("#racepopup").remove()
2388        }, 400);
2389    });
2390    $('table td').mouseleave(function (e) {
2391        $("#racepopup").remove()
2392    });
2393    $("table td").hover(function () {
2394        let rowindex = $("table tr").index($(this).closest('tr'))
2395        currentcell = rowindex + $(this)[0].outerHTML;
2396    });
2397    $(".tablediv0").css("width", $("table").width())
2398
2399
2400}
2401
2402function createPopUp(d, x, y) {
2403    // console.log(d)
2404    $("#racepopup").remove()
2405
2406    let lastname = d.value
2407    if (d.value.includes('</b> ')) {
2408        lastname = lastname.split('</b> ')[0]
2409    } else {
2410        lastname = '<b>' + lastname + '</b> '
2411    }
2412
2413    let newhtml = "";
2414    newhtml += '<div id="biopicture2"><img id="biopictureimg2" src="https://ibu.blob.core.windows.net/docs/athletes/' + d.ibuId + '.png" onError="oldImage(&quot;' + d.ibuId + '&quot;);" class="rounded"></div></div>';
2415
2416    let breakline = "<br>";
2417    newhtml += '<div class="headertoprow2"> <div class="athname2"><h1 class="popupname" id=\"content\">' + d.firstname +
2418        breakline + lastname.replace("Hammerschmidt", "Hammer-schmidt").replace("Bjoentegaard", "Bjoente-gaard").replace("Stroemsheim", "Stroems-heim")
2419            .replace("Lardschneider", "Lard-schneider").replace("Moshchenkova", "Moshchen-kova").replace("Machyniakova", "Machynia-kova")
2420        + ' </h1></div>' +
2421        '</div><div id="bioflag2" class="bioflag2"></div>';
2422
2423    var excludelist = ["BTNOR10312196901", "BTAUT11709196701", "BTFRA23010196801", "BTGER21312199401", "BTRUS12607200101", "BTUKR21706198501", "BTPOL22504197901", "BTCZE22411198601", "BTRUS11803199701", "BTSUI10503197901"]
2424
2425    if (!excludelist.includes(d.ibuId)) {
2426        let year = d.ibuId.substring(d.ibuId.length - 10).substring(4, 8);
2427        let month = d.ibuId.substring(d.ibuId.length - 10).substring(2, 4);
2428        let day = d.ibuId.substring(d.ibuId.length - 10).substring(0, 2);
2429        if ((calculate_age(new Date(year + '-' + month + '-' + day)) < 50)) {
2430            newhtml += '<div><h4  class="bd-lead subheaderage2"  >'
2431            newhtml += 'Age: <b>' + calculate_age(new Date(year + '-' + month + '-' + day)) + '</b>  ';
2432            newhtml += '  </h4>';
2433        }
2434    }
2435
2436    if ($("#namepopover").length > 0) {
2437        $('#namepopover').html(newhtml)
2438        $("#namepopover").css({opacity: 1});
2439
2440    } else {
2441        $("body").append('<div class="namepopover" id="namepopover" style="left:' + (x - 20) + 'px;
2441 top:' + (y + 40) + 'px"><div class="namepopover" id="namepopover">' + newhtml + '</div></div>');
2442        $("#namepopover").css({opacity: 1});
2443
2444    }
2445
2446    let flagdiv = document.getElementById("bioflag2");
2447    const flag = new CountryFlag(flagdiv);
2448    flag.selectByAlpha3(d.nat);
2449
2450    $('#namepopover').on('mouseover', function () {
2451        $("#namepopover").remove()
2452    });
2453
2454}
2455
2456
2457function destroyPopUp(id, x, y) {
2458    $("#namepopover").remove()
2459
2460}
2461
2462function createRacePopUp(raceId, x, y) {
2463    if (vw < 1000) return;
2464    let cell1 = currentcell;
2465    setTimeout(function () {
2466        let cell2 = currentcell;
2467        if (cell1 === cell2) getRaceStump(raceId, x, y)
2468    }, 350);
2469}
2470
2471function destroyRacePopUp(id, x, y) {
2472    $("#racepopup").remove()
2473}
2474
2475function calculate_age(dob) {
2476    var diff_ms = Date.now() - dob.getTime();
2477    var age_dt = new Date(diff_ms);
2478
2479    return Math.abs(age_dt.getUTCFullYear() - 1970);
2480}
2481
2482function movingAverageChart(size, data, publishedAt, timemax, min, max, reverse, width, height, xticks, yticks, plusminus, tickIsSeasonId) {
2483    // Define the div for the tooltip
2484    var div = d3.select("#main").append("div")
2485        .attr("class", 'tooltip')
2486        .attr("id", 'tooltipdiv')
2487        .style("opacity", 0);
2488
2489    let isTeamInd = (typeof rangemin !== "undefined" && rangemin > 1957);
2490
2491    // set the dimensions and margins of the graph
2492    var margin = {top: 5, right: 100, bottom: 60, left: 50},
2493        width = width - margin.left - margin.right,
2494        height = height - margin.top - margin.bottom;
2495    // append the svg object to the body of the page
2496
2497    var svg = d3.select("div#" + size + "chartdiv")
2498        .append("div")
2499        // Container class to make it responsive.
2500        .classed("svg-container", true)
2501        .append("svg")
2502        .attr("preserveAspectRatio", "xMinYMin meet")
2503        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
2504        .classed("svg-content-responsive", true)
2505        .append("g")
2506        .attr("transform",
2507            "translate(" + margin.left + "," + margin.top + ")");
2508    // List of groups (here I have one group per column)
2509    var allGroup = [];
2510    for (const [key, value] of Object.entries(data[0])) {
2511        if (key !== "name")
2512            allGroup.push(key);
2513    }
2514    var myColor = d3.scaleOrdinal() // D3 Version 4
2515        .domain([allGroup[0], allGroup[1], allGroup[2]])
2516        .range(["rgba(50,136,189,0.7)", "rgba(50,136,189,0.7)", "#5E4FA2"])
2517        .unknown("rgba(163,154,154,0.1)");
2518    if ((document.getElementById("darkSwitch").checked))
2519        myColor.range(["rgba(50,136,189,0.4)", "rgba(228,26,28,0.4)", "#4daf4a"])
2520    //allGroup.reverse();
2521    // Reformat the data: we need an array of arrays of {x, y} tuples
2522    let rawvalues = [];
2523
2524    var dataReady = allGroup.map(function (grpName, i) { // .map allows to do something for each element of the list
2525        return {
2526            name: grpName,
2527            values: data.map(function (d) {
2528                rawvalues.push(d[grpName]);
2529                return {time: d.name, value: +d[grpName], athlete: grpName, position: i + 1};
2530            })
2531        };
2532    });
2533    dataReady.reverse();
2534    var dataAvgValues = []
2535    if (plusminus !== "none") {
2536        movingAvgValues = function (data, neighbors) {
2537            return data.map((val, idx, arr) => {
2538                let start = Math.max(0, idx - neighbors), end = idx + neighbors
2539                let subset = arr.slice(start, end + 1)
2540                let sum = subset.reduce((a, b) => a + b)
2541                return sum / subset.length
2542            })
2543        }
2544        dataAvgValues = movingAvgValues(rawvalues, Number(plusminus))
2545    }
2546
2547    var dataAvg = [];
2548    dataReady.forEach(function (c, j) {
2549        let item = {
2550            name: dataReady[j].name,
2551            values: []
2552        }
2553
2554        c.values.forEach(function (d, i) {
2555            let vs = {
2556                time: d.time,
2557                value: dataAvgValues[i]
2558            }
2559            item.values.push(vs);
2560        })
2561        dataAvg.push(item);
2562    })
2563
2564    //console.log(dataReady.length, dataReady)
2565    //console.log(dataAvg.length, dataAvg)
2566
2567    function searchfunction(searchbar) {
2568        d3.select("#SearchBar" + searchbar)
2569            .on("keyup input", function () {
2570                d3.select("#chartdiv").html("");
2571
2572                if (cat === "progression") {
2573                    if (racedata.relayTeams.length > 0) {
2574                        let chartdata = raceProgressionData(racedata.relayTeams, 2);
2575                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
2576                    } else {
2577                        let chartdata = raceProgressionData(racedata.athletes, 1);
2578                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
2579                    }
2580                }
2581                if (cat === "loops") {
2582                    if (racedata.relayTeams.length > 0) {
2583
2584                    } else {
2585                        let chartdata = loopChartData(racedata.athletes, 1)
2586                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 650, chartdata.ticks, 10)
2587                    }
2588                }
2589
2590
2591            })
2592    }
2593
2594    searchfunction("FamilyName");
2595    searchfunction("Nation");
2596
2597    function update(selected) {
2598        svg.selectAll("*").remove();
2599        let latestseason = "";
2600        // Add X axis --> it is a date format
2601        var x = d3.scalePoint()
2602            .range([0, width]);
2603        x.domain(dataReady[0].values.map(function (d) {
2604            return d.time;
2605        }));
2606        let divider = Math.ceil(dataReady[0].values.length / 30);
2607
2608
2609        var xAxis = svg.append("g")
2610            .attr("transform", "translate(0," + height + ")")
2611            .attr("class", "axis")
2612            .call(d3.axisBottom(x)
2613                .tickValues(x.domain().filter(function (d, i) {
2614                    return !(i % divider)
2615                }))
2616                .tickFormat(function (d) {
2617                    if (tickIsSeasonId) {
2618                        if (d.substring(2, 6) !== latestseason) {
2619                            latestseason = d.substring(2, 6);
2620                            if (d.substring(2, 3) > 4)
2621                                return "19" + d.substring(2, 4) + "-" + d.substring(4, 6);
2622                            else
2623                                return "20" + d.substring(2, 4) + "-" + d.substring(4, 6);
2624                        }
2625                    } else
2626                        return d.toString().split(" ")[0]
2627                }).ticks(10)
2628            )
2629            .selectAll("text")
2630            .style("text-anchor", "start")
2631            .attr("dx", function (d) {
2632                return 4;
2633            })
2634            .attr("dy", 8)
2635            .attr("transform", function (d) {
2636                return "rotate(28)";
2637            });
2638        // Add Y axis
2639        var y;
2640        if ((dataReady[0].name.includes("peed") || dataReady[0].name.includes("Percent") ||
2641                (dataReady[0].name.includes("hitRate") && dataReady[0].name !== "hitRateRank") || reverse) && !dataReady[0].name.includes("Back")
2642            && !dataReady[0].name.includes("ZScore")) {
2643            y = d3.scaleLinear()
2644                .domain([min, max])
2645                .range([height, 0]);
2646        } else {
2647            y = d3.scaleLinear()
2648                .domain([max, min])
2649                .range([height, 0]);
2650        }
2651
2652        var yAxis = svg.append("g")
2653            .attr("class", "axis")
2654            .call(d3.axisLeft(y).tickFormat(function (d) {
2655                if (typeof chosenstat !== "undefined" && (chosenstat === "time" || chosenstat.includes("Loss") || chosenstat.includes("projected") || chosenstat.includes("Plus")))
2656                    return d.toString().toHHMMSS();
2657                else
2658                    return d;
2659            }).ticks(yticks));
2660        // gridlines in x axis function
2661        let tickInterval = Math.ceil(dataAvg[0].values.length / 50);
2662        let tickValues = data.map(function (d) {
2663            return d.name;
2664        }).filter(function (d, i) {
2665            return i % tickInterval === 0
2666        });
2667
2668        function make_x_gridlines() {
2669            return d3.axisBottom(x).tickValues(tickValues)
2670
2671        }
2672
2673        // gridlines in y axis function
2674        function make_y_gridlines() {
2675            return d3.axisLeft(y)
2676        }
2677
2678        let positionx = window.location.href.includes("athletes.html") || window.location.href.includes("teams.html") ? 16 : width - 98
2679        svg.append("text")
2680            .attr("x", positionx)
2681            .attr("y", 16)
2682            .attr("class", "svgdarktext")
2683            .text("realbiathlon.com")
2684            .style("font-size", "12px")
2685            .style("opacity", 0.4)
2686
2687        // add the X gridlines
2688        svg.append("g")
2689            .attr("class", "grid")
2690            .attr("transform", "translate(0," + height + ")")
2691            .call(make_x_gridlines()
2692                .tickSize(-height)
2693                .tickFormat("")
2694                .ticks(10)
2695            );
2696        svg.append("g")
2697            .attr("class", "grid")
2698            .call(make_y_gridlines()
2699                .tickSize(-width)
2700                .tickFormat("")
2701            )
2702        var p = svg.selectAll("path")
2703            .data(dataReady)
2704        // Add the lines
2705        var line = d3.line()
2706            .x(function (d) {
2707                return x(d.time)
2708            })
2709            .y(function (d) {
2710                return y(d.value)
2711            })
2712            .defined(function (d) {
2713                return d !== undefined && !Number.isNaN(d.value);
2714            })
2715        var curvedLine = d3.line()
2716            .x(function (d) {
2717                return x(d.time)
2718            })
2719            .y(function (d) {
2720                return y(d.value)
2721            })
2722            .curve(d3.curveBasis)
2723
2724
2725        if (!isTeamInd) {
2726            svg.selectAll("myLines")
2727                .data(dataReady)
2728                .enter()
2729                .append("path").classed("line", true)
2730                .attr("class", function (d) {
2731                    return d.name.replace(/. /g, '');
2732                })
2733                .attr("d", function (d) {
2734                    if (d !== undefined && !Number.isNaN(d.value))
2735                        return line(d.values)
2736                })
2737                .attr("stroke", function (d) {
2738                    return myColor(d.name);
2739                })
2740                .style("stroke-width", 1)
2741                .style("fill", "none")
2742
2743        }
2744
2745
2746        var u = svg.selectAll("circle")
2747            .data(dataReady)
2748        // Add the points
2749        svg
2750            // First we need to enter in a group
2751            .selectAll("myDots").merge(u)
2752            .data(dataReady)
2753            .enter()
2754            .append('g')
2755            .attr("class", function (d) {
2756                if (d.name !== undefined)
2757                    return d.name.replace(/. /g, '')
2758            })
2759            .style("fill", function (d) {
2760                return myColor(d.name);
2761            })
2762            // Second we need to enter in the 'values' part of this group
2763            .selectAll("myPoints")
2764            .data(function (d) {
2765                if (d !== undefined)
2766                    return d.values
2767            })
2768            .enter()
2769            .append("circle")
2770            .attr("cx", function (d) {
2771                if (d !== undefined && !Number.isNaN(d.value))
2772                    return x(d.time)
2773            })
2774            .attr("cy", function (d) {
2775                if (d !== undefined && !Number.isNaN(d.value))
2776                    return y(d.value)
2777            })
2778            .attr("r", function (d) {
2779                if (d !== undefined && !Number.isNaN(d.value))
2780                    return 4
2781            })
2782            .attr("stroke", "white")
2783            .style('stroke-opacity', '0.2')
2784            .on("mouseover", function (d) {
2785                d3.select(this).attr("r", 5).style("fill", "rgba(50,136,189,1)");
2786                d3.select(this).style("cursor", "pointer");
2787
2788                div.style("opacity", .8);
2789                let seasonstring = "";
2790                if (d.time.substring(2, 3) > 4)
2791                    seasonstring = "19" + d.time.substring(2, 4) + "–" + d.time.substring(4, 6);
2792                else
2793                    seasonstring = "20" + d.time.substring(2, 4) + "–" + d.time.substring(4, 6);
2794                if (tickIsSeasonId) {
2795                    div.html(seasonstring + "<br/>  " + d.time.substring(10, 14).replace("__", "").replace("CP0", "WC ") +
2796                        " " + d.time.slice(-2) + "<br/> Value: " + d.value.toFixed(1).replace(".0", ""));
2797                } else {
2798                    if (chosenstat === "time" || chosenstat.includes("Loss") || chosenstat.includes("Plus"))
2799                        div.html(d.time + "<br/>  " + "Value: " + d.value.toString().toHHMMSS());
2800                    else
2801                        div.html(d.time + "<br/>  " + "Value: " + d.value.toFixed(1).replace(".0", ""));
2802
2803                }
2804
2805                // setTimeout(function() {
2806                //     $('#tooltipdiv').css({"opacity":"0"});
2807                // }, 5000);
2808
2809
2810            })
2811            .on("mousemove", function (d) {
2812                div
2813                    .style("left", (d3.event.pageX + 15) + "px")
2814                    .style("top", (d3.event.pageY + 15) + "px")
2815            })
2816            .on("mouseout", function (d) {
2817                if ((document.getElementById("darkSwitch").checked)) d3.select(this).attr("r", 4).style("fill", "rgba(50,136,189,0.4)");
2818                else d3.select(this).attr("r", 4).style("fill", "rgba(50,136,189,0.7)");
2819
2820                div
2821                    .style("opacity", 0);
2822            });
2823        if (plusminus !== "none") {
2824            svg.selectAll("myTrendLines")
2825                .data(dataAvg)
2826                .enter()
2827                .append("path").classed("line", true)
2828                .attr('class', 'avg')
2829                .attr("d", function (d) {
2830                    if (d !== undefined && !Number.isNaN(d.value))
2831                        return curvedLine(d.values)
2832                })
2833                .attr("stroke", function (d) {
2834                    return "rgba(158,1,66,0.8)";
2835                })
2836                .style("stroke-width", 3)
2837                .style("fill", "none")
2838        }
2839
2840
2841    }
2842
2843    update(0);
2844}
2845
2846function movingAverageChart2(size, data, publishedAt, timemax, min, max, reverse, width, height, xticks, yticks, plusminus, tickIsSeasonId) {
2847    // Define the div for the tooltip
2848    var div = d3.select("body").append("div")
2849        .attr("class", 'tooltip')
2850        .attr("id", 'tooltipdiv')
2851        .style("opacity", 0);
2852
2853    // console.log(data.length)
2854    plusminus = Math.min(Math.ceil(data.length / 20), 8);
2855    // console.log(plusminus)
2856    let isTeamInd = (typeof rangemin !== "undefined" && rangemin > 1957);
2857    // set the dimensions and margins of the graph
2858    var margin = {top: 27, right: 100, bottom: 60, left: 50},
2859        width = width - margin.left - margin.right,
2860        height = height - margin.top - margin.bottom;
2861    // append the svg object to the body of the page
2862    var svg = d3.select("div#" + size + "chartdiv")
2863        .append("div")
2864        // Container class to make it responsive.
2865        .classed("svg-container", true)
2866        .append("svg")
2867        .attr("preserveAspectRatio", "xMinYMin meet")
2868        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
2869        .classed("svg-content-responsive", true)
2870        .append("g")
2871        .attr("transform",
2872            "translate(" + margin.left + "," + margin.top + ")");
2873    // List of groups (here I have one group per column)
2874    var allGroup = [];
2875
2876    for (let i = 0; i < data.length; i++) {
2877        for (const [key, value] of Object.entries(data[i])) {
2878            if (key !== "name" && key !== "date" && key !== "dataId" && key !== "No" && key !== "Season" && key !== "Event" && key !== "Discipline" && !allGroup.includes(key))
2879                allGroup.push(key);
2880        }
2881    }
2882    // console.log(allGroup)
2883    if (allGroup[0] === "Race Rank" && allGroup[1] === "Ski Rank" && allGroup[2] === "Shooting Rank") allGroup = ['Ski Rank', 'Shooting Rank', 'Race Rank']
2884    else if (allGroup[0] === "Ski Rank" && allGroup[1] === "Race Rank" && allGroup[2] === "Shooting Rank") allGroup = ['Ski Rank', 'Shooting Rank', 'Race Rank']
2885    else if (allGroup[0] === "Ski Rank" && allGroup[1] === "Shooting Rank" && allGroup[2] === "Race Rate Rank") allGroup = ['Ski Rank', 'Shooting Rank', 'Race Rank']
2886    else if (allGroup[0] === "Race Rank" && allGroup[1] === "Shooting Rank" && allGroup[2] === "Ski Rank") allGroup = ['Ski Rank', 'Shooting Rank', 'Race Rank']
2887
2888    var myColor = d3.scaleOrdinal() // D3 Version 4
2889        .domain([allGroup[0], allGroup[1], allGroup[2]])
2890        .range(["rgba(50,136,189,0.25)", "rgba(77, 175, 74,0.25)", "rgba(158,1,66,0.25)", "rgba(171,221,164,0.25)", "#E6F598",
2891            "#FDAE61", "rgba(244,109,67,0.25)", "rgba(213,62,79,0.25)", "rgba(158,1,66,0.25)"])
2892        // .range([  "rgba(55,126,184,0.4)","rgba(77, 175, 74, 0.15)","rgba(228,26,28,0.4)"])
2893        .unknown("rgba(163,154,154,0.1)");
2894    if (isTeamInd && data.length > 600)
2895        myColor.range(["rgba(55,126,184,0.1)", "rgba(77, 175, 74, 0.13)", "rgba(228,26,28,0.13)"])
2896    if ((document.getElementById("darkSwitch").checked))
2897        myColor.range(["rgba(55,126,184,0.2)", "rgba(77, 175, 74, 0.13)", "rgba(228,26,28,0.2)"])
2898    if (isTeamInd && data.length > 600 && (document.getElementById("darkSwitch").checked))
2899        myColor.range(["rgba(55,126,184,0.1)", "rgba(77, 175, 74, 0.13)", "rgba(228,26,28,0.13)"])
2900    var myColor2 = d3.scaleOrdinal() // D3 Version 4
2901        .domain([allGroup[0], allGroup[1], allGroup[2]])
2902        .range(["#3288BD", "rgba(77, 175, 74,1)", "rgba(158,1,66,1)", "#ABDDA4", "#E6F598",
2903            "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"])
2904        .unknown("rgba(163,154,154,0.1)");
2905
2906    if (allGroup.length == 2){
2907        myColor2 = d3.scaleOrdinal() // D3 Version 4
2908            .domain([allGroup[0], allGroup[1], allGroup[2]])
2909            .range([ "rgba(158,1,66,1)", "rgba(77, 175, 74,1)", "#ABDDA4", "#E6F598",
2910                "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"])
2911            .unknown("rgba(163,154,154,0.1)");
2912    }
2913    //allGroup.reverse();
2914    // Reformat the data: we need an array of arrays of {x, y} tuples
2915    let rawvalues = [[], [], []];
2916    var dataReady = allGroup.map(function (grpName, i) {
2917
2918        return {
2919            name: grpName,
2920            values: data.map(function (d) {
2921                rawvalues[i].push(d[grpName]);
2922                return {
2923                    time: d.name,
2924                    value: isNaN(+d[grpName]) ? null : +d[grpName],
2925                    athlete: grpName,
2926                    position: i + 1
2927                };
2928            })
2929        };
2930    });
2931    if (allGroup[0] === "Race Rank" && allGroup[1] === "Ski Rank" && allGroup[2] === "Shooting Rank") dataReady.reverse();
2932    if (allGroup[0] === "Race Rank" && allGroup[1] === "Ski Rank" && allGroup[2] === "Shooting Rank") rawvalues.reverse();
2933
2934    if (allGroup.length == 2 && allGroup[0] === "Race Rank" && allGroup[1] === "Shooting Rank") dataReady.reverse();
2935    if (allGroup.length == 2 && allGroup[0] === "Race Rank" && allGroup[1] === "Shooting Rank") rawvalues.reverse();
2936
2937
2938    var dataAvgValues = [[], [], []]
2939    if (plusminus !== "none") {
2940        let movingAvgValues = function (data, neighbors) {
2941            return data.map((val, idx, arr) => {
2942                let start = Math.max(0, idx - neighbors), end = idx + neighbors
2943                let subset = arr.slice(start, end + 1)
2944                let sum = subset.reduce((a, b) => a + b)
2945                return sum / subset.length
2946            })
2947        }
2948        rawvalues.forEach(function (v, i) {
2949            let newv = v.filter(function (rawv) {
2950                if (rawv === undefined) {
2951                    return false;
2952                }
2953                return true;
2954            })
2955            dataAvgValues[i] = movingAvgValues(newv, Number(plusminus))
2956        })
2957    }
2958
2959    var dataAvgValues = dataAvgValues.filter(function (el) {
2960        return el.length;
2961    });
2962
2963    let dataAveraged = [{name: "Shooting Rank", values: {}, valuesAvg: []}, {
2964        name: "Ski Rank",
2965        values: {},
2966        valuesAvg: []
2967    }, {name: "Race Rank", values: {}, valuesAvg: []}]
2968
2969    let iteratedata = dataReady;
2970
2971    if (isTeamInd && cat === "overview") {
2972        dataReady[0].values.forEach(function (d, i) {
2973            dataAveraged[0].values[d.time] = [];
2974            dataAveraged[1].values[d.time] = [];
2975            dataAveraged[2].values[d.time] = [];
2976        })
2977        dataReady[0].values.forEach(function (d, i) {
2978            dataAveraged[0].values[d.time].push(d.value);
2979        })
2980        dataReady[1].values.forEach(function (d, i) {
2981            dataAveraged[1].values[d.time].push(d.value);
2982        })
2983        dataReady[2].values.forEach(function (d, i) {
2984            dataAveraged[2].values[d.time].push(d.value);
2985        })
2986
2987        rawvalues = [[], [], []];
2988
2989        for (const [key, value] of Object.entries(dataAveraged[0].values)) {
2990            dataAveraged[0].valuesAvg.push({
2991                athlete: "Shooting Rank",
2992                position: 1,
2993                time: key,
2994                value: getAvg(value, 0)
2995            })
2996        }
2997
2998        for (const [key, value] of Object.entries(dataAveraged[1].values)) {
2999            dataAveraged[1].valuesAvg.push({
3000                athlete: "Ski Rank",
3001                position: 1,
3002                time: key,
3003                value: getAvg(value, 1)
3004            })
3005        }
3006
3007        for (const [key, value] of Object.entries(dataAveraged[2].values)) {
3008            dataAveraged[2].valuesAvg.push({
3009                athlete: "Race Rank",
3010                position: 2,
3011                time: key,
3012                value: getAvg(value, 2)
3013            })
3014        }
3015
3016        function getAvg(array, i) {
3017            let count = 0;
3018            let sum = 0;
3019            array.forEach(function (d) {
3020                if (d !== null) {
3021                    count++;
3022                    sum = sum + d;
3023                }
3024            })
3025            if (count != 0 && !Number.isNaN(sum / count)) rawvalues[i].push(sum / count);
3026            return count = 0 || Number.isNaN(sum / count) ? null : sum / count;
3027        }
3028
3029        dataAveraged[0].values = dataAveraged[0].valuesAvg
3030        dataAveraged[1].values = dataAveraged[1].valuesAvg
3031        dataAveraged[2].values = dataAveraged[2].valuesAvg
3032        iteratedata = dataAveraged;
3033
3034        // console.log(rawvalues)
3035
3036        var dataAvgValues = [[], [], []]
3037        if (plusminus !== "none") {
3038            let movingAvgValues = function (data, neighbors) {
3039                return data.map((val, idx, arr) => {
3040                    let start = Math.max(0, idx - neighbors), end = idx + neighbors
3041                    let subset = arr.slice(start, end + 1)
3042                    let sum = subset.reduce((a, b) => a + b)
3043                    return sum / subset.length
3044                })
3045            }
3046            rawvalues.forEach(function (v, i) {
3047                let newv = v.filter(function (rawv) {
3048                    if (rawv === undefined) {
3049                        return false;
3050                    }
3051                    return true;
3052                })
3053                dataAvgValues[i] = movingAvgValues(newv, Number(plusminus))
3054            })
3055        }
3056    }
3057
3058    var dataAvg = [];
3059    iteratedata.forEach(function (c, j) {
3060        let item = {
3061            name: dataReady[j].name,
3062            values: []
3063        }
3064        let counter = 0;
3065        c.values.forEach(function (d, i) {
3066            let vs = {
3067                time: d.time,
3068                value: plusminus !== "none" ? dataAvgValues[j][counter] : null
3069            }
3070            if (d.value !== null) {
3071                counter++;
3072                item.values.push(vs);
3073            }
3074        })
3075        dataAvg.push(item);
3076    })
3077
3078
3079    // console.log(dataAveraged.length, dataAveraged)
3080    //
3081    // console.log(dataReady.length, dataReady)
3082    // console.log(dataAvg.length, dataAvg)
3083
3084    function searchfunction(searchbar) {
3085        d3.select("#SearchBar" + searchbar)
3086            .on("keyup input", function () {
3087                d3.select("#chartdiv").html("");
3088
3089                if (cat === "progression") {
3090                    if (racedata.relayTeams.length > 0) {
3091                        let chartdata = raceProgressionData(racedata.relayTeams, 2);
3092                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
3093                    } else {
3094                        let chartdata = raceProgressionData(racedata.athletes, 1);
3095                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
3096                    }
3097                }
3098                if (cat === "loops") {
3099                    if (racedata.relayTeams.length > 0) {
3100
3101                    } else {
3102                        let chartdata = loopChartData(racedata.athletes, 1)
3103                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 650, chartdata.ticks, 10)
3104                    }
3105                }
3106
3107
3108            })
3109    }
3110
3111    searchfunction("FamilyName");
3112    searchfunction("Nation");
3113
3114    function update(selected) {
3115        svg.selectAll("*").remove();
3116        let latestseason = "";
3117        // Add X axis --> it is a date format
3118        var x = d3.scalePoint()
3119            .range([0, width]);
3120        x.domain(dataReady[0].values.map(function (d) {
3121            return d.time;
3122        }));
3123        let divider = Math.ceil(dataReady[0].values.length / 30);
3124
3125
3126        var xAxis = svg.append("g")
3127            .attr("transform", "translate(0," + height + ")")
3128            .attr("class", "axis")
3129            .call(d3.axisBottom(x)
3130                .tickValues(x.domain().filter(function (d, i) {
3131                    return !(i % divider)
3132                }))
3133                .tickFormat(function (d) {
3134                    if (tickIsSeasonId) {
3135                        if (d.substring(2, 6) !== latestseason) {
3136                            latestseason = d.substring(2, 6);
3137                            if (d.substring(2, 3) > 4)
3138                                return "19" + d.substring(2, 4) + "-" + d.substring(4, 6);
3139                            else
3140                                return "20" + d.substring(2, 4) + "-" + d.substring(4, 6);
3141                        }
3142                    } else
3143                        return d.toString().split(" ")[0]
3144                }).ticks(10)
3145            )
3146            .selectAll("text")
3147            .style("text-anchor", "start")
3148            .attr("dx", function (d) {
3149                return 4;
3150            })
3151            .attr("dy", 8)
3152            .attr("transform", function (d) {
3153                return "rotate(28)";
3154            });
3155        // Add Y axis
3156        var y;
3157        if ((loopstable.includes("Percent") || loopstable.includes("peed") ||
3158            (loopstable.includes("hitRate") && loopstable !== "hitRateRank") || reverse) && !loopstable.includes("Back") && !loopstable.includes("ZScore")) {
3159            y = d3.scaleLinear()
3160                .domain([min, max])
3161                .range([height, 0]);
3162        } else {
3163            y = d3.scaleLinear()
3164                .domain([max, min])
3165                .range([height, 0]);
3166        }
3167
3168        var yAxis = svg.append("g")
3169            .attr("class", "axis")
3170            .call(d3.axisLeft(y).tickFormat(function (d) {
3171                if (typeof chosenstat !== "undefined" && chosenstat === "time")
3172                    return d.toString().toHHMMSS();
3173                else
3174                    return d;
3175            }).ticks(yticks));
3176        // gridlines in x axis function
3177        let tickInterval = Math.ceil(dataAvg[0].values.length / 50);
3178        let tickValues = data.map(function (d) {
3179            return d.name;
3180        }).filter(function (d, i) {
3181            return i % tickInterval === 0
3182        });
3183
3184        function make_x_gridlines() {
3185            return d3.axisBottom(x).tickValues(tickValues)
3186
3187        }
3188
3189        // gridlines in y axis function
3190        function make_y_gridlines() {
3191            return d3.axisLeft(y)
3192        }
3193
3194        svg.append("text")
3195            .attr("x", 16)
3196            .attr("y", 16)
3197            .attr("class", "svgdarktext")
3198            .text("realbiathlon.com")
3199            .style("font-size", "12px")
3200            .style("opacity", 0.4)
3201
3202        // add the X gridlines
3203        svg.append("g")
3204            .attr("class", "grid")
3205            .attr("transform", "translate(0," + height + ")")
3206            .call(make_x_gridlines()
3207                .tickSize(-height)
3208                .tickFormat("")
3209                .ticks(10)
3210            );
3211        svg.append("g")
3212            .attr("class", "grid")
3213            .call(make_y_gridlines()
3214                .tickSize(-width)
3215                .tickFormat("")
3216            )
3217
3218        var p = svg.selectAll("path")
3219            .data(dataReady)
3220        // Add the lines
3221        var line = d3.line()
3222            .x(function (d) {
3223                return x(d.time)
3224            })
3225            .y(function (d) {
3226                return y(d.value)
3227            })
3228            .defined(function (d) {
3229                return d !== undefined && !Number.isNaN(d.value) && d.value !== null;
3230            })
3231
3232        var curvedLine = d3.line()
3233            .x(function (d) {
3234                return x(d.time)
3235            })
3236            .y(function (d) {
3237                return y(d.value)
3238            })
3239            .defined(function (d) {
3240                return d !== undefined && !Number.isNaN(d.value) && d.value !== undefined && d.value !== null && d.value !== 0;
3241            })
3242            .curve(d3.curveBasis)
3243
3244        if (!isTeamInd) {
3245            // svg.selectAll("myLines")
3246            //     .data(dataReady)
3247            //     .enter()
3248            //     .append("path").classed("line", true)
3249            //     .attr("class", function (d) {
3250            //         return d.name.replace(/. /g, '');
3251            //     })
3252            //     .attr("d", function (d) {
3253            //         if (d !== undefined && !Number.isNaN(d.value) )
3254            //             return line(d.values)
3255            //     })
3256            //     .attr("stroke", function (d) {
3257            //         return  myColor(d.name);
3258            //     })
3259            //     .style("stroke-width", 1)
3260            //     .style("fill", "none")
3261        }
3262
3263        var u = svg.selectAll("circle")
3264            .data(dataReady)
3265        // Add the points
3266        svg
3267            // First we need to enter in a group
3268            .selectAll("myDots").merge(u)
3269            .data(dataReady)
3270            .enter()
3271            .append('g')
3272            .attr("class", function (d) {
3273                if (d.name !== undefined)
3274                    return d.name.replace(/. /g, '')
3275            })
3276            .style("fill", function (d) {
3277                if (d !== undefined && !Number.isNaN(d.value))
3278                    return myColor(d.name);
3279            })
3280            // Second we need to enter in the 'values' part of this group
3281            .selectAll("myPoints")
3282            .data(function (d) {
3283                if (d !== undefined)
3284                    return d.values
3285            })
3286            .enter()
3287            .append("circle")
3288            .attr("cx", function (d) {
3289                if (d !== undefined && !Number.isNaN(d.value) && d.value !== undefined && d.value !== null && d.value !== 0)
3290                    return x(d.time)
3291            })
3292            .attr("cy", function (d) {
3293                if (d !== undefined && !Number.isNaN(d.value) && d.value !== undefined && d.value !== null && d.value !== 0)
3294                    return y(d.value)
3295            })
3296            .attr("r", function (d) {
3297                if (d !== undefined && !Number.isNaN(d.value) && d.value !== undefined && d.value !== null && d.value !== 0)
3298                    return 4
3299            })
3300            .attr("stroke", function () {
3301                if (data.length < 300)
3302                    return "white"
3303                else
3304                    return "";
3305            })
3306            .attr("stroke-opacity", 0)
3307            .on("mouseover", function (d) {
3308                d3.select(this).style("cursor", "pointer");
3309                d3.select(this).attr("r", 7);
3310
3311
3312                div.style("opacity", .8);
3313                let seasonstring = "";
3314                if (d.time.substring(2, 3) > 4)
3315                    seasonstring = "19" + d.time.substring(2, 4) + "-" + d.time.substring(4, 6);
3316                else
3317                    seasonstring = "20" + d.time.substring(2, 4) + "-" + d.time.substring(4, 6);
3318                if (tickIsSeasonId) {
3319                    div.html(seasonstring + "<br/>  " + d.time.substring(10, 14).replace("__", "").replace("CP0", "WC ") +
3320                        " " + d.time.slice(-2) + "<br/> Value: " + d.value.toFixed(1).replace(".0", ""));
3321                } else {
3322                    if (chosenstat === "time")
3323                        div.html(d.time + "<br/>  " + "Value: " + d.value.toString().toHHMMSS());
3324                    else
3325                        div.html(d.time + "<br/>  " + "Value: " + d.value.toFixed(1).replace(".0", ""));
3326
3327                }
3328
3329
3330            })
3331            .on("mousemove", function (d) {
3332                div
3333                    .style("left", (d3.event.pageX + 15) + "px")
3334                    .style("top", (d3.event.pageY + 15) + "px")
3335            })
3336            .on("mouseout", function (d) {
3337                d3.select(this).attr("r", 4);
3338
3339                div
3340                    .style("opacity", 0);
3341            });
3342        if (plusminus !== "none") {
3343            svg.selectAll("myTrendLines")
3344                .data(dataAvg)
3345                .enter()
3346                .append("path").classed("line", true)
3347                .attr('class', 'avg')
3348                .attr("d", function (d) {
3349                    if (d !== undefined && !Number.isNaN(d.value) && d.values !== undefined && d.values !== null)
3350                        return curvedLine(d.values)
3351                })
3352                .attr("stroke", function (d) {
3353                    return dataAvgValues.length > 1 ? myColor2(d.name) : "rgba(228,26,28,1)";
3354                })
3355                .style("stroke-width", 3)
3356                .style("fill", "none")
3357        }
3358
3359        if (dataAvgValues.length > 1) {
3360            svg
3361                .selectAll("myLegend")
3362                .data(dataReady)
3363                .enter()
3364                .append('g')
3365                .append("text")
3366                .attr('x', function (d, i) {
3367                    if (d.values[0].position < 4)
3368                        return (-50 + d.values[0].position * 170);
3369                })
3370                .attr('y', -15)
3371                .text(function (d) {
3372                    return d.name;
3373                })
3374                .style("fill", function (d) {
3375                    return myColor2(d.name)
3376                })
3377                .style("font-size", 15)
3378        }
3379
3380    }
3381
3382    update(0);
3383}
3384
3385
3386function boxplot(size, data1, publishedAt, timemax, min, max, reverse, width, height, xticks, yticks, plusminus, tickIsSeasonId) {
3387    var labels = true; // show the text labels beside individual boxplots?
3388
3389    var margin = {top: 5, right: 5, bottom: 20, left: 50};
3390    var width = 800 - margin.left - margin.right;
3391    var height = 400 - margin.top - margin.bottom;
3392
3393    var min = Infinity,
3394        max = -Infinity;
3395
3396
3397    var data = [];
3398    var allGroup = [];
3399    var spliceGroup = [];
3400    let counter = 0;
3401    for (const [key, value] of Object.entries(data1[0])) {
3402        if (key !== "name") {
3403
3404            allGroup.push(key);
3405            spliceGroup.push(key);
3406
3407            data[counter] = [];
3408            let itemUp = key.charAt(0).toUpperCase() + key.slice(1)
3409            if (key.includes("–"))
3410                itemUp = key;
3411            else if (key.match(/[A-Z][a-z]+|[0-9]+/g) === null)
3412                itemUp = itemUp.replace("Secondplaces", "Second Places").replace("Thirdplaces", "Third Places")
3413            else
3414                itemUp = itemUp.match(/[A-Z][a-z]+|[0-9]+/g).join(" ").replace("Score", "zScore")
3415
3416            data[counter][0] = itemUp;
3417
3418            data[counter][1] = [];
3419
3420            counter++;
3421        }
3422
3423    }
3424
3425    data1.forEach(function (x) {
3426
3427        allGroup.forEach(function (g, i) {
3428            if (x[g] !== undefined && x[g] !== null && x[g] !== "none" && x[g] !== "null" && x[g] !== "") {
3429                spliceGroup.splice(spliceGroup.indexOf(g), 1)
3430                var v1 = x[g];
3431                // add more variables if your csv file has more columns
3432
3433                data[i][1].push(v1);
3434
3435                // add more rows if your csv file has more columns
3436
3437                if (v1 > max) max = v1;
3438                if (v1 < min) min = v1;
3439            }
3440
3441        })
3442
3443
3444    });
3445
3446    var times = data.length;
3447    for (var i = 0; i < times; i++) {
3448        data.forEach(function (r) {
3449            if (r[1].length === 0) {
3450                let index = data.indexOf(r);
3451                if (index !== -1) data.splice(index, 1);
3452            }
3453        })
3454    }
3455    columnsNo = data.length;
3456
3457
3458    max = max + (max - min) * 0.05
3459    min = min - (max - min) * 0.05
3460    if (loopstable === "Rankings" || loopstable.includes("Rank") || loopstable.includes("rank")) {
3461        min = -3;
3462    }
3463    if (loopstable === "totalPercent" || loopstable === "pronePercent" || loopstable === "standingPercent" || loopstable === "Shooting Percentages") {
3464        max = 104;
3465
3466    }
3467    // console.log(data, min, max)
3468
3469    var chart = d3.box()
3470        .whiskers(iqr(1.5))
3471        .height(height)
3472        .domain([min, max])
3473        .showLabels(labels);
3474
3475    var svg = d3.select("div#" + size + "chartdiv")
3476        .append("div")
3477        // Container class to make it responsive.
3478        .classed("svg-container", true)
3479        .append("svg")
3480        .attr("preserveAspectRatio", "xMinYMin meet")
3481        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
3482        .classed("svg-content-responsive", true)
3483        .append("g")
3484        .attr("transform",
3485            "translate(" + margin.left + "," + margin.top + ")");
3486
3487    // the x-axis
3488    var x = d3.scaleBand()
3489        .domain(data.map(function (d) {
3490            return d[0]
3491        }))
3492        .range([0, width]);
3493
3494
3495    var xAxis = svg.append("g")
3496        .attr("transform", "translate(0," + (height + 0) + ")")
3497        .attr("class", "axis")
3498        .call(d3.axisBottom(x))
3499
3500
3501    // the y-axis
3502    var y = d3.scaleLinear()
3503        .domain([min, max])
3504        .range([height + margin.top, 0 + margin.top]);
3505
3506
3507    var yAxis = svg.append("g")
3508        .attr("class", "axis")
3509        .call(d3.axisLeft(y))
3510
3511
3512    // draw the boxplots
3513    svg.selectAll(".box")
3514        .data(data)
3515        .enter().append("g")
3516        .attr("transform", function (d) {
3517            return "translate(" + x(d[0]) + "," + margin.top + ")";
3518        })
3519        .call(chart.width(Math.abs(x.bandwidth())));
3520
3521
3522// Returns a function to compute the interquartile range.
3523    function iqr(k) {
3524        return function (d, i) {
3525            var q1 = d.quartiles[0],
3526                q3 = d.quartiles[2],
3527                iqr = (q3 - q1) * k,
3528                i = -1,
3529                j = d.length;
3530            while (d[++i] < q1 - iqr) ;
3531            while (d[--j] > q3 + iqr) ;
3532            return [i, j];
3533        };
3534    }
3535}
3536
3537function divergingbarchart(size, container, data, n1, n2, w, h, offset) {
3538
3539    // set the dimensions and margins of the graph
3540    var margin = {top: 0, right: 150, bottom: 30, left: 150},
3541        width = w - margin.left - margin.right,
3542        height = h - margin.top - margin.bottom;
3543    let addon = "";
3544    const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
3545    if (vw < 700) {
3546        margin.right = 35;
3547        margin.left = 35;
3548        addon = "1";
3549    }
3550
3551
3552    var div = d3.select("body").append("div")
3553        .attr("class", 'tooltip')
3554        .style("opacity", 0);
3555
3556
3557    var svg = d3.select("div#" + size + "chartdiv")
3558        .append("div")
3559        // Container class to make it responsive.
3560        .classed("svg-container" + addon + container, true)
3561        .append("svg")
3562        .attr("preserveAspectRatio", "xMinYMin meet")
3563        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
3564        .classed("svg-content-responsive", true)
3565        .append("g")
3566        .attr("transform",
3567            "translate(" + margin.left + "," + margin.top + ")");
3568
3569
3570    // Config
3571    var cfg = {
3572        labelMargin: 5,
3573        xAxisMargin: 10,
3574        legendRightMargin: 0
3575    }
3576    var max = d3.max(data, function (d) {
3577        return d.diff;
3578    });
3579    let maxwidth = 0;
3580    data.forEach(function (d, i) {
3581        if (Math.abs(d.diff) > maxwidth) maxwidth = Math.abs(d.diff);
3582    })
3583    var x = d3.scaleLinear()
3584        .domain([-maxwidth, maxwidth])
3585
3586        .range([0, width]);
3587
3588    var colour = d3.scaleSequential(d3.interpolateRdYlGn);
3589
3590    var y = d3.scaleBand()
3591        .range([height, 0])
3592        .padding(0.1);
3593
3594    function parse(d) {
3595        d.rank = +d.rank;
3596        d.diff = +d.diff;
3597        return d;
3598    }
3599
3600    var legend = svg.append("g")
3601        .attr("class", "legend");
3602
3603    legend.append("text")
3604        .attr("x", 0)
3605        .attr("y", 16)
3606        .attr("text-anchor", "start")
3607        .style("opacity", 0.8)
3608        .style("font-size", 12)
3609        .style("fill", function (d) {
3610            return "grey";
3611        })
3612        .text("← advantage " + n1);
3613
3614
3615    legend.append("text")
3616        .attr("x", width)
3617        .attr("y", 16)
3618        .attr("text-anchor", "end")
3619        .style("opacity", 0.8)
3620        .style("font-size", 12)
3621        .style("fill", function (d) {
3622            return "grey";
3623        })
3624        .text("advantage " + n2 + " →");
3625
3626    let index = []
3627    let allnegative = true;
3628    data.forEach(function (d, i) {
3629        d.headline = false;
3630        if (d.diff > 0) allnegative = false;
3631        if (d.category.includes("Loop 1") || d.category.includes("Shooting Time 1") || d.category.includes("Range Time 1"))
3632            index.push({cat: d.category.replace(" 1", "").replace(" Loop", ""), num: data.indexOf(d)});
3633    })
3634    index.forEach(function (d, i) {
3635        data.splice(d.num + i + 1, 0, {category: d.cat, diff: 0, headline: true, i: i})
3636
3637    })
3638    let currentI = 1;
3639    data.forEach(function (d, i) {
3640        if (d.headline) currentI = d.i - 1;
3641        d.i = currentI;
3642    })
3643
3644
3645    y.domain(data.map(function (d) {
3646        return d.category;
3647    }));
3648    // x.domain(d3.extent(data, function(d) { return d.diff; }));
3649
3650    colour.domain([max, -max]);
3651    if (allnegative) colour.domain([-max, max]);
3652
3653
3654    var yAxis = svg.append("g")
3655        .attr("class", "y-axis")
3656        .attr("transform", "translate(" + x(0) + ",0)")
3657        .append("line")
3658        .attr("y1", 0)
3659        .attr("y2", height);
3660
3661    var xAxis = svg.append("g")
3662        .attr("class", "axis")
3663        .attr("transform", "translate(0," + (height + cfg.xAxisMargin) + ")")
3664        .call(d3.axisBottom(x).tickSizeOuter(0));
3665
3666    // gridlines in x axis function
3667    function make_x_gridlines() {
3668        return d3.axisBottom(x).ticks(30)
3669    }
3670
3671
3672    // add the X gridlines
3673    svg.append("g")
3674        .attr("class", "grid")
3675        .style("opacity", 0.5)
3676        .attr("transform", "translate(0," + height + ")")
3677
3678        .call(make_x_gridlines()
3679            .tickSize(-height)
3680            .tickFormat("")
3681        );
3682
3683    var bars0 = svg.append("g")
3684        .attr("class", "bars")
3685
3686    bars0.selectAll("rect")
3687        .data(data)
3688        .enter().append("rect")
3689        .attr("class", "annual-growth")
3690        .attr("x", function (d) {
3691            return 0;
3692        })
3693        .attr("y", function (d) {
3694            return y(d.category) - 1;
3695        })
3696        .attr("height", y.bandwidth() + 2)
3697        .attr("width", function (d) {
3698            if (d.category.includes("Total"))
3699                return width;
3700        })
3701        .style("fill", function (d) {
3702            return "grey";
3703        })
3704        .style("opacity", function (d) {
3705            return 0.07;
3706
3707        })
3708
3709    var bars = svg.append("g")
3710        .attr("class", "bars")
3711
3712    bars.selectAll("rect")
3713        .data(data)
3714        .enter().append("rect")
3715        .attr("class", "annual-growth")
3716        .attr("x", function (d) {
3717            return x(Math.min(0, d.diff));
3718        })
3719        .attr("y", function (d) {
3720            return y(d.category);
3721        })
3722        .attr("height", y.bandwidth())
3723        .attr("width", function (d) {
3724            return Math.abs(x(d.diff) - x(0))
3725        })
3726        .style("fill", function (d) {
3727            return colour(d.diff)
3728        })
3729
3730        .on("mouseover", function (d) {
3731            d3.select(this).style("opacity", 0.7);
3732            d3.select(this).style("cursor", "pointer");
3733            div.style("width", "185px");
3734
3735            div.style("opacity", .8);
3736            if (sh1.familyName === undefined)
3737                div.html(d.category + "<br/>" + sh1.nat + ": " + d.value1.toString().toHHMMSS() + "<br/>" + sh2.nat + ": " + d.value2.toString().toHHMMSS());
3738            else
3739                div.html(d.category + "<br/>" + sh1.givenName + " " + sh1.familyName + ": " + d.value1.toString().toHHMMSS() + "<br/>" + sh2.givenName + " " + sh2.familyName + ": " + d.value2.toString().toHHMMSS());
3740        })
3741        .on("mousemove", function (d) {
3742            div
3743                .style("left", (d3.event.pageX + 15) + "px")
3744                .style("top", (d3.event.pageY + 15) + "px")
3745        })
3746        .on("mouseout", function (d) {
3747            d3.select(this).style("opacity", 1);
3748            div.style("opacity", 0);
3749
3750        })
3751
3752    var labels = svg.append("g")
3753        .attr("class", "labels");
3754
3755    labels.selectAll("text")
3756        .data(data)
3757        .enter().append("text")
3758        .attr("class", function (d) {
3759            if (d.headline) {
3760                return "bar-label headline-label";
3761            } else {
3762                return "bar-label";
3763            }
3764        })
3765        .attr("x", x(0))
3766        .attr("y", function (d) {
3767            return y(d.category) - offset
3768        })
3769        .attr("dx", function (d) {
3770            if (d.headline)
3771                return cfg.labelMargin + 10;
3772            else
3773                return d.diff < 0 ? cfg.labelMargin : -cfg.labelMargin;
3774
3775        })
3776        .attr("dy", y.bandwidth())
3777        .attr("text-anchor", function (d) {
3778            return d.diff < 0 ? "start" : "end";
3779        })
3780        .text(function (d) {
3781            return d.category
3782        })
3783        .style("font-weight", function (d) {
3784            if (d.category.includes("Total")) {
3785                return "bold";
3786            }
3787        })
3788        .style("opacity", function (d) {
3789            if (d.headline) {
3790                return 0;
3791            }
3792        })
3793
3794    var labels2 = svg.append("g")
3795        .attr("class", "labels2");
3796
3797    labels2.selectAll("text")
3798        .data(data)
3799        .enter().append("text")
3800        .attr("class", function (d) {
3801            return "bar-label2";
3802        })
3803        .attr("x", function (d) {
3804            return x(d.diff);
3805        })
3806        .attr("y", function (d) {
3807            return y(d.category) - 5
3808        })
3809        .attr("dx", function (d) {
3810            return d.diff < 0 ? cfg.labelMargin - 10 : -cfg.labelMargin + 10;
3811
3812        })
3813        .attr("dy", y.bandwidth())
3814        .attr("text-anchor", function (d) {
3815            return d.diff < 0 ? "end" : "start";
3816        })
3817        .text(function (d) {
3818            return d.diff > 0 ? "+" + d.diff.toFixed(1) + "s" : d.diff.toFixed(1) + "s"
3819        })
3820        .style("opacity", function (d) {
3821            if (d.headline) {
3822                return 0;
3823            }
3824        })
3825
3826    var lines = svg.append("g")
3827        .attr("class", "mean-line");
3828    lines
3829        .selectAll("line")
3830        .data(data)
3831        .enter().append("line")
3832
3833        .attr("class", "mean-line")
3834
3835        .attr("x1", 0)
3836        .attr("y1", function (d) {
3837            return y(d.category) + 10
3838        })
3839        .attr("x2", 500)
3840        .attr("y2", function (d) {
3841            return y(d.category) + 10
3842        })
3843
3844        .style("stroke", function (d, i) {
3845            if (d.headline && i + 1 < data.length)
3846                return "grey"
3847        })
3848        .style("stroke-width", 1)
3849        .style("opacity", function (d) {
3850            return 0.3;
3851        })
3852
3853
3854}
3854
3855
3856function histogram1(size, data1, name1, name2, type) {
3857    // set the dimensions and margins of the graph
3858    var margin = {top: 25, right: 30, bottom: 30, left: 40},
3859        width = 800 - margin.left - margin.right,
3860        height = 400 - margin.top - margin.bottom;
3861
3862    var div = d3.select("body").append("div")
3863        .attr("class", 'tooltip')
3864        .style("opacity", 0);
3865
3866    var svg = d3.select("div#" + size + "chartdiv")
3867        .append("div")
3868        // Container class to make it responsive.
3869        .classed("svg-container", true)
3870        .append("svg")
3871        .attr("preserveAspectRatio", "xMinYMin meet")
3872        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
3873        .classed("svg-content-responsive", true)
3874        .append("g")
3875        .attr("transform",
3876            "translate(" + margin.left + "," + margin.top + ")");
3877
3878    let data = [];
3879    let values = []
3880    // console.log(data1)
3881
3882    data1.forEach(function (d, i) {
3883        if (d[loopstable] !== undefined && d[loopstable] !== null && d[loopstable] !== "" && d[loopstable] !== "null" && !isNaN(d[loopstable])) {
3884            data.push({[loopstable]: d[loopstable], name: d.name});
3885            values.push(d[loopstable])
3886        }
3887    })
3888
3889    // X axis: scale and draw:
3890    var x = d3.scaleLinear()
3891        // .domain([d3.min(data, function(d) { return +d[loopstable] }), d3.max(data, function(d) { return +d[loopstable] })])
3892        .domain([d3.min(data, function (d) {
3893            return +d[loopstable]
3894        }), d3.max(data, function (d) {
3895            return +d[loopstable]
3896        })])
3897        .range([0, width]);
3898
3899
3900    svg.append("g")
3901        .attr("transform", "translate(0," + height + ")")
3902        .call(d3.axisBottom(x)).attr("class", "axis")
3903
3904    // Y axis: initialization
3905    var y = d3.scaleLinear()
3906        .range([height, 0]);
3907    var yAxis = svg.append("g")
3908        .attr("class", "axis")
3909
3910
3911    svg.append("text")
3912        .attr("x", 620)
3913        .attr("y", 16)
3914        .attr("class", "svgdarktext")
3915        .text("realbiathlon.com")
3916        .style("font-size", "12px")
3917        .style("opacity", 0.4)
3918
3919    // A function that builds the graph for a specific value of bin
3920    function update(nBin) {
3921
3922        d3.selectAll("circle").remove()
3923        d3.selectAll(".legend").remove()
3924        d3.selectAll(".grid").remove()
3925
3926        let [min, max] = d3.extent(values);
3927
3928        let thresholds = d3.range(min, max, (max - min) / (nBin));
3929        let id = thresholds.indexOf(max);
3930        if (id > -1) {
3931            thresholds.splice(id, 1);
3932        }
3933
3934        document.getElementById("nBin").value = nBin;
3935
3936        // set the parameters for the histogram
3937        var histogram = d3.histogram()
3938            .value(function (d) {
3939                return d[loopstable];
3940            })   // I need to give the vector of value
3941            .domain(x.domain())  // then the domain of the graphic
3942            .thresholds(thresholds); // then the numbers of bins
3943
3944        // console.log(data)
3945        // And apply this function to data to get the bins
3946        // And apply twice this function to data to get the bins.
3947        var bins = histogram(data.filter(function (d) {
3948            return d.name === name1
3949        }));
3950        var bins2 = histogram(data.filter(function (d) {
3951            return d.name === name2
3952        }));
3953
3954        // Y axis: update now that we know the domain
3955        y.domain([0, Math.max(d3.max(bins, function (d) {
3956            return d.length;
3957        }), d3.max(bins2, function (d) {
3958            return d.length;
3959        }))]);   // d3.hist has to be called before the Y axis obviously
3960        yAxis
3961            .call(d3.axisLeft(y));
3962
3963        let colordefault = type === 2 ? "#9e0142" : "#3288bd";
3964
3965        // gridlines in x axis function
3966        function make_x_gridlines() {
3967            return d3.axisBottom(x).ticks(10)
3968        }
3969
3970        // gridlines in y axis function
3971        function make_y_gridlines() {
3972            return d3.axisLeft(y).ticks(10)
3973        }
3974
3975        // add the X gridlines
3976        svg.append("g")
3977            .attr("class", "grid")
3978            .style("opacity", 0.3)
3979            .attr("transform", "translate(0," + height + ")")
3980            .call(make_x_gridlines()
3981                .tickSize(-height)
3982                .tickFormat("")
3983            );
3984        svg.append("g")
3985            .attr("class", "grid")
3986            .style("opacity", 0.3)
3987            .call(make_y_gridlines()
3988                .tickSize(-width)
3989                .tickFormat("")
3990            )
3991
3992        if (type === 2) {
3993            // Join the rect with the bins data
3994            var u2 = svg.selectAll(".rect2")
3995                .data(bins2)
3996
3997            // Manage the existing bars and eventually the new ones:
3998            u2
3999                .enter()
4000                .append("rect") // Add a new rect for each new elements
4001                .attr("class", "rect2")
4002                .on("mouseover", function (d) {
4003                    d3.select(this).style("opacity", 0.8);
4004                    d3.select(this).style("cursor", "pointer");
4005                    div.style("height", "32px");
4006
4007                    div.style("opacity", .8);
4008                    div.html("Range: " + d.x0.toFixed(1) + " - " + d.x1.toFixed(1) + "<br/>Frequency : " + d.length);
4009                })
4010                .on("mousemove", function (d) {
4011                    div
4012                        .style("left", (d3.event.pageX + 15) + "px")
4013                        .style("top", (d3.event.pageY + 15) + "px")
4014                })
4015                .on("mouseout", function (d) {
4016                    d3.select(this).style("opacity", 0.6);
4017                    div.style("opacity", 0);
4018                    div.style("height", "75px");
4019
4020                })
4021                .merge(u2) // get the already existing elements as well
4022                .attr("x", 1)
4023                .attr("transform", function (d) {
4024                    return "translate(" + (x(d.x0) + 0) + "," + y(d.length) + ")";
4025                })
4026                .attr("width", function (d) {
4027                    return Math.max(0, x(d.x1) - x(d.x0) - 1);
4028                })
4029                .attr("height", function (d) {
4030                    return height - y(d.length);
4031                })
4032                .style("fill", "#3288bd")
4033                .style("opacity", 0.6)
4034            u2
4035                .exit()
4036                .remove()
4037        }
4038
4039
4040        // Join the rect with the bins data
4041        var u = svg.selectAll(".rect1")
4042            .data(bins)
4043
4044        // Manage the existing bars and eventually the new ones:
4045        u
4046            .enter()
4047            .append("rect") // Add a new rect for each new elements
4048            .attr("class", "rect1")
4049            .on("mouseover", function (d) {
4050                d3.select(this).style("opacity", 0.8);
4051                d3.select(this).style("cursor", "pointer");
4052                div.style("height", "32px");
4053                div.style("opacity", .8);
4054                div.html("Range: " + d.x0.toFixed(1) + " - " + d.x1.toFixed(1) + "<br/>Frequency : " + d.length);
4055            })
4056            .on("mousemove", function (d) {
4057                div
4058                    .style("left", (d3.event.pageX + 15) + "px")
4059                    .style("top", (d3.event.pageY + 15) + "px")
4060            })
4061            .on("mouseout", function (d) {
4062                d3.select(this).style("opacity", 0.6);
4063                div.style("opacity", 0);
4064                div.style("height", "75px");
4065            })
4066            .merge(u) // get the already existing elements as well
4067            .attr("x", 1)
4068            .attr("transform", function (d) {
4069                return "translate(" + x(d.x0) + "," + y(d.length) + ")";
4070            })
4071            .attr("width", function (d) {
4072                return Math.max(0, x(d.x1) - x(d.x0) - 1);
4073            })
4074            .attr("height", function (d) {
4075                return height - y(d.length);
4076            })
4077            .style("fill", colordefault)
4078            .style("opacity", 0.6)
4079
4080
4081        // If less bar in the new histogram, I delete the ones not in use anymore
4082        u
4083            .exit()
4084            .remove()
4085
4086        if (type === 2) {
4087            // Handmade legend
4088            svg.append("circle").attr("cx", 100).attr("cy", -15).attr("r", 6).style("fill", "#e41a1c").style("opacity", 0.6)
4089            svg.append("circle").attr("cx", 400).attr("cy", -15).attr("r", 6).style("fill", "#377eb8").style("opacity", 0.6)
4090            svg.append("text").attr("x", 120).attr("y", -15).text(name
40901).style("font-size", "15px").attr("alignment-baseline", "middle").attr("class", "legend svgdarktext2")
4091            svg.append("text").attr("x", 420).attr("y", -15).text(name2).style("font-size", "15px").attr("alignment-baseline", "middle").attr("class", "legend svgdarktext2")
4092
4093        }
4094    }
4095
4096    update(bins)
4097
4098    // Listen to the button -> update if user change it
4099    d3.select("#nBin").on("input", function () {
4100        update(+this.value);
4101        bins = this.value;
4102    });
4103
4104
4105}
4106
4107function chart1(size, data, publishedAt, timemax, min, max, width, height, xticks, yticks, type, hidetop) {
4108
4109    let hidetopnames = hidetop === undefined || hidetop === false ? false : true;
4110    // Define the div for the tooltip
4111    var div = d3.select("body").append("div")
4112        .attr("class", 'tooltip')
4113        .style("opacity", 0);
4114
4115    function clearAll() {
4116        buttons.selectAll('rect')
4117            .each(function (d, i) {
4118                button.clear.call(this, d, i)
4119            });
4120    }
4121
4122    // set the dimensions and margins of the graph
4123    var margin = {top: 30, right: 130, bottom: 60, left: 60},
4124        width = width - margin.left - margin.right,
4125        height = height - margin.top - margin.bottom;
4126    // append the svg object to the body of the page
4127    var svg = d3.select("div#" + size + "chartdiv")
4128        .append("div")
4129        // Container class to make it responsive.
4130        .classed("svg-container", true)
4131        .append("svg")
4132        .attr("preserveAspectRatio", "xMinYMin meet")
4133        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
4134        .classed("svg-content-responsive", true)
4135        .append("g")
4136        .attr("transform",
4137            "translate(" + margin.left + "," + margin.top + ")");
4138    // List of groups (here I have one group per column)
4139    var allGroup = [];
4140    for (const [key, value] of Object.entries(data[0])) {
4141        if (key !== "name")
4142            allGroup.push(key);
4143    }
4144
4145    let relativeindex;
4146    if (typeof datarace1 !== "undefined") {
4147        if (data[0].name === "Start")
4148            relativeindex = 5;
4149    }
4150    if (window.location.href.includes("races.html")) {
4151        racedata.athletes.forEach(function (a) {
4152            if (a.ibuid === relativeathlete)
4153                relativeindex = racedata.athletes.indexOf(a);
4154        })
4155
4156        racedata.relayTeams.forEach(function (a) {
4157            if (a.ibuid === relativeathlete)
4158                relativeindex = racedata.relayTeams.indexOf(a);
4159        })
4160    }
4161    if (window.location.href.includes("scores.html")) {
4162        scoresdata.scores.forEach(function (a) {
4163            if (a.ibuid === relativeathlete)
4164                relativeindex = scoresdata.scores.indexOf(a);
4165        })
4166    }
4167
4168    var myColor = d3.scaleOrdinal() // D3 Version 4
4169        .domain([allGroup[relativeindex], allGroup[0], allGroup[1], allGroup[2]])
4170        .range(["#cf06f1", "rgba(158,1,66,0.8)", "rgba(50,136,189,0.8)", "rgba(77, 175, 74,0.8)"])
4171        .unknown("rgba(163,154,154,0.3)");
4172    if ((document.getElementById("darkSwitch").checked))
4173        myColor.unknown("rgba(163,154,154,0.08)");
4174    //allGroup.reverse();
4175    // Reformat the data: we need an array of arrays of {x, y} tuples
4176    var dataReady = allGroup.map(function (grpName, i) {
4177        return {
4178            name: grpName,
4179            values: data.map(function (d) {
4180                return {time: d.name, value: +d[grpName], athlete: grpName, position: i + 1};
4181            })
4182        };
4183    });
4184    dataReady = dataReady.sort(function (a, b) {
4185        return a.values[dataReady[0].values.length - 1].value - b.values[dataReady[0].values.length - 1].value;
4186    });
4187    dataReady.forEach(function (v, i) {
4188        v.values[dataReady[0].values.length - 1].position = i + 1
4189    })
4190    // dataReady.reverse();
4191
4192    // console.log(dataReady);
4193
4194    if (typeof loopstable !== 'undefined') {
4195        if (loopstable === "netTime") {
4196            let temp = min;
4197            min = max;
4198            max = temp;
4199        }
4200    }
4201
4202    function searchfunction(searchbar) {
4203        d3.select("#SearchBar" + searchbar)
4204            .on("keyup input", function () {
4205                d3.select("div#" + size + "chartdiv").html("");
4206
4207                if (cat === "progression") {
4208                    if (racedata.relayTeams.length > 0) {
4209                        let chartdata = raceProgressionData(racedata.relayTeams, 2);
4210                        chart1(size, chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 550, chartdata.ticks, 10, type)
4211                    } else {
4212                        let chartdata = raceProgressionData(racedata.athletes, 1);
4213                        chart1(size, chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 550, chartdata.ticks, 10, type)
4214                    }
4215                }
4216                if (cat === "loops") {
4217                    if (racedata.relayTeams.length > 0) {
4218
4219                    } else {
4220                        let chartdata = loopChartData(racedata.athletes, 1)
4221                        chart1(size, chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 550, chartdata.ticks, 10, type)
4222                    }
4223                }
4224
4225
4226            })
4227    }
4228
4229    searchfunction("FamilyName");
4230    searchfunction("Nation");
4231
4232
4233    function update(selected) {
4234        svg.selectAll("*").remove();
4235        // Add X axis --> it is a date format
4236        var x = d3.scalePoint().rangeRound([0, width])
4237            .range([0, width]);
4238        x.domain(dataReady[0].values.map(function (d) {
4239            return d.time;
4240        }));
4241        var xAxis = svg.append("g")
4242            .attr("transform", "translate(0," + height + ")")
4243            .attr("class", "axis")
4244            .call(d3.axisBottom(x).ticks(22))
4245            .selectAll("text")
4246            .style("text-anchor", "start")
4247            .attr("dx", function (d) {
4248                return 4;
4249            })
4250            .attr("dy", 8)
4251            .attr("transform", function (d) {
4252                return "rotate(28)";
4253            });
4254
4255
4256        // Add Y axis
4257        var y = d3.scaleLinear()
4258            .domain([max, min])
4259            .range([height, 0]);
4260        var yAxis = svg.append("g")
4261            .attr("class", "axis")
4262            .call(d3.axisLeft(y).tickFormat(function (d) {
4263                if (type !== "notimes") {
4264                    if (d < 0) {
4265                        let temp = d * -1;
4266                        return "-" + temp.toString().toHHMMSS();
4267                    } else
4268                        return d.toString().toHHMMSS();
4269                } else
4270                    return d;
4271            }).ticks(yticks));
4272
4273        // gridlines in x axis function
4274        function make_x_gridlines() {
4275            return d3.axisBottom(x).ticks(xticks)
4276        }
4277
4278        // gridlines in y axis function
4279        function make_y_gridlines() {
4280            return d3.axisLeft(y).ticks(yticks)
4281        }
4282
4283        svg.append("text")
4284            .attr("x", 20)
4285            .attr("y", 16)
4286            .attr("class", "svgdarktext")
4287            .text("realbiathlon.com")
4288            .style("font-size", "12px")
4289            .style("opacity", 0.4)
4290
4291        // add the X gridlines
4292        svg.append("g")
4293            .attr("class", "grid")
4294            .attr("transform", "translate(0," + height + ")")
4295            .call(make_x_gridlines()
4296                .tickSize(-height)
4297                .tickFormat("")
4298            );
4299        svg.append("g")
4300            .attr("class", "grid")
4301            .call(make_y_gridlines()
4302                .tickSize(-width)
4303                .tickFormat("")
4304            )
4305        var p = svg.selectAll("path")
4306            .data(dataReady)
4307        // Add the lines
4308        var line = d3.line()
4309            .x(function (d) {
4310                return x(d.time)
4311            })
4312            .y(function (d) {
4313                return y(d.value)
4314            })
4315            .defined(function (d) {
4316                return d !== undefined && !Number.isNaN(d.value);
4317            })
4318        svg.selectAll("myLines")
4319            .data(dataReady)
4320            .enter()
4321            .append("path").classed("line", true)
4322            .attr("class", function (d) {
4323                return d.name.replace(/. /g, '');
4324            })
4325            .attr("d", function (d) {
4326                if (d !== undefined && !Number.isNaN(d.value))
4327                    return line(d.values)
4328            })
4329            .attr("stroke", function (d) {
4330                return myColor(d.name)
4331            })
4332            .style("stroke-width", 4)
4333            .style("fill", "none")
4334            .on("mouseover", function (d) {
4335                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4336            })
4337            .on("mouseout", function (d) {
4338                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.name.replace(/. /g, ''))
4339            })
4340        var u = svg.selectAll("circle")
4341            .data(dataReady)
4342        // Add the points
4343        svg
4344            // First we need to enter in a group
4345            .selectAll("myDots").merge(u)
4346            .data(dataReady)
4347            .enter()
4348            .append('g')
4349            .on("mouseout", function (d) {
4350                div
4351                    .style("opacity", 0);
4352            })
4353            .style("fill", function (d) {
4354                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4355                    return myColor(d.name)
4356            })
4357            // Second we need to enter in the 'values' part of this group
4358            .selectAll("myPoints")
4359            .data(function (d) {
4360                if (d !== undefined && d.value !== 0)
4361                    return d.values
4362            })
4363
4364            .enter()
4365            .append("circle")
4366            .attr("class", function (d) {
4367                if (d.athlete !== undefined)
4368                    return d.athlete.replace(/. /g, '')
4369            })
4370            .attr("cx", function (d) {
4371                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4372                    return x(d.time)
4373            })
4374            .attr("cy", function (d) {
4375                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4376                    return y(d.value)
4377            })
4378            .attr("r", function (d) {
4379                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4380                    return 5
4381            })
4382            .attr("stroke", "white")
4383            .style('stroke-opacity', '0.2')
4384            .on("mouseover", function (d) {
4385                d3.select(this).attr("r", 8);
4386                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4387
4388                div.style("opacity", .8);
4389                if (type !== "notimes")
4390                    div.html(d.athlete.substring(d.athlete.indexOf("§") + 1) + "<br/>  " + d.time + "<br/> Time: " + d.value.toString().toHHMMSS());
4391                else {
4392                    if (window.location.href.includes("scores.html") || window.location.href.includes("races.html"))
4393                        div.html(d.athlete.substring(d.athlete.indexOf("§") + 1) + "<br/>  " + d.time + "<br/> Value: " + d.value.toFixed(0));
4394                    else if (window.location.href.includes("trends.html"))
4395                        div.html(d.athlete.substring(d.athlete.indexOf("§") + 1) + "<br/>  " + d.time + "<br/> Value: " + d.value.toFixed(1));
4396                    else
4397                        div.html(d.athlete.substring(d.athlete.indexOf("§") + 1) + "<br/>  " + d.time + "<br/> Value: " + d.value.toString());
4398                }
4399
4400            })
4401            .on("mousemove", function (d) {
4402                div
4403                    .style("left", (d3.event.pageX + 15) + "px")
4404                    .style("top", (d3.event.pageY + 15) + "px")
4405            })
4406            .on("mouseout", function (d) {
4407                d3.select(this).attr("r", 5);
4408                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.athlete.replace(/. /g, ''))
4409                div
4410                    .style("opacity", 0);
4411            });
4412        // Add a label at the end of each line
4413        let usedspace = Array.from(Array(height).keys());
4414        let usedpixels = [];
4415        svg
4416            .selectAll("myLabels")
4417            .data(dataReady)
4418            .enter()
4419            .append('g')
4420            .append("text")
4421            .attr("class", function (d) {
4422                return d.name.replace(/. /g, '')
4423            })
4424            .datum(function (d) {
4425                if (typeof lastraceIndex !== "undefined")
4426                    return {name: d.name, value: d.values[lastraceIndex]};
4427                else
4428                    return {name: d.name, value: d.values[d.values.length - 1]};
4429            })
4430            // keep only the last value of each time series
4431            .attr("transform", function (d, i) {
4432                // console.log(i, dataReady.length)
4433
4434                if (d.value !== undefined && !Number.isNaN(x(d.value.time)) && !Number.isNaN(y(d.value.value)) && d.value.position <= slidervalue) {
4435
4436                    // if ( i === dataReady.length - 1 || window.location.href.includes("trends.html")) {
4437
4438                    if (!window.location.href.includes("scores") && (d.value.position < 4 || window.location.href.includes("trends.html"))) {
4439                        let yvalue = Math.round(y(d.value.value)) + 16;
4440                        if (d.value.position === 1 && !window.location.href.includes("trends.html")) yvalue = Math.round(y(d.value.value))
4441
4442                        let closestfree = usedspace.reduce(function (prev, curr) {
4443                            return (Math.abs(curr - yvalue) < Math.abs(prev - yvalue) ? curr : prev);
4444                        });
4445                        let collisonpixel;
4446                        if (usedpixels.length > 0)
4447                            collisonpixel = usedpixels[(usedpixels.indexOf(yvalue))]
4448                        if (closestfree === yvalue || collisonpixel === -1 || (collisonpixel !== -1 && closestfree - collisonpixel >= 0))
4449                            usedspace.splice(usedspace.indexOf(closestfree), 14)
4450                        if (closestfree === yvalue || collisonpixel === -1 || (collisonpixel !== -1 && closestfree - collisonpixel <= 0))
4451                            usedspace.splice(usedspace.indexOf(closestfree - 14), 14)
4452                        usedpixels.push(yvalue);
4453                        return "translate(" + x(d.value.time) + "," + (closestfree - 18) + ")";
4454
4455                    }
4456                    if (i === dataReady.length - 1 && d.value.value - dataReady[i - 1].values[dataReady[0].values.length - 1].value < 30) {
4457                        return "translate(" + x(d.value.time) + "," + (y(d.value.value) - 9) + ")";
4458                    }
4459                    if (i === dataReady.length - 3 && d.value.value - dataReady[i + 1].values[dataReady[0].values.length - 1].value < 30) {
4460                        return "translate(" + x(d.value.time) + "," + (y(d.value.value) + 3) + ")";
4461                    }
4462
4463                    return "translate(" + x(d.value.time) + "," + (y(d.value.value) - 3) + ")";
4464                }
4465
4466            }) // Put the text at the position of the last point
4467            .attr("y", 7)
4468            .attr("x", 12) // shift the text a bit more right
4469            .text(function (d) {
4470                if (d.value !== undefined && !Number.isNaN(x(d.value.time)) && !Number.isNaN(y(d.value.value)) && d.value.position <= slidervalue)
4471                    return d.name.substring(d.name.indexOf("§") + 1);
4472            })
4473            .style("fill", function (d) {
4474                return myColor(d.name)
4475            })
4476            .style("font-size", 14)
4477            .on("mouseover", function (d) {
4478                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4479            })
4480            .on("mouseout", function (d) {
4481                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.name.replace(/. /g, ''))
4482            })
4483        // Add a legend on top (interactive)
4484        // svg
4485        //     .selectAll("myLegend")
4486        //     .data(dataReady)
4487        //     .enter()
4488        //     .append('g')
4489        //     .append("text")
4490        //     .attr('x', function (d, i) {
4491        //         if ( d.values[0].position < 4)
4492        //             return (-100 + d.values[0].position * 160);
4493        //     })
4494        //     .attr('y', -16)
4495        //     .text(function (d) {
4496        //         if ( d.values[0].position < 4 && !hidetopnames)
4497        //             return d.name.substring(d.name.indexOf("§") + 1);
4498        //     })
4499        //     .style("fill", function (d) {
4500        //         return myColor(d.name)
4501        //     })
4502        //     .style("font-size", 15)
4503        // .on("click", function (d) {
4504        //     // is the element currently visible ?
4505        //     currentOpacity = d3.selectAll("." + d.name.replace(/. /g, '')).style("opacity");
4506        //     // Change the opacity: from 0 to 1 or from 1 to 0
4507        //     d3.selectAll("." + d.name.replace(/. /g, '')) .style("opacity", currentOpacity === 1 ? 0 : 1)
4508        //
4509        // })
4510        // .on('mouseover', function (d) {
4511        //     d3.select(this).style("cursor", "pointer");
4512        // })
4513    }
4514
4515    update(0);
4516}
4517
4518function chartPerSeason(size, data, publishedAt, timemax, min, max, width, height, xticks, yticks, type, reverse) {
4519    // Define the div for the tooltip
4520    var div = d3.select("body").append("div")
4521        .attr("class", 'tooltip')
4522        .style("opacity", 0);
4523
4524    function clearAll() {
4525        buttons.selectAll('rect')
4526            .each(function (d, i) {
4527                button.clear.call(this, d, i)
4528            });
4529    }
4530
4531    // set the dimensions and margins of the graph
4532    var margin = {top: 25, right: 210, bottom: 60, left: 60},
4533        width = width - margin.left - margin.right,
4534        height = height - margin.top - margin.bottom;
4535    // append the svg object to the body of the page
4536    var svg = d3.select("div#" + size + "chartdiv")
4537        .append("div")
4538        // Container class to make it responsive.
4539        .classed("svg-container", true)
4540        .append("svg")
4541        .attr("preserveAspectRatio", "xMinYMin meet")
4542        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
4543        .classed("svg-content-responsive", true)
4544        .append("g")
4545        .attr("transform",
4546            "translate(" + margin.left + "," + margin.top + ")");
4547    // List of groups (here I have one group per column)
4548    var allGroup = [];
4549    for (let i = 0; i < data.length; i++) {
4550        for (const [key, value] of Object.entries(data[i])) {
4551            if (!key.includes("gain/loss") && key !== "name" && !allGroup.includes(key))
4552                allGroup.push(key);
4553        }
4554    }
4555
4556    var myColor = d3.scaleOrdinal() // D3 Version 4
4557        .domain([allGroup[0], allGroup[1], allGroup[2], allGroup[3], allGroup[4], allGroup[5]])
4558        .range(["rgba(158,1,66,0.8)", "rgba(50,136,189,0.8)", "rgba(77, 175, 74,0.8)",
4559            "rgba(94,79,162,0.4)", "rgba(171,221,164,0.6)", "rgba(253,174,97,0.4)"])
4560        .unknown("rgba(163,154,154,0.2)");
4561
4562    if ((document.getElementById("darkSwitch").checked))
4563        myColor.range(["rgba(158,1,66,0.8)", "rgba(50,136,189,0.8)", "rgba(77, 175, 74,0.8)",
4564            "rgba(253,174,97,0.2)", "rgba(171,221,164,0.2)", "rgba(94,79,162,0.2)"])
4565
4566    //allGroup.reverse();
4567    // Reformat the data: we need an array of arrays of {x, y} tuples
4568    var dataReady = allGroup.map(function (grpName, i) { // .map allows to do something for each element of the list
4569        return {
4570            name: grpName,
4571            values: data.map(function (d) {
4572                return {time: d.name, value: +d[grpName], athlete: grpName, position: i + 1};
4573            })
4574        };
4575    });
4576    dataReady.reverse();
4577
4578    // console.log(dataReady);
4579
4580    function searchfunction(searchbar) {
4581        d3.select("#SearchBar" + searchbar)
4582            .on("keyup input", function () {
4583                d3.select("#chartdiv").html("");
4584
4585                if (cat === "progression") {
4586                    if (racedata.relayTeams.length > 0) {
4587                        let chartdata = raceProgressionData(racedata.relayTeams, 2);
4588                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
4589                    } else {
4590                        let chartdata = raceProgressionData(racedata.athletes, 1);
4591                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 900, 550, chartdata.ticks, 10)
4592                    }
4593                }
4594                if (cat === "loops") {
4595                    if (racedata.relayTeams.length > 0) {
4596
4597                    } else {
4598                        let chartdata = loopChartData(racedata.athletes, 1)
4599                        chart1(chartdata, chartdata.starttime, chartdata.endtime, chartdata.mintime, chartdata.maxtime, 1000, 650, chartdata.ticks, 10)
4600                    }
4601                }
4602
4603
4604            })
4605    }
4606
4607    searchfunction("FamilyName");
4608    searchfunction("Nation");
4609
4610
4611    function update(selected) {
4612        svg.selectAll("*").remove();
4613        // Add X axis --> it is a date format
4614        var x = d3.scalePoint().rangeRound([0, width])
4615            .range([0, width]);
4616        x.domain(dataReady[0].values.map(function (d) {
4617            return d.time;
4618        }));
4619        let divider = Math.ceil(dataReady[0].values.length / 20);
4620
4621        var xAxis = svg.append("g")
4622            .attr("transform", "translate(0," + height + ")")
4623            .attr("class", "axis")
4624            .call(d3.axisBottom(x)
4625                .tickValues(x.domain().filter(function (d, i) {
4626                    return !(i % divider)
4627                }))
4628                .tickFormat(function (d) {
4629                    if (d.toString().includes(".")) {
4630                        let thisyear = d.toString().substring(0, 4);
4631                        let thisseason = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
4632                        if (d.toString().endsWith(".01")) return thisseason + " - Tri. 1"
4633                        if (d.toString().endsWith(".25")) return thisseason + " - Tri. 2"
4634                        if (d.toString().endsWith(".75")) return thisseason + " - Tri. 3"
4635                        if (d.toString().endsWith(".5")) return thisseason + " - Champ."
4636                    } else {
4637                        return (d < 2000) ? d - 1 + "–" + (d.toString().slice(-2)) : d - 1 + "–" + (d.toString().slice(-2));
4638                    }
4639
4640                }))
4641            .selectAll("text")
4642            .style("text-anchor", "start")
4643            .attr("dx", function (d) {
4644                return 4;
4645            })
4646            .attr("dy", 8)
4647            .attr("transform", function (d) {
4648                return "rotate(28)";
4649            });
4650        // Add Y axis
4651        var y = d3.scaleLinear()
4652            .domain([max, min])
4653            .range([height, 0]);
4654        if (reverse === -1) y.domain([min, max])
4655
4656        var yAxis = svg.append("g")
4657            .attr("class", "axis")
4658            .call(d3.axisLeft(y).tickFormat(function (d) {
4659                if (type !== "notimes" || (typeof loopstable !== "undefined" && loopstable === "Shooting Efficiency"))
4660                    return d.toString().toHHMMSS();
4661                else
4662                    return d;
4663            }).ticks(yticks));
4664
4665        // gridlines in x axis function
4666        function make_x_gridlines() {
4667            return d3.axisBottom(x).ticks(xticks)
4668        }
4669
4670        // gridlines in y axis function
4671        function make_y_gridlines() {
4672            return d3.axisLeft(y).ticks(yticks)
4673        }
4674
4675        // add the X gridlines
4676        svg.append("g")
4677            .attr("class", "grid")
4678            .attr("transform", "translate(0," + height + ")")
4679            .call(make_x_gridlines()
4680                .tickSize(-height)
4681                .tickFormat("")
4682            );
4683        svg.append("g")
4684            .attr("class", "grid")
4685            .call(make_y_gridlines()
4686                .tickSize(-width)
4687                .tickFormat("")
4688            )
4689        var p = svg.selectAll("path")
4690            .data(dataReady)
4691        // Add the lines
4692        var line = d3.line()
4693            .x(function (d) {
4694                return x(d.time)
4695            })
4696            .y(function (d) {
4697                return y(d.value)
4698            })
4699            .defined(function (d) {
4700                return d !== undefined && d.value !== 0 && !Number.isNaN(d.value);
4701            })
4702        svg.selectAll("myLines")
4703            .data(dataReady)
4704            .enter()
4705            .append("path").classed("line", true)
4706            .attr("class", function (d) {
4707                return d.name.replace(/. /g, '');
4708            })
4709            .attr("d", function (d) {
4710                if (d !== undefined && !Number.isNaN(d.value))
4711                    return line(d.values)
4712            })
4713            .attr("stroke", function (d) {
4714                return myColor(d.name)
4715            })
4716            .style("stroke-width", 4)
4717            .style("fill", "none")
4718            .on("mouseover", function (d) {
4719                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4720            })
4721            .on("mouseout", function (d) {
4722                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.name.replace(/. /g, ''))
4723            })
4724
4725        svg.append("text")
4726            .attr("x", 16)
4727            .attr("y", 16)
4728            .attr("class", "svgdarktext")
4729            .text("realbiathlon.com")
4730            .style("font-size", "12px")
4731            .style("opacity", 0.4)
4732
4733        var u = svg.selectAll("circle")
4734            .data(dataReady)
4735        // Add the points
4736        svg
4737            // First we need to enter in a group
4738            .selectAll("myDots").merge(u)
4739            .data(dataReady)
4740            .enter()
4741            .append('g')
4742            .style("fill", function (d) {
4743                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4744                    return myColor(d.name)
4745            })
4746            // Second we need to enter in the 'values' part of this group
4747            .selectAll("myPoints")
4748            .data(function (d) {
4749                if (d !== undefined && d.value !== 0)
4750                    return d.values
4751            })
4752            .enter()
4753            .append("circle")
4754            .attr("class", function (d) {
4755                if (d.athlete !== undefined)
4756                    return d.athlete.replace(/. /g, '')
4757            })
4758            .attr("cx", function (d) {
4759                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4760                    return x(d.time)
4761            })
4762            .attr("cy", function (d) {
4763                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4764                    return y(d.value)
4765            })
4766            .attr("r", function (d) {
4767                if (d !== undefined && d.value !== 0 && !Number.isNaN(d.value))
4768                    return 5
4769            })
4770            .attr("stroke", "white")
4771            .style('stroke-opacity', '1')
4772            .on("mouseover", function (d) {
4773                d3.select(this).attr("r", 7);
4774                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4775
4776                div.style("opacity", .8);
4777                if (d.time.toString().includes(".")) {
4778                    let thisyear = d.time.toString().substring(0, 4);
4779                    let seasonstring = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
4780                    if (d.time.toString().endsWith(".01")) div.html(seasonstring + "<br/> Trimester 1 average:<br/>" + d.value.toFixed(1));
4781                    if (d.time.toString().endsWith(".25")) div.html(seasonstring + "<br/> Trimester 2 average:<br/>" + d.value.toFixed(1));
4782                    if (d.time.toString().endsWith(".5")) div.html(seasonstring + "<br/> Championship average:<br/>" + d.value.toFixed(1));
4783                    if (d.time.toString().endsWith(".75")) div.html(seasonstring + "<br/> Trimester 3 average:<br/>" + d.value.toFixed(1));
4784                } else {
4785                    let seasonstring = (d.time < 2000) ? d.time - 1 + "–" + (d.time.toString().slice(-2)) : d.time - 1 + "–" + (d.time.toString().slice(-2));
4786                    div.html(seasonstring + "<br/> Season average:<br/>" + d.value.toFixed(1));
4787                }
4788
4789
4790            })
4791            .on("mousemove", function (d) {
4792                div
4793                    .style("left", (d3.event.pageX + 15) + "px")
4794                    .style("top", (d3.event.pageY + 15) + "px")
4795            })
4796            .on("mouseout", function (d) {
4797                d3.select(this).attr("r", 5);
4798                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.athlete.replace(/. /g, ''))
4799                div
4800                    .style("opacity", 0);
4801            });
4802
4803        let usedspace = Array.from(Array(height).keys());
4804        let usedpixels = [];
4805        dataReady.reverse();
4806
4807
4808        svg
4809            .selectAll("myLabels")
4810            .data(dataReady)
4811            .enter()
4812            .append('g')
4813            .append("text")
4814            .attr("class", function (d) {
4815                return d.name.replace(/. /g, '')
4816            })
4817            .datum(function (d) {
4818                let index = 0;
4819                d.values.forEach(function (v, i) {
4820                    if (!isNaN(v.value)) index = i;
4821                })
4822                return {name: d.name, value: d.values[index]};
4823            })
4824            // keep only the last value of each time series
4825            .attr("transform", function (d) {
4826                if (d.value !== undefined && !Number.isNaN(x(d.value.time)) && !Number.isNaN(y(d.value.value)) && d.value.position <= slidervalue) {
4827                    let yvalue = Math.round(y(d.value.value)) + 15;
4828                    let closestfree = usedspace.reduce(function (prev, curr) {
4829                        return (Math.abs(curr - yvalue) < Math.abs(prev - yvalue) ? curr : prev);
4830                    });
4831                    let collisonpixel;
4832                    if (usedpixels.length > 0)
4833                        collisonpixel = usedpixels[(usedpixels.indexOf(yvalue))]
4834
4835                    if (closestfree === yvalue || collisonpixel === -1 || (collisonpixel !== -1 && closestfree - collisonpixel >= 0))
4836                        usedspace.splice(usedspace.indexOf(closestfree), 14)
4837                    if (closestfree === yvalue || collisonpixel === -1 || (collisonpixel !== -1 && closestfree - collisonpixel <= 0))
4838                        usedspace.splice(usedspace.indexOf(closestfree - 14), 14)
4839                    usedpixels.push(yvalue);
4840                    return "translate(" + x(d.value.time) + "," + (closestfree - 12) + ")";
4841                }
4842
4843            }) // Put the text at the position of the last point
4844            .attr("y", function (d) {
4845            })
4846            .attr("x", 12) // shift the text a bit more right
4847            .text(function (d) {
4848                let label = "";
4849                let itemUp = d.name.charAt(0).toUpperCase() + d.name.slice(1)
4850                if (d.name.match(/[A-Z][a-z]+|[0-9]+/g) === null)
4851                    label = itemUp.replace("Wcpoints", "World Cup Points");
4852                else
4853                    label = itemUp.match(/[A-Z][a-z]+|[0-9]+/g).join(" ").replace("Score", "zScore").replace("Average", "Avg.")
4854                        .replace("Median", "Med.").replace("Back", "back").replace("Percent", "%")
4855                if (d.value !== undefined && d.value.value !== null && !Number.isNaN(d.value.value) && d.value.value !== "" && d.value.value !== 0)
4856                    return label;
4857            })
4858            .style("fill", function (d) {
4859                return myColor(d.name)
4860            })
4861            .style("font-size", 15)
4862            .on("mouseover", function (d) {
4863                d3.selectAll("." + d3.select(this).attr("class")).attr("class", d3.select(this).attr("class") + " highlightedline").style("opacity", 1);
4864            })
4865            .on("mouseout", function (d) {
4866                d3.selectAll("." + d3.select(this).attr("class").replace(" highlightedline", "")).attr("class", d.name.replace(/. /g, ''))
4867            })
4868
4869    }
4870
4871    update(0);
4872}
4873
4874function shootingintervalchart(div1, data1, text, image, shtime, widthtotal, heigthtotal, maxtime) {
4875
4876    var div = d3.select("body").append("div")
4877        .attr("class", 'tooltip')
4878        .style("opacity", 0);
4879
4880    var svg = div1.append("div").attr("class", "shootingbar")
4881            .append("svg")
4882            .style("width", widthtotal + 'px')
4883            .style("height", (heigthtotal + 35) + 'px')
4884            .style("margin-top", "-29px")
4885            .style("overflow", "visible"),
4886        margin = {top: 0, right: 2, bottom: 20, left: 2},
4887        width = widthtotal - margin.left - margin.right,
4888        height = heigthtotal - margin.top - margin.bottom,
4889        g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
4890    var y = d3.scaleBand()            // x = d3.scaleBand()
4891        .rangeRound([0, height])    // .rangeRound([0, width])
4892        .paddingInner(0.05)
4893        .align(0.1);
4894    var x = d3.scaleLinear()        // y = d3.scaleLinear()
4895        .rangeRound([0, width]);	// .rangeRound([height, 0]);
4896    let keys = [0, 1, 2, 3, 4, 5, 6, 7, 100];
4897    var z = d3.scaleOrdinal()
4898        .domain([keys[0], keys[1], keys[2], keys[3], keys[4], keys[5], keys[6], keys[7], keys[8]])
4899        .range(["#4A576220", "#4A576230",
4900            "#4A576240", "#4A576250", "#4A576260",
4901            "#4A576262", "#4A576264", "#4A576268", "#4A576274"]);
4902
4903
4904    let previous = 0;
4905    data = [];
4906    if (data1 === undefined || data1 === null) return;
4907    data1.forEach(function (e, i) {
4908        let point = {
4909            key: i,
4910            0: previous,
4911            1: e + previous,
4912            end: false,
4913            hit: shotOrder ? image.includes((i + 1).toString()) : image.charAt(i) !== "0"
4914        }
4915        data.push(point);
4916        previous = point[1]
4917    })
4918    let point = {
4919        key: 100,
4920        0: previous,
4921        1: shtime,
4922        end: true,
4923        hit: false,
4924    }
4925    data.push(point);
4926
4927    x.domain([0, maxtime]).nice();	// y.domain...
4928    y.domain([0, 0]);
4929    z.domain(keys);
4930    var u = svg.selectAll("rect")
4931        .data(data)
4932
4933
4934    g.append("g")
4935        .selectAll("g")
4936        .merge(u)
4937        .data(data)
4938        .enter().append("rect")
4939        .attr("fill", function (d) {
4940            return z(d.key);
4941        })
4942        .attr("y", function (d) {
4943            return y(0) + 1 + 35;
4944        })
4945        .attr("x", function (d) {
4946            return x(d[0]);
4947        })
4948        .attr("width", function (d) {
4949            if (d[1] <= 80)
4950                return x(d[1]) - x(d[0]);
4951        })
4952        .attr("height", height)
4953        .on("mouseover", function (d) {
4954            d3.select(this).attr("opacity", 0.7);
4955            d3.select(this).style("cursor", "pointer");
4956
4957            div.style("opacity", .8);
4958            if (d.key === 100)
4959                div.html("Exit" + "<br/>Time:  <br/>" + (d[1] - d[0]).toFixed(1) + " sec")
4960            else
4961                div.html("Shot " + (d.key + 1) + "<br/>Time:  <br/>" + (d[1] - d[0]).toFixed(1) + " sec")
4962
4963        })
4964        .on("mousemove", function (d) {
4965            div
4966                .style("left", (d3.event.pageX + 15) + "px")
4967                .style("top", (d3.event.pageY + 15) + "px")
4968        })
4969        .on("mouseout", function (d) {
4970            d3.select(this).attr("opacity", 1);
4971            div
4972                .style("opacity", 0);
4973        })
4974
4975    g.append("g")
4976        .selectAll("g")
4977        .data(data)
4978        .enter().append("line")
4979        .attr("x1", function (d) {
4980            return x(d[1]);
4981        })
4982        .attr("class", "svgdark")
4983        .attr("y1", 1 + 35)
4984        .attr("x2", function (d) {
4985            return x(d[1]);
4986        })
4987        .attr("y2", 15 + 35)
4988        .attr("stroke-width", 2)
4989        .style("opacity", function (d) {
4990            if (d.end || (d[1] > 80)) return 0;
4991            else return 1;
4992        })
4993        .attr("stroke", function (d) {
4994            if (!shotOrder) return "black";
4995            else if (d.hit) return "#02a910";
4996            else return "#fa2543";
4997        })
4998        .attr("class", function (d) {
4999            if (!d.hit) return "missedshotline";
5000        })
5001
5002
5003    g.append("text")
5004        .attr("x", 2)
5005        .attr("y", 31 + 35)
5006        .attr("class", "svgdarktext2")
5007        .text(text.value)
5008        .style("font-size", "12px")
5009
5010
5011    g.append("text")
5012        .attr("x", 6)
5013        .attr("y", 30)
5014        .attr("class", "svgdarktext2 shootingimage")
5015        .text(image)
5016        .style("font-size", "10px")
5017        .style("letter-spacing", "22.5px")
5018
5019
5020}
5021
5022
5023function lineChart(size, ana, start, min, max, numberticks, decimals, type, width, height) {
5024    // Define the div for the tooltip
5025    var div = d3.select("body").append("div")
5026        .attr("class", 'tooltip')
5027        .style("opacity", 0);
5028    // set the dimensions and margins of the graph
5029    var margin = {top: 35, right: 150, bottom: 60, left: 30},
5030        width = width - margin.left - margin.right,
5031        height = height - margin.top - margin.bottom;
5032
5033    var svg = d3.select("div#" + size + "chartdiv")
5034        .append("div")
5035        // Container class to make it responsive.
5036        .classed("svg-container", true)
5037        .append("svg")
5038        .attr("preserveAspectRatio", "xMinYMin meet")
5039        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
5040        .classed("svg-content-responsive", true)
5041        .append("g")
5042        .attr("transform",
5043            "translate(" + margin.left + "," + margin.top + ")");
5044
5045
5046    var Group = ["Wins", "Podiums", "Top10s", "Top20s", "Top30s", "Top40s"];
5047    var neededGroups = [];
5048    var allGroup = [];
5049
5050    var seasons = 0;
5051    var scaleSeasons = []
5052    var data = [];
5053    var years = []
5054    ana.forEach(function (e, i) {
5055        years.push(e.Season)
5056    })
5057
5058
5059    if (type < 3) {
5060        absent(years).forEach(function (year) {
5061            var e = ana.filter(function (oneyear, i) {
5062                return oneyear.Season == year;
5063            })[0];
5064            var check = [];
5065            if (e !== undefined) {
5066                var item = {
5067                    Season: e.Season
5068                }
5069                item.Wins = isNaN(e.Wins / e.Races) ? 0 : (e.Wins / e.Races);
5070                if (e.Wins > 0)
5071                    neededGroups.push("Wins")
5072                item.Podiums = isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races);
5073                if (e.Podiums > 0)
5074                    neededGroups.push("Podiums")
5075                item.Top10s = isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races);
5076                if (e.Top10s > 0)
5077                    neededGroups.push("Top10s")
5078                item.Top20s = isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races);
5079                if (e.Top20s > 0)
5080                    neededGroups.push("Top20s")
5081                item.Top30s = isNaN(e.Top30s / e.Races) ? 0 : (e.Top30s / e.Races);
5082                if (e.Top30s > 0)
5083                    neededGroups.push("Top30s")
5084                item.Top40s = isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races);
5085                if (e.Top40s > 0)
5086                    neededGroups.push("Top40s")
5087                data.push(item);
5088                seasons++;
5089                scaleSeasons.push(e.Season)
5090            } else {
5091                data.push(null)
5092            }
5093        })
5094    } else {
5095        data = newdata;
5096    }
5097
5098    Group.forEach(function (g) {
5099        if (neededGroups.indexOf(g) !== -1)
5100            allGroup.push(g)
5101    })
5102
5103    function absent(arr) {
5104        var numbers = [];
5105        var count = 0;
5106        for (var i = arr[0]; i < arr[arr.length - 1] + 1; i++) {
5107            numbers.push(arr[0] + count);
5108            if (typeof showTrimester !== "undefined" && showTrimester !== null && showTrimester === "true" && arr[0].toString().endsWith(".01")) {
5109                numbers.push(arr[0] + 0.24 + count);
5110                numbers.push(arr[0] + 0.49 + count);
5111                numbers.push(arr[0] + 0.74 + count);
5112            } else if (typeof showTrimester !== "undefined" && showTrimester !== null && showTrimester === "true" && arr[0].toString().endsWith(".25")) {
5113                numbers.push(arr[0] + 0.25 + count);
5114                numbers.push(arr[0] + 0.5 + count);
5115                numbers.push(arr[0] + 0.76 + count);
5116            } else if (typeof showTrimester !== "undefined" && showTrimester !== null && showTrimester === "true" && arr[0].toString().endsWith(".5")) {
5117                numbers.push(arr[0] + 0.25 + count);
5118                numbers.push(arr[0] + 0.51 + count);
5119                numbers.push(arr[0] + 0.75 + count);
5120            } else if (typeof showTrimester !== "undefined" && showTrimester !== null && showTrimester === "true" && arr[0].toString().endsWith(".75")) {
5121                numbers.push(arr[0] + 0.26 + count);
5122                numbers.push(arr[0] + 0.5 + count);
5123                numbers.push(arr[0] + 0.75 + count);
5124            }
5125            count++;
5126        }
5127        // console.log(numbers)
5128        return numbers;
5129    }
5130
5131    // Reformat the data: we need an array of arrays of {x, y} tuples
5132    var dataReady = allGroup.map(function (grpName) { // .map allows to do something for each element of the list
5133        return {
5134            name: grpName,
5135            values: data.map(function (d) {
5136                if (d !== null)
5137                    return {time: d.Season, value: +d[grpName]};
5138            })
5139        };
5140    });
5141    // console.log(dataReady)
5142
5143    var xticks = years.length - 1 === 0 ? years.length : years.length + 1
5144
5145
5146    // A color scale: one color for each group
5147    var myColor = d3.scaleOrdinal()
5148        .domain(allGroup)
5149        .range(["#9e0142", "#5e4fa2", "#3288bd",
5150            "#66c2a5", "#abdda4",
5151            "#fdae61"])
5152
5153    // Add X axis --> it is a date format
5154    var x = d3.scaleLinear().nice()
5155        .domain([years[0], years[years.length - 1]])
5156        .range([0, width])
5157
5158    let divider = Math.ceil(years.length / 30);
5159
5160
5161    var xAxis = svg.append("g")
5162        .attr("transform", "translate(0," + height + ")")
5163        .attr("class", "axis")
5164        .call(d3.axisBottom(x)
5165            .tickValues(years.filter(function (d, i) {
5166                return !(i % divider)
5167            }))
5168            .tickFormat(d3.format("d"))
5169        ).selectAll("text")
5170        .style("text-anchor", "end")
5171
5172        .attr("transform", "rotate(35)")
5173        .text(function (d) {
5174            if (d.toString().includes(".")) {
5175                let thisyear = d.toString().substring(0, 4);
5176                let thisseason = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
5177                if (d.toString().endsWith(".01")) return thisseason + " - Tri. 1"
5178                if (d.toString().endsWith(".25")) return thisseason + " - Tri. 2"
5179                if (d.toString().endsWith(".75")) return thisseason + " - Tri. 3"
5180                if (d.toString().endsWith(".5")) return thisseason + " - Champ."
5181            } else {
5182                return (d < 2000) ? d - 1 + "–" + (d.toString().slice(-2)) : d - 1 + "–" + (d.toString().slice(-2));
5183            }
5184        })
5185        .style("text-anchor", "start")
5186
5187    // Add Y axis
5188    var y = d3.scaleLinear()
5189        .domain([min, max])
5190        .range([height, start]);
5191    svg.append("g")
5192        .attr("class", "axis")
5193        .call(d3.axisLeft(y).ticks(numberticks));
5194
5195    // gridlines in x axis function
5196    function make_x_gridlines() {
5197        return d3.axisBottom(x).ticks(xticks)
5198    }
5199
5200    // gridlines in y axis function
5201    function make_y_gridlines() {
5202        return d3.axisLeft(y).ticks(numberticks)
5203    }
5204
5205    // add the X gridlines
5206    svg.append("g")
5207        .attr("class", "grid")
5208        .attr("transform", "translate(0," + height + ")")
5209        .call(make_x_gridlines()
5210            .tickSize(-height)
5211            .tickFormat("")
5212        )
5213    svg.append("g")
5214        .attr("class", "grid")
5215        .call(make_y_gridlines()
5216            .tickSize(-width)
5217            .tickFormat("")
5218        )
5219
5220    var previousyear = 0;
5221    // Add the lines
5222    var line = d3.line()
5223        .x(function (d) {
5224            return x(+d.time)
5225        })
5226        .y(function (d) {
5227            return y(+d.value)
5228        })
5229        .defined(function (d) {
5230            return d != null;
5231        })
5232    svg.selectAll("myLines")
5233        .data(dataReady.reverse())
5234        .enter()
5235        .append("path")
5236        .attr("class", function (d) {
5237
5238            return d.name + type;
5239        })
5240        .attr("d", function (d) {
5241
5242            return line(d.values)
5243        })
5244        .attr("stroke", function (d) {
5245            return myColor(d.name)
5246        })
5247        .style("stroke-width", 4)
5248        .style("fill", "none")
5249
5250    svg.append("text")
5251        .attr("x", 20)
5252        .attr("y", 16)
5253        .attr("class", "svgdarktext")
5254        .text("realbiathlon.com")
5255        .style("font-size", "12px")
5256        .style("opacity", 0.4)
5257
5258    // Add the points
5259    svg
5260        // First we need to enter in a group
5261        .selectAll("myDots")
5262        .data(dataReady)
5263        .enter()
5264        .append('g')
5265        .style("fill", function (d) {
5266            if (d !== undefined && d !== null)
5267                return myColor(d.name)
5268        })
5269        .attr("class", function (d) {
5270            if (d !== undefined && d !== null)
5271                return d.name + type;
5272        })
5273
5274        // Second we need to enter in the 'values' part of this group
5275        .selectAll("myPoints")
5276        .data(function (d) {
5277            if (d !== undefined && d !== null)
5278                return d.values
5279        })
5280        .enter()
5281        .append("circle")
5282        .attr("class", "circlePerSeason")
5283        .attr("cx", function (d) {
5284            if (d !== undefined && d !== null)
5285                return x(d.time)
5286        })
5287        .attr("cy", function (d) {
5288            if (d !== undefined && d !== null)
5289                return y(d.value)
5290        })
5291        .attr("r", function (d) {
5292            if (d !== undefined && d !== null)
5293                return 5;
5294        })
5295        .attr("stroke", "white")
5296
5297
5298    d3.selectAll(".circlePerSeason")
5299        .on("mouseover", function (d) {
5300            d3.select(this).attr("r", 7);
5301            d3.select(this).style("cursor", "pointer");
5302
5303            var time = '';
5304            div.style("opacity", .8);
5305            var subgroupName = d3.select(this.parentNode).datum().name;
5306
5307            if (d.time.toString().includes(".")) {
5308                let thisyear = d.time.toString().substring(0, 4);
5309                let seasonstring = (thisyear < 2000) ? thisyear - 1 + "–" + (thisyear.toString().slice(-2)) : thisyear - 1 + "–" + (thisyear.toString().slice(-2))
5310                if (d.time.toString().endsWith(".01")) div.html(seasonstring + " - Tri. 1 <br/>" + subgroupName + ":<br/>" + d.value.toFixed(decimals));
5311                if (d.time.toString().endsWith(".25")) div.html(seasonstring + " - Tri. 2 <br/>" + subgroupName + ":<br/>" + d.value.toFixed(decimals));
5312                if (d.time.toString().endsWith(".5")) div.html(seasonstring + " - Champ. <br/>" + subgroupName + ":<br/>" + d.value.toFixed(decimals));
5313                if (d.time.toString().endsWith(".75")) div.html(seasonstring + " - Tri. 3<br/>" + subgroupName + ":<br/>" + d.value.toFixed(decimals));
5314            } else {
5315                if (d.time < 2000)
5316                    time = d.time - 1 + "–" + (d.time.toString().slice(-2));
5317                else
5318                    time = d.time - 1 + "–" + (d.time.toString().slice(-2));
5319                div.html(time + "<br/>" + subgroupName + ":<br/>" + d.value.toFixed(decimals))
5320            }
5321
5322        })
5323        .on("mousemove", function (d) {
5324            div
5325                .style("left", (d3.event.pageX + 15) + "px")
5326                .style("top", (d3.event.pageY + 15) + "px")
5327        })
5328        .on("mouseout", function (d) {
5329            d3.select(this).attr("r", 5);
5330            div
5331                .style("opacity", 0);
5332        })
5333
5334    // Add a legend (interactive)
5335    svg
5336        .selectAll("myLegend")
5337        .data(dataReady)
5338        .enter()
5339        .append('g')
5340        .append("text")
5341        .attr('x', function (d, i) {
5342            if (d.name === 'Podiums')
5343                return 30 + i * 75 - 11;
5344            else
5345                return 30 + i * 75;
5346        })
5347        .attr('y', -20)
5348        .text(function (d) {
5349            return d.name;
5350        })
5351        .style("fill", function (d) {
5352            return myColor(d.name)
5353        })
5354        .style("font-size", 15)
5355        .on("click", function (d) {
5356            // is the element currently visible ?
5357            currentOpacity = d3.selectAll("." + d.name + type).style("opacity")
5358            // Change the opacity: from 0 to 1 or from 1 to 0
5359            d3.selectAll("." + d.name + type).transition().style("opacity", currentOpacity == 1 ? 0 : 1)
5360
5361        })
5362        .on('mouseover', function (d) {
5363            d3.select(this).style("cursor", "pointer");
5364        })
5365
5366    let previousposition = -200;
5367    let previousthis;
5368
5369    // Add a label at the end of each line
5370    svg
5371        .selectAll("myLabels")
5372        .data(dataReady)
5373        .enter()
5374        .append('g')
5375        .append("text")
5376        .attr("class", function (d) {
5377            return d.name + type;
5378        })
5379        .datum(function (d) {
5380            let i = 1
5381            while (d.values[d.values.length - i] === undefined) {
5382                i++;
5383            }
5384            return {name: d.name, value: d.values[d.values.length - i]};
5385        }) // keep only the last value of each time series
5386        .attr("transform", function (d) {
5387            return "translate(" + x(d.value.time) + "," + y(d.value.value) + ")";
5388        }) // Put the text at the position of the last point
5389        .attr("x", 12) // shift the text a bit more right
5390        .text(function (d) {
5391            return d.name;
5392        })
5393        .style("fill", function (d) {
5394            if (previousposition === y(d.value.value)) {
5395                previousthis.style("opacity", 0)
5396                previousthis = d3.select(this);
5397            } else {
5398                previousposition = y(d.value.value);
5399                previousthis = d3.select(this);
5400            }
5401
5402
5403            return myColor(d.name)
5404        })
5405        .style("font-size", 15)
5406}
5407
5408function barChart(size, ana, type, podiums, width, height, selected) {
5409
5410    // Define the div for the tooltip
5411    var div = d3.select("body").append("div")
5412        .attr("class", "tooltip")
5413        .style("opacity", 0);
5414
5415    let leftmargin = 25;
5416    if ((typeof rangemin !== "undefined" && rangemin > 1957)) leftmargin = 35;
5417    // set the dimensions and margins of the graph
5418    var margin = {top: 10, right: 75, bottom: 50, left: leftmargin},
5419        width = width - margin.left - margin.right,
5420        height = height - margin.top - margin.bottom;
5421
5422    var svg = d3.select("div#" + size + "chartdiv")
5423        .append("div")
5424        // Container class to make it responsive.
5425        .classed("svg-container", true)
5426        .append("svg")
5427        .attr("preserveAspectRatio", "xMinYMin meet")
5428        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
5429        .classed("svg-content-responsive", true)
5430        .append("g")
5431        .attr("transform",
5432            "translate(" + margin.left + "," + margin.top + ")");
5433
5434
5435    // List of subgroups = header of the csv files = soil condition here
5436    var subgroups = ["Wins", "2nd places", "3rd places"];
5437
5438
5439    // Add X axis
5440    var x = d3.scaleBand()
5441        .domain(subgroups)
5442        .range([0, width])
5443        .padding([0.2])
5444    var xAxis = svg.append("g").attr("class", "x axis")
5445        .attr("transform", "translate(0," + height + ")")
5446        .call(d3.axisBottom(x).tickSizeOuter(0));
5447
5448    // Add Y axis
5449    var y = d3.scaleLinear()
5450        .domain([0, 1])
5451        .range([height, 0]);
5452    var yAxis = svg.append("g").attr("class", "y axis")
5453        .call(d3.axisLeft(y));
5454
5455    // color palette = one color per subgroup
5456    var color = d3.scaleOrdinal()
5457        .domain(subgroups)
5458        .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
5459            "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
5460
5461    function update(selected) {
5462        var data = [];
5463        var headerlist = []
5464        var max = 0;
5465        if (selected === 2 || selected === 4) {
5466            ana.forEach(function (e, i) {
5467                var item = {
5468                    group: e.Discipline,
5469                    Wins: e.Wins,
5470                    '2nd places': e['2nd'],
5471                    '3rd places': e['3rd'],
5472                    Podiums: e.Podiums,
5473                    '4th - 10th': e.Top10s - e.Podiums
5474                }
5475                data.push(item)
5476                headerlist.push(item.Discipline)
5477                if ((typeof rangemin !== "undefined" && rangemin > 1957) && e.Discipline === "Non-Team" && selected === 2)
5478                    max = e.Wins + e['2nd'] + e['3rd'];
5479                else if (e.Discipline === "Total" && selected === 2)
5480                    max = e.Wins + e['2nd'] + e['3rd'];
5481                else
5482                    max = e.Top10s;
5483            })
5484        }
5485        if (selected === 1 || selected === 3) {
5486            ana.forEach(function (e, i) {
5487                var item = {
5488                    group: e.Discipline,
5489                    Wins: isNaN(e.Wins / e.Races) ? 0 : (e.Wins / e.Races * 100),
5490                    '2nd places': isNaN(e['2nd'] / e.Races) ? 0 : (e['2nd'] / e.Races * 100),
5491                    '3rd places': isNaN(e['3rd'] / e.Races) ? 0 : (e['3rd'] / e.Races * 100),
5492                    Podiums: isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100),
5493                    '4th - 10th': (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)) - (isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100))
5494                }
5495                data.push(item)
5496                headerlist.push(item.Discipline)
5497                if (item.Wins + item['2nd places'] + item['3rd places'] > max && selected === 1)
5498                    max = item.Wins + item['2nd places'] + item['3rd places'];
5499                if (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100) > max && selected === 3)
5500                    max = isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100);
5501            })
5502        }
5503        if (selected === 5) {
5504
5505            ana.forEach(function (e, i) {
5506                var item = {
5507                    group: e.Discipline,
5508                    Podiums: isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100),
5509                    '4th - 10th ': (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)) - (isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100)),
5510                    '11th - 20th': (isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races * 100)) - (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)),
5511                    '21th - 30th': (isNaN(e.Top30s / e.Races) ? 0 : (e.Top30s / e.Races * 100)) - (isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races * 100)),
5512                    '31th - 40th': (isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100)) - (isNaN(e.Top30s / e.Races) ? 0 : (e.Top30s / e.Races * 100))
5513                }
5514                data.push(item)
5515                headerlist.push(item.Discipline)
5516                if (isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100) > max)
5517                    max = isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100);
5518            })
5519
5520        }
5521        if (selected === 6) {
5522            ana.forEach(function (e, i) {
5523                var item = {
5524                    group: e.Discipline,
5525                    Podiums: e.Podiums,
5526                    '4th - 10th ': e.Top10s - e.Podiums,
5527                    '11th - 20th': e.Top20s - e.Top10s,
5528                    '21th - 30th': e.Top30s - e.Top20s,
5529                    '31th - 40th': e.Top40s - e.Top30s
5530                }
5531                data.push(item)
5532                headerlist.push(item.Discipline)
5533                max = e.Top40s;
5534
5535            })
5536        }
5537        if (selected === 7) {
5538            ana.forEach(function (e, i) {
5539                var item = {
5540                    group: e.Discipline
5541                }
5542                item[chosenstat] = e[chosenstat]
5543                data.push(item)
5544                headerlist.push(item.Discipline)
5545                max = e.Top40s;
5546
5547            })
5548        }
5549
5550        if (selected === 5 || selected === 6)
5551            subgroups = ["Podiums", "4th - 10th ", "11th - 20th", "21th - 30th", "31th - 40th"];
5552        if (selected === 3 || selected === 4)
5553            subgroups = ["Wins", "2nd places", "3rd places", "4th - 10th"];
5554        if (selected === 1 || selected === 2)
5555            subgroups = ["Wins", "2nd places", "3rd places"];
5556
5557        //console.log(data)
5558
5559        d3.selectAll(".barchartbars" + type).remove();
5560        d3.selectAll(".endlabels" + type).remove();
5561        d3.selectAll(".grid2" + type).remove();
5562
5563        // List of groups = species here = value of the first column called group -> I show them on the X axis
5564        var groups = d3.map(data, function (d) {
5565            return (d.group)
5566        }).keys()
5567
5568        var myColor = d3.scaleOrdinal()
5569            .domain(subgroups)
5570            .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
5571                "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
5572
5573
5574        //stack the data? --> stack per subgroup
5575        var stackedData = d3.stack()
5576            .keys(subgroups)
5577            (data)
5578
5579        // X axis
5580        x.domain(groups)
5581        xAxis.call(d3.axisBottom(x))
5582
5583        // Add Y axis
5584        y.domain([0, max]);
5585        yAxis.call(d3.axisLeft(y));
5586
5587        var u = svg.selectAll("rect")
5588            .data(data)
5589
5590        // gridlines in x axis function
5591        function make_x_gridlines() {
5592            return d3.axisBottom(x).ticks(16)
5593        }
5594
5595        // gridlines in y axis function
5596        function make_y_gridlines() {
5597            return d3.axisLeft(y).ticks(10)
5598        }
5599
5600        svg.append("text")
5601            .attr("x", 16)
5602            .attr("y", 16)
5603            .attr("class", "svgdarktext")
5604            .text("realbiathlon.com")
5605            .style("font-size", "12px")
5606            .style("opacity", 0.4)
5607
5608        // add the X gridlines
5609        svg.append("g")
5610            .attr("class", "grid")
5611            .attr("transform", "translate(0," + height + ")")
5612            .call(make_x_gridlines()
5613                .tickSize(-height)
5614                .tickFormat("")
5615            )
5616        var g = svg.append("g")
5617            .attr("class", "grid")
5618
5619            .call(make_y_gridlines()
5620                .tickSize(-width)
5621                .tickFormat("")
5622            )
5623
5624        var barpos = []
5625        var endpos = []
5626        // Show the bars
5627        var barchart = svg.append("g")
5628            .selectAll("g")
5629            // Enter in the stack data = loop key per key = group per group
5630            .merge(u)
5631            .data(stackedData)
5632            .enter().append("g")
5633            .attr("fill", function (d) {
5634                return myColor(d.key);
5635            })
5636            .selectAll("rect")
5637            // enter a second time = loop subgroup per subgroup to add all rectangles
5638            .data(function (d) {
5639                return d;
5640            })
5641            .enter().append("rect")
5642            .attr("class", "barchartbars" + type)
5643            .attr("x", function (d) {
5644                barpos.push(x(d.data.group))
5645                return x(d.data.group);
5646            })
5647            .attr("y", function (d) {
5648                if ((typeof rangemin !== "undefined" && rangemin > 1957) && d.data.group === "Non-Team")
5649                    endpos.push([y(d[1])]);
5650                if (d.data.group === "Total")
5651                    endpos.push([y(d[1])]);
5652                return y(d[1]);
5653            })
5654            .attr("height", function (d) {
5655                return y(d[0]) - y(d[1]);
5656            })
5657            .attr("width", x.bandwidth())
5658            .style("opacity", function (d) {
5659                if (d.data.group === "Total" || d.data.group === "Total Team" || d.data.group === "Total Non-Team")
5660                    return 1;
5661                else
5662                    return 1;
5663            })
5664            .style("stroke", function (d) {
5665                if (d.data.group === "Total" || d.data.group === "Total Team" || d.data.group === "Total Non-Team")
5666                    return 'grey';
5667                else
5668                    return 'grey';
5669            })
5670            .attr('stroke-width', 1)
5671
5672
5673        d3.selectAll("rect")
5674            .on("mouseover", function (d) {
5675                d3.select(event.currentTarget).style("cursor", "pointer");
5676                d3.select(event.currentTarget).style("opacity", 0.75);
5677                if (d.label === undefined) {
5678                    var subgroupName = d3.select(this.parentNode).datum().key;
5679                    var subgroupValue = d.data[subgroupName];
5680                    div
5681                        .style("opacity", .8);
5682                    if (selected % 2 === 1)
5683                        div.html(d.data.group.replace("Thingnes", "T.") + "<br/>" + subgroupName + ":<br/>" + subgroupValue.toFixed(1) + "%")
5684                    if (selected % 2 === 0)
5685                        div.html(d.data.group.replace("Thingnes", "T.") + "<br/>" + subgroupName + ":<br/>" + subgroupValue)
5686                            .style("left", (d3.event.pageX) + "px")
5687                            .style("top", (d3.event.pageY - 28) + "px");
5688                }
5689            })
5690            .on("mousemove", function (d) {
5691                div
5692                    .style("left", (d3.event.pageX + 15) + "px")
5693                    .style("top", (d3.event.pageY + 15) + "px")
5694            })
5695            .on("mouseout", function (d) {
5696                d3.select(event.currentTarget).style("opacity", 1);
5697                div
5698                    .style("opacity", 0);
5699            })
5700
5701        var l = svg.selectAll("myLabels")
5702            .data(subgroups)
5703
5704        l
5705            .enter()
5706            .append('g')
5707
5708            .append("text")
5709            .merge(l)
5710
5711            .attr("class", 'endlabels' + type)
5712            .datum(function (d) {
5713                if (d === "Wins" && (endpos[0][0] !== height))
5714                    return {name: d, value: (endpos[0][0] + height) / 2};
5715                if (d === "2nd places" && (endpos[0][0] !== endpos[1][0]))
5716                    return {name: d, value: (endpos[0][0] + endpos[1][0]) / 2};
5717                if (d === "3rd places" && (endpos[2][0] !== endpos[1][0]))
5718                    return {name: d, value: (endpos[2][0] + endpos[1][0]) / 2};
5719                if (d === "4th - 10th" && (endpos[2][0] !== endpos[3][0]))
5720                    return {name: d, value: (endpos[2][0] + endpos[3][0]) / 2};
5721
5722                if (d === "Podiums" && (endpos[0][0] !== height))
5723                    return {name: d, value: (endpos[0][0] + height) / 2};
5724                if (d === "4th - 10th " && (endpos[0][0] !== endpos[1][0]))
5725                    return {name: d, value: (endpos[0][0] + endpos[1][0]) / 2};
5726                if (d === "11th - 20th" && (endpos[2][0] !== endpos[1][0]))
5727                    return {name: d, value: (endpos[2][0] + endpos[1][0]) / 2};
5728                if (d === "21th - 30th" && (endpos[2][0] !== endpos[3][0]))
5729                    return {name: d, value: (endpos[2][0] + endpos[3][0]) / 2};
5730                if (d === "31th - 40th" && (endpos[3][0] !== endpos[4][0]))
5731                    return {name: d, value: (endpos[3][0] + endpos[4][0]) / 2};
5732            }) // keep only the last value of each time series
5733
5734            .attr("transform", function (d) {
5735                if (d !== undefined) {
5736                    return "translate(" + (width - 25) + "," + d.value + ")";
5737                }
5738
5739            }) // Put the text at the position of the last point
5740            .attr("x", 14) // shift the text a bit more right
5741            .text(function (d) {
5742                if (d !== undefined) {
5743                    return d.name;
5744                }
5745            })
5746            .style("fill", function (d) {
5747                if (d !== undefined) {
5748                    return myColor(d.name)
5749                }
5750            })
5751            .style("font-size", 15)
5752    }
5753
5754
5755    update(selected);
5756
5757
5758}
5759
5760// Used to ignore stale simulation responses when multiple renders overlap
5761let simulationRenderToken = 0;
5762
5763function simulation(size, raceData, width, height, binInput, bins, comparisonRaceData, comparisonName){
5764    // Every simulation call gets a token; only the latest response may render
5765    const renderToken = ++simulationRenderToken;
5766
5767    // Extract primary athlete values
5768    let values = []
5769    raceData.forEach(function (s,i) {
5770        // Try the field name as-is first, then try with lowercased first character
5771        let fieldValue = s[loopstable];
5772        if (fieldValue === undefined || fieldValue === null || fieldValue === "") {
5773            // Try lowercased version
5774            let itemDown = loopstable.charAt(0).toLowerCase() + loopstable.slice(1);
5775            fieldValue = s[itemDown];
5776        }
5777        if (fieldValue !== undefined && fieldValue !== null && fieldValue !== "") {
5778            values.push(fieldValue);
5779        }
5780    })
5781    console.log(values)
5782
5783    // Check if we have any values to simulate
5784    if (values.length === 0) {
5785        console.error("No values found for simulation - field:", loopstable)
5786        return;
5787    }
5788    
5789    // Extract comparison athlete values if present
5790    let comparisonValues = null;
5791    if (comparisonRaceData && comparisonRaceData.length > 0) {
5792        comparisonValues = [];
5793        comparisonRaceData.forEach(function (s, i) {
5794            let fieldValue = s[loopstable];
5795            if (fieldValue === undefined || fieldValue === null || fieldValue === "") {
5796                let itemDown = loopstable.charAt(0).toLowerCase() + loopstable.slice(1);
5797                fieldValue = s[itemDown];
5798            }
5799            if (fieldValue !== undefined && fieldValue !== null && fieldValue !== "") {
5800                comparisonValues.push(fieldValue);
5801            }
5802        });
5803        
5804        // If no values found for comparison, set to null
5805        if (comparisonValues.length === 0) {
5806            console.warn("Comparison athlete has no data for", loopstable);
5807            comparisonValues = null;
5808        }
5809    }
5810
5811    // Prepare API request(s)
5812    let apiCalls = [];
5813    
5814    // Primary athlete (always)
5815    let requestData1 = {
5816        values: values,
5817        data_point: histogramString,
5818        event: eventString,
5819        level: levelString,
5820        num: binInput,
5821        discipline: disciplineString,
5822        range: rangeDropDownString
5823    };
5824
5825    console.log("Primary request:", requestData1);
5826
5827    apiCalls.push(
5828        $.post("mdbConnection.php", {
5829            operation: "postRequest",
5830            domain: 'http://144.24.168.40:29979/simulation/',
5831            data: requestData1,
5832            datatype: "json"
5833        })
5834    );
5835
5836    // Comparison athlete (only if data exists)
5837    let hasComparison = false;
5838    if (comparisonValues !== null && comparisonValues.length > 0) {
5839        hasComparison = true;
5840        let requestData2 = {
5841            values: comparisonValues,
5842            data_point: histogramString,
5843            event: eventString,
5844            level: levelString,
5845            num: binInput,
5846            discipline: disciplineString,
5847            range: rangeDropDownString
5848        };
5849        
5850        console.log("Comparison request:", requestData2);
5851        
5852        apiCalls.push(
5853            $.post("mdbConnection.php", {
5854                operation: "postRequest",
5855                domain: 'http://144.24.168.40:29979/simulation/',
5856                data: requestData2,
5857                datatype: "json"
5858            })
5859        );
5860    }
5861
5862    // Execute API call(s) and render chart
5863    Promise.all(apiCalls)
5864        .then(function(responses) {
5865            // Drop stale responses that completed after a newer render started
5866            if (renderToken !== simulationRenderToken) {
5867                console.warn("Ignoring stale simulation response", renderToken, "current", simulationRenderToken);
5868                return;
5869            }
5870
5871            // Parse primary result
5872            let allSimData = JSON.parse(responses[0])
5873            const currentMean = allSimData.mean
5874            const currentMode = allSimData.mode !== undefined ? allSimData.mode : currentMean
5875            // console.log(allSimData)
5876            let data = allSimData.array
5877            let pdfData = allSimData.pdf_values  // Dense PDF curve for smooth visualization
5878            
5879            // Parse comparison result if present
5880            let comparisonSimData = null;
5881            if (hasComparison && responses.length > 1) {
5882                comparisonSimData = JSON.parse(responses[1]);
5883                console.log("Comparison simulation data:", comparisonSimData);
5884            }
5885
5886            // set the dimensions and margins of the graph
5887            var margin = {top: 25, right: 30, bottom: 40, left: 40},
5888                chartWidth = width - margin.left - margin.right,
5889                chartHeight = height - margin.top - margin.bottom;
5890
5891            var svg = d3.select("div#" + size + "chartdiv")
5892                .append("div")
5893                // Container class to make it responsive.
5894                .classed("svg-container", true)
5895                .append("svg")
5896                .attr("preserveAspectRatio", "xMinYMin meet")
5897                .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
5898                .classed("svg-content-responsive", true)
5899                .append("g")
5900                .attr("transform",
5901                    "translate(" + margin.left + "," + margin.top + ")");
5902
5903            svg.append("text")
5904                .attr("class", "chart-title")
5905                .attr("x", (chartWidth + margin.left + margin.right) / 2) // Center horizontally
5906                .attr("y", 2) // Position above the chart
5907                .attr("text-anchor", "middle") // Center alignment
5908                .text("10,000 Simulations | Mean: " + currentMean.toFixed(2) + " | Mode: " + currentMode.toFixed(2) + " | Sigma: " + allSimData.sigma.toFixed(2))
5909                .style("font-size", "14px") // Adjust font size
5910                .style("fill", "grey"); // Set color to grey
5911
5912            svg.append("text")
5913                .attr("x", margin.left)
5914                .attr("y", margin.top - 5)
5915                .attr("class", "svgdarktext")
5916                .text("realbiathlon.com")
5917                .style("font-size", "12px")
5918                .style("opacity", 0.4)
5919                .attr("text-anchor", "start")
5920
5921            // Create a group for the chart
5922            const chart = svg.append('g')
5923                .attr('transform', `translate(${margin.left}, ${margin.top})`);
5924
5925            // Consolidated legend below chart (will be populated after all elements are created)
5926
5927            // Create a linear scale for the x-axis
5928            const xScale = d3.scaleBand()
5929                .domain(data.map(d => d.label))
5930                .range([0, chartWidth])
5931                .padding(0.1);
5932
5933            // Use the same horizontal span as the bar centers for the mean line
5934            const firstLabel = data[0].label;
5935            const lastLabel = data[data.length - 1].label;
5936            const xLinearScale = d3.scaleLinear()
5937                .domain([+firstLabel, +lastLabel])
5938                .range([
5939                    xScale(firstLabel) + xScale.bandwidth() / 2,
5940                    xScale(lastLabel) + xScale.bandwidth() / 2
5941                ]);
5942
5943            // Create a linear scale for the y-axis (left side, bar chart)
5944            const yScale = d3.scaleLinear()
5945                .domain([0, d3.max(data, d => d.value)])
5946                .nice()
5947                .range([chartHeight, 0]);
5948
5949
5950            // Create a linear scale for the y-axis (right side, line chart)
5951            // Calculate max PDF value across all datasets to prevent clipping
5952            let maxPDFValue;
5953            if (comparisonSimData !== null) {
5954                // Find max across both PDFs
5955                const max1 = d3.max(pdfData, d => d.pdf);
5956                const max2 = d3.max(comparisonSimData.pdf_values, d => d.pdf);
5957                maxPDFValue = Math.max(max1, max2);
5958            } else {
5959                // Just primary athlete (default behavior)
5960                maxPDFValue = d3.max(pdfData, d => d.pdf);
5961            }
5962            
5963            const lineYScale = d3.scaleLinear()
5964                .domain([0, maxPDFValue])
5965                .range([chartHeight, 0]);
5966
5967            // Create x-axis
5968            const xAxis = d3.axisBottom(xScale);
5969
5970            // Create y-axis (left side, bar chart)
5971            const yAxis = d3.axisLeft(yScale);
5972
5973            // Create y-axis (right side, line chart)
5974            const lineYAxis = d3.axisRight(lineYScale);
5975
5976            function make_x_gridlines() {
5977                return d3.axisBottom(xScale).ticks(10);
5978            }
5979
5980            function make_y_gridlines() {
5981                return d3.axisLeft(yScale).ticks(10);
5982            }
5983
5984            // Add X gridlines
5985            chart.append("g")
5986                .attr("class", "grid")
5987                .attr("transform", `translate(0, ${chartHeight})`)
5988                .call(make_x_gridlines()
5989                    .tickSize(-chartHeight)
5990                    .tickFormat("")
5991                )
5992                .style("stroke", "lightgrey")
5993                .style("opacity", 0.3);
5994
5995            // Add Y gridlines
5996            chart.append("g")
5997                .attr("class", "grid")
5998                .call(make_y_gridlines()
5999                    .tickSize(-chartWidth)
6000                    .tickFormat("")
6001                )
6002                .style("stroke", "lightgrey")
6003                .style("opacity", 0.3);
6004
6005
6006            // Add x-axis to the chart
6007            chart.append('g')
6008                .attr('class', 'axis')
6009                .attr('transform', `translate(0, ${chartHeight})`)
6010                .call(xAxis);
6011
6012            // Add y-axis to the chart (left side, bar chart)
6013            chart.append('g')
6014                .attr('class', 'axis')
6015                .call(yAxis);
6016
6017            // Add y-axis to the chart (right side, line chart)
6018            chart.append('g')
6019                .attr('class', 'axis')
6020                .attr('transform', `translate(${chartWidth}, 0)`)
6021                .call(lineYAxis);
6022
6023            // Create a line chart path (PDF) using dense pdf_values on linear scale
6024            const line = d3.line()
6025                .x(d => xLinearScale(parseFloat(d.label)))  // Use linear scale, same as mean line
6026                .y(d => lineYScale(d.pdf))
6027                .curve(d3.curveBasis);  // Smooth curve; with 200 points this won't shift the peak
6028
6029            chart.append('path')
6030                .datum(pdfData)  // Use dense pdf_values (200 points) for smooth curve
6031                .attr('class', 'line')
6032                .attr('d', line)
6033                .attr("stroke", function (d) {
6034                    return "rgba(158,1,66,0.4)";
6035                })
6036                .style("stroke-width", 3)
6037                .style("fill", "none")
6038
6039            // Draw comparison PDF curve if present
6040            if (comparisonSimData !== null) {
6041                const comparisonPdfData = comparisonSimData.pdf_values;
6042                
6043                chart.append('path')
6044                    .datum(comparisonPdfData)
6045                    .attr('class', 'line-comparison')
6046                    .attr('d', line)  // Same line generator
6047                    .attr("stroke", "rgba(0,158,66,0.6)")  // Green color
6048                    .style("stroke-width", 2.5)
6049                    .style("fill", "none");
6050            }
6051
6052            // Create bars (bar chart)
6053            chart.selectAll('.bar')
6054                .data(data)
6055                .enter()
6056                .append('rect')
6057                .attr('class', 'bar')
6058                .attr('x', d => xScale(d.label))
6059                .attr('y', d => yScale(d.value))
6060                .attr('width', xScale.bandwidth())
6061                .attr('height', d => chartHeight - yScale(d.value))
6062                .style('fill', 'rgba(50, 136, 189, 0.7)')
6063                .on("mouseover", function (d) {
6064                    d3.select(event.currentTarget).style("opacity", 0.75);
6065                })
6066                .on("mouseout", function (d) {
6067                    d3.select(event.currentTarget).style("opacity", 1);
6068                })
6069
6070
6071            // Add labels for bars (bar chart)
6072            chart.selectAll('.bar-label')
6073                .data(data)
6074                .enter()
6075                .append('text')
6076                .attr('class', 'axis')
6077                .attr('x', d => xScale(d.label) + xScale.bandwidth() / 2)
6078                .attr('y', d => yScale(d.value) - 5) // Adjust for label position
6079                .text(d => (d.probability * 100).toFixed(1) + "%")
6080                .attr('text-anchor', 'middle')
6081                .style('font-size', '11px')
6082                .style('fill', 'grey')
6083
6084
6085            // Mean line (expected value)
6086            chart.append("line")
6087                .attr("x1", xLinearScale(currentMean))
6088                .attr("x2", xLinearScale(currentMean))
6089                .attr("y1", 0)
6090                .attr("y2", chartHeight)
6091                .style("stroke", "rgba(0,0,0,0.4)")
6092                .style("stroke-width", 2)
6093                .style("stroke-dasharray", "4,2");
6094
6095            // Mode line (most likely value / PDF peak) - limited to PDF height
6096            let pdfAtPrimaryMode = 0;
6097            
6098            // Find the PDF value at the mode position
6099            for (let i = 0; i < pdfData.length; i++) {
6100                if (Math.abs(parseFloat(pdfData[i].label) - currentMode) < 0.01) {
6101                    pdfAtPrimaryMode = pdfData[i].pdf;
6102                    break;
6103                }
6104            }
6105            
6106            // If we couldn't find exact match, find the max PDF (peak)
6107            if (pdfAtPrimaryMode === 0) {
6108                pdfAtPrimaryMode = d3.max(pdfData, d => d.pdf);
6109            }
6110            
6111            chart.append("line")
6112                .attr("x1", xLinearScale(currentMode))
6113                .attr("x2", xLinearScale(currentMode))
6114                .attr("y1", lineYScale(pdfAtPrimaryMode))  // Start at PDF height
6115                .attr("y2", chartHeight)                   // End at bottom
6116                .style("stroke", "rgba(158,1,66,0.4)")
6117                .style("stroke-width", 2)
6118                .style("stroke-dasharray", "8,4");
6119            
6120            // Comparison mode line (if present) - limited to PDF height
6121            if (comparisonSimData !== null) {
6122                const comparisonMode = comparisonSimData.mode !== undefined ? comparisonSimData.mode : comparisonSimData.mean;
6123                
6124                // Find the PDF value at the mode position
6125                const comparisonPdfData = comparisonSimData.pdf_values;
6126                let pdfAtMode = 0;
6127                
6128                // Find closest PDF point to the mode
6129                for (let i = 0; i < comparisonPdfData.length; i++) {
6130                    if (Math.abs(parseFloat(comparisonPdfData[i].label) - comparisonMode) < 0.01) {
6131                        pdfAtMode = comparisonPdfData[i].pdf;
6132                        break;
6133                    }
6134                }
6135                
6136                // If we couldn't find exact match, find the max PDF (peak)
6137                if (pdfAtMode === 0) {
6138                    pdfAtMode = d3.max(comparisonPdfData, d => d.pdf);
6139                }
6140                
6141                chart.append("line")
6142                    .attr("x1", xLinearScale(comparisonMode))
6143                    .attr("x2", xLinearScale(comparisonMode))
6144                    .attr("y1", lineYScale(pdfAtMode))  // Start at PDF height
6145                    .attr("y2", chartHeight)            // End at bottom
6146                    .style("stroke", "rgba(0,158,66,0.6)")
6147                    .style("stroke-width", 2)
6148                    .style("stroke-dasharray", "8,4");
6149            }
6150
6151            // Consolidated legend below chart - centered
6152            const legend = chart.append("g")
6153                .attr("class", "consolidated-legend")
6154                .attr("transform", `translate(${chartWidth / 2}
6154, ${chartHeight + 35})`);
6155
6156            let xOffset = 0;
6157
6158            // PDF curve indicator
6159            legend.append("line")
6160                .attr("x1", xOffset)
6161                .attr("x2", xOffset + 20)
6162                .attr("y1", 0)
6163                .attr("y2", 0)
6164                .style("stroke", "rgba(158,1,66,0.4)")
6165                .style("stroke-width", 3);
6166
6167            legend.append("text")
6168                .attr("x", xOffset + 25)
6169                .attr("y", 4)
6170                .text("Probability density function (PDF)")
6171                .style("font-size", "11px")
6172                .style("fill", "grey")
6173                .attr("text-anchor", "start");
6174
6175            xOffset += 250;
6176
6177            // Mean line indicator
6178            legend.append("line")
6179                .attr("x1", xOffset)
6180                .attr("x2", xOffset + 20)
6181                .attr("y1", 0)
6182                .attr("y2", 0)
6183                .style("stroke", "rgba(0,0,0,0.4)")
6184                .style("stroke-width", 2)
6185                .style("stroke-dasharray", "4,2");
6186
6187            legend.append("text")
6188                .attr("x", xOffset + 25)
6189                .attr("y", 4)
6190                .text("Mean (expected value)")
6191                .style("font-size", "11px")
6192                .style("fill", "grey")
6193                .attr("text-anchor", "start");
6194
6195            xOffset += 180;
6196
6197            // Mode line indicator
6198            legend.append("line")
6199                .attr("x1", xOffset)
6200                .attr("x2", xOffset + 20)
6201                .attr("y1", 0)
6202                .attr("y2", 0)
6203                .style("stroke", "rgba(158,1,66,0.4)")
6204                .style("stroke-width", 2)
6205                .style("stroke-dasharray", "8,4");
6206
6207            legend.append("text")
6208                .attr("x", xOffset + 25)
6209                .attr("y", 4)
6210                .text("Mode (most likely value)")
6211                .style("font-size", "11px")
6212                .style("fill", "grey")
6213                .attr("text-anchor", "start");
6214
6215            // Center the entire legend group
6216            const legendBBox = legend.node().getBBox();
6217            legend.attr("transform", `translate(${(chartWidth - legendBBox.width) / 2}
6217, ${chartHeight + 35})`);
6218            
6219            // Add second legend line for comparison athlete if present
6220            if (comparisonSimData !== null && comparisonName !== null) {
6221                const legend2 = chart.append("g")
6222                    .attr("class", "consolidated-legend-comparison");
6223                
6224                let xOffset2 = 0;
6225                
6226                // Comparison PDF line indicator
6227                legend2.append("line")
6228                    .attr("x1", xOffset2)
6229                    .attr("x2", xOffset2 + 20)
6230                    .attr("y1", 0)
6231                    .attr("y2", 0)
6232                    .style("stroke", "rgba(0,158,66,0.6)")
6233                    .style("stroke-width", 2.5)
6234                    .style("stroke-dasharray", "5,5");
6235
6236                legend2.append("text")
6237                    .attr("x", xOffset2 + 25)
6238                    .attr("y", 4)
6239                    .text("PDF (" + comparisonName + ", μ:" + comparisonSimData.mean.toFixed(2) + ", σ:" + comparisonSimData.sigma.toFixed(2) + ")")
6240                    .style("font-size", "11px")
6241                    .style("fill", "grey")
6242                    .attr("text-anchor", "start");
6243                
6244                // Center the comparison legend group (with more space below first line)
6245                const legend2BBox = legend2.node().getBBox();
6246                legend2.attr("transform", `translate(${(chartWidth - legend2BBox.width) / 2}
6246, ${chartHeight + 55})`);
6247            }
6248
6249        })
6250        .catch(function(error) {
6251            console.error("Simulation API error:", error);
6252        });
6253}
6254
6255
6256function barChartRecords(size, ana, type, podiums, width, height, selected) {
6257
6258    // Define the div for the tooltip
6259    var div = d3.select("body").append("div")
6260        .attr("class", "tooltip")
6261        .style("opacity", 0);
6262
6263    // set the dimensions and margins of the graph
6264    var margin = {top: 10, right: 75, bottom: 70, left: 35},
6265        width = width - margin.left - margin.right,
6266        height = height - margin.top - margin.bottom;
6267
6268    var svg = d3.select("div#" + size + "chartdiv")
6269        .append("div")
6270        // Container class to make it responsive.
6271        .classed("svg-container", true)
6272        .append("svg")
6273        .attr("preserveAspectRatio", "xMinYMin meet")
6274        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
6275        .classed("svg-content-responsive", true)
6276        .append("g")
6277        .attr("transform",
6278            "translate(" + margin.left + "," + margin.top + ")");
6279
6280
6281    // List of subgroups = header of the csv files = soil condition here
6282    var subgroups = ["Wins", "2nd places", "3rd places"];
6283
6284
6285    // Add X axis
6286    var x = d3.scaleBand()
6287        .domain(subgroups)
6288        .range([0, width])
6289        .padding([0.2])
6290    var xAxis = svg.append("g").attr("class", "x axis")
6291        .attr("transform", "translate(0," + height + ")")
6292        .call(d3.axisBottom(x).tickSizeOuter(0));
6293
6294    // Add Y axis
6295    var y = d3.scaleLinear()
6296        .domain([0, 1])
6297        .range([height, 0]);
6298    var yAxis = svg.append("g").attr("class", "y axis")
6299        .call(d3.axisLeft(y));
6300
6301    // color palette = one color per subgroup
6302    var color = d3.scaleOrdinal()
6303        .domain(subgroups)
6304        .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
6305            "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
6306
6307    function update(selected) {
6308        var data = [];
6309        var headerlist = []
6310        var max = 0;
6311        if (selected === 2 || selected === 4 || selected === 8) {
6312            ana.forEach(function (e, i) {
6313                var item = {
6314                    group: e.Discipline,
6315                    Wins: e.Wins,
6316                    '2nd places': e['2nd'],
6317                    '3rd places': e['3rd'],
6318                    Podiums: e.Podiums,
6319                    '4th - 10th': e.Top10s - e.Podiums,
6320                    '4th - 5th': e.Top5s - e.Podiums,
6321                    '6th - 10th': e.Top10s - e.Top5s,
6322
6323                }
6324
6325                if (e[chosenstat.charAt(0).toUpperCase() + chosenstat.slice(1)] !== 0) {
6326                    data.push(item)
6327                    headerlist.push(item.Discipline)
6328
6329
6330                    if (selected === 2) {
6331                        if (item.Podiums > max) max = item.Podiums;
6332                    } else if (selected === 8) {
6333                        if (e.Top5s > max) max = e.Top5s;
6334                    } else {
6335                        if (e.Top10s > max) max = e.Top10s;
6336                    }
6337                }
6338
6339
6340            })
6341        }
6342        if (selected === 1 || selected === 3 || selected === 7) {
6343            ana.forEach(function (e, i) {
6344
6345                var item = {
6346                    group: e.Discipline,
6347                    Wins: isNaN(e.Wins / e.Races) ? 0 : (e.Wins / e.Races * 100),
6348                    '2nd places': isNaN(e['2nd'] / e.Races) ? 0 : (e['2nd'] / e.Races * 100),
6349                    '3rd places': isNaN(e['3rd'] / e.Races) ? 0 : (e['3rd'] / e.Races * 100),
6350                    Podiums: isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100),
6351                    '4th - 5th': (isNaN(e.Top5s / e.Races) ? 0 : (e.Top5s / e.Races * 100)) - (isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100)),
6352                    '4th - 10th': (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)) - (isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100)),
6353                    '11th - 20th': (isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races * 100)) - (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)),
6354
6355                }
6356                if (e[chosenstat.charAt(0).toUpperCase() + chosenstat.slice(1)] !== 0) {
6357                    data.push(item)
6358                    headerlist.push(item.Discipline)
6359                    if (item.Wins + item['2nd places'] + item['3rd places'] > max && selected === 1)
6360                        max = item.Wins + item['2nd places'] + item['3rd places'];
6361                    if (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100) > max && selected === 3)
6362                        max = isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100);
6363                    if (isNaN(e.Top5s / e.Races) ? 0 : (e.Top5s / e.Races * 100) > max && selected === 7)
6364                        max = isNaN(e.Top5s / e.Races) ? 0 : (e.Top5s / e.Races * 100);
6365                }
6366
6367            })
6368        }
6369        if (selected === 5 || selected === 9 || selected === 11) {
6370
6371            ana.forEach(function (e, i) {
6372                var item = {
6373                    group: e.Discipline,
6374                    Podiums: isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100),
6375                    '4th - 10th ': (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)) - (isNaN(e.Podiums / e.Races) ? 0 : (e.Podiums / e.Races * 100)),
6376                    '11th - 20th': (isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races * 100)) - (isNaN(e.Top10s / e.Races) ? 0 : (e.Top10s / e.Races * 100)),
6377                    '21th - 30th': (isNaN(e.Top30s / e.Races) ? 0 : (e.Top30s / e.Races * 100)) - (isNaN(e.Top20s / e.Races) ? 0 : (e.Top20s / e.Races * 100)),
6378                    '31th - 40th': (isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100)) - (isNaN(e.Top30s / e.Races) ? 0 : (e.Top30s / e.Races * 100))
6379                }
6380                if (e[chosenstat.charAt(0).toUpperCase() + chosenstat.slice(1)] !== 0) {
6381                    data.push(item)
6382                    headerlist.push(item.Discipline)
6383                    if (isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100) > max)
6384                        max = isNaN(e.Top40s / e.Races) ? 0 : (e.Top40s / e.Races * 100);
6385                }
6386
6387            })
6388
6389        }
6390        if (selected === 6 || selected === 10 || selected === 12) {
6391            ana.forEach(function (e, i) {
6392                var item = {
6393                    group: e.Discipline,
6394                    Podiums: e.Podiums,
6395                    '4th - 10th ': e.Top10s - e.Podiums,
6396                    '11th - 20th': e.Top20s - e.Top10s,
6397                    '21th - 30th': e.Top30s - e.Top20s,
6398                    '31th - 40th': e.Top40s - e.Top30s
6399                }
6400                if (e[chosenstat.charAt(0).toUpperCase() + chosenstat.slice(1)] !== 0) {
6401                    data.push(item)
6402                    headerlist.push(item.Discipline)
6403
6404                    if (selected === 10) {
6405                        if (e.Top20s > max) max = e.Top20s;
6406                    } else if (selected === 12) {
6407                        if (e.Top30s > max) max = e.Top30s;
6408                    } else {
6409                        if (e.Top40s > max) max = e.Top40s;
6410                    }
6411                }
6412
6413            })
6414        }
6415
6416        if (selected === 7 || selected === 8)
6417            subgroups = ["Wins", "2nd places", "3rd places", "4th - 5th"];
6418        if (selected === 9 || selected === 10)
6419            subgroups = ["Podiums", "4th - 10th ", "11th - 20th"];
6420        if (selected === 11 || selected === 12)
6421            subgroups = ["Podiums", "4th - 10th ", "11th - 20th", "21th - 30th"];
6422
6423        if (selected === 5 || selected === 6)
6424            subgroups = ["Podiums", "4th - 10th ", "11th - 20th", "21th - 30th", "31th - 40th"];
6425        if (selected === 3 || selected === 4)
6426            subgroups = ["Wins", "2nd places", "3rd places", "4th - 10th"];
6427        if (selected === 1 || selected === 2)
6428            subgroups = ["Wins", "2nd places", "3rd places"];
6429
6430        //console.log(data)
6431
6432        d3.selectAll(".barchartbars" + type).remove();
6433        d3.selectAll(".endlabels" + type).remove();
6434        d3.selectAll(".grid2" + type).remove();
6435
6436        // List of groups = species here = value of the first column called group -> I show them on the X axis
6437        var groups = d3.map(data, function (d) {
6438            return (d.group)
6439        }).keys()
6440
6441        var myColor = d3.scaleOrdinal()
6442            .domain(subgroups)
6443            .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
6444                "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
6445
6446
6447        //stack the data? --> stack per subgroup
6448        var stackedData = d3.stack()
6449            .keys(subgroups)
6450            (data)
6451        let divider = Math.ceil(data.length / 60);
6452
6453        // X axis
6454        x.domain(groups)
6455        xAxis.call(d3.axisBottom(x)
6456            .tickValues(x.domain().filter(function (d, i) {
6457                return !(i % divider)
6458            }))
6459            .tickFormat(function (d) {
6460                return d.toString().replace("Commonwealth Of Independent States", "CIS")
6461            }).ticks(10)
6462        ).selectAll("text")
6463            .style("text-anchor", "start")
6464            .attr("dx", function (d) {
6465                return 4;
6466            })
6467            .attr("dy", 8)
6468            .attr("transform", function (d) {
6469                return "rotate(28)";
6470            });
6471
6472        // Add Y axis
6473        y.domain([0, max]);
6474        yAxis.call(d3.axisLeft(y).tickFormat(function (d) {
6475            if (disPercent === "Percent")
6476                return d + "%";
6477            else {
6478                if (Math.floor(d) != d) {
6479                    return;
6480                }
6481
6482                return d;
6483            }
6484        }));
6485
6486        // add the x Axis
6487        svg.append("g")
6488            .attr("transform", "translate(0," + height + ")").attr("class", "x axis")
6489
6490
6491        var u = svg.selectAll("rect")
6492            .data(data)
6493
6494        // gridlines in x axis function
6495        function make_x_gridlines() {
6496            return d3.axisBottom(x).ticks(16)
6497        }
6498
6499        // gridlines in y axis function
6500        function make_y_gridlines() {
6501            return d3.axisLeft(y).ticks(10)
6502        }
6503
6504
6505        svg.append("text")
6506            .attr("x", width - 94)
6507            .attr("y", 16)
6508            .attr("class", "svgdarktext")
6509            .text("realbiathlon.com")
6510            .style("font-size", "12px")
6511            .style("opacity", 0.4)
6512
6513        // add the X gridlines
6514        svg.append("g")
6515            .attr("class", "grid")
6516            .attr("transform", "translate(0," + height + ")")
6517            .call(make_x_gridlines()
6518                .tickSize(-height)
6519                .tickFormat("")
6520            )
6521        var g = svg.append("g")
6522            .attr("class", "grid")
6523
6524            .call(make_y_gridlines()
6525                .tickSize(-width)
6526                .tickFormat("")
6527            )
6528
6529        var barpos = []
6530        var endpos = []
6531        // Show the bars
6532        var barchart = svg.append("g")
6533            .selectAll("g")
6534            // Enter in the stack data = loop key per key = group per group
6535            .merge(u)
6536            .data(stackedData)
6537            .enter().append("g")
6538            .attr("fill", function (d) {
6539                return myColor(d.key);
6540            })
6541            .selectAll("rect")
6542            // enter a second time = loop subgroup per subgroup to add all rectangles
6543            .data(function (d) {
6544                return d;
6545            })
6546            .enter().append("rect")
6547            .attr("class", "barchartbars" + type)
6548            .attr("x", function (d) {
6549                barpos.push(x(d.data.group))
6550                return x(d.data.group);
6551            })
6552            .attr("y", function (d) {
6553                if (d.data.group === data[data.length - 1].group)
6554                    endpos.push([y(d[1])]);
6555                return y(d[1]);
6556            })
6557            .attr("height", function (d) {
6558                return y(d[0]) - y(d[1]);
6559            })
6560            .attr("width", x.bandwidth())
6561            .style("opacity", function (d) {
6562                if (d.data.group === "Total" || d.data.group === "Total Team" || d.data.group === "Total Non-Team")
6563                    return 1;
6564                else
6565                    return 1;
6566            })
6567            .style("stroke", function (d) {
6568                if (d.data.group === "Total" || d.data.group === "Total Team" || d.data.group === "Total Non-Team")
6569                    return 'grey';
6570                else
6571                    return 'grey';
6572            })
6573            .attr('stroke-width', 1)
6574
6575
6576        d3.selectAll("rect")
6577            .on("mouseover", function (d) {
6578                d3.select(event.currentTarget).style("cursor", "pointer");
6579                d3.select(event.currentTarget).style("opacity", 0.75);
6580
6581                if (d.label === undefined) {
6582                    var subgroupName = d3.select(this.parentNode).datum().key;
6583                    var subgroupValue = d.data[subgroupName];
6584                    div
6585                        .style("opacity", .8);
6586                    if (selected % 2 === 1)
6587                        div.html(d.data.group.replace("Thingnes", "T.") + "<br/>" + subgroupName + ":<br/>" + subgroupValue.toFixed(1) + "%")
6588                    if (selected % 2 === 0)
6589                        div.html(d.data.group.replace("Thingnes", "T.") + "<br/>" + subgroupName + ":<br/>" + subgroupValue)
6590                            .style("left", (d3.event.pageX) + "px")
6591                            .style("top", (d3.event.pageY - 28) + "px");
6592                }
6593            })
6594            .on("mousemove", function (d) {
6595                div
6596                    .style("left", (d3.event.pageX + 15) + "px")
6597                    .style("top", (d3.event.pageY + 15) + "px")
6598            })
6599            .on("mouseout", function (d) {
6600                d3.select(event.currentTarget).style("opacity", 1);
6601
6602                div
6603                    .style("opacity", 0);
6604            })
6605
6606        var l = svg.selectAll("myLabels")
6607            .data(subgroups)
6608
6609        l
6610            .enter()
6611            .append('g')
6612
6613            .append("text")
6614            .merge(l)
6615
6616            .attr("class", 'endlabels' + type)
6617            .datum(function (d) {
6618                if (d === "Wins" && (endpos[0][0] !== height))
6619                    return {name: d, value: 3 + (endpos[0][0] + height) / 2};
6620                if (d === "2nd places" && (endpos[0][0] !== endpos[1][0]))
6621                    return {name: d, value: 2 + (endpos[0][0] + endpos[1][0]) / 2};
6622                if (d === "3rd places" && (endpos[2][0] !== endpos[1][0]))
6623                    return {name: d, value: (endpos[2][0] + endpos[1][0]) / 2};
6624                if (d === "4th - 10th" && (endpos[2][0] !== endpos[3][0]))
6625                    return {name: d, value: (endpos[2][0] + endpos[3][0]) / 2};
6626                if (d === "4th - 5th" && (endpos[2][0] !== endpos[3][0]))
6627                    return {name: d, value: (endpos[2][0] + endpos[3][0]) / 2};
6628
6629                if (d === "Podiums" && (endpos[0][0] !== height))
6630                    return {name: d, value: (endpos[0][0] + height) / 2};
6631                if (d === "4th - 10th " && (endpos[0][0] !== endpos[1][0]))
6632                    return {name: d, value: (endpos[0][0] + endpos[1][0]) / 2};
6633                if (d === "11th - 20th" && (endpos[2][0] !== endpos[1][0]))
6634                    return {name: d, value: (endpos[2][0] + endpos[1][0]) / 2};
6635                if (d === "21th - 30th" && (endpos[2][0] !== endpos[3][0]))
6636                    return {name: d, value: (endpos[2][0] + endpos[3][0]) / 2};
6637                if (d === "31th - 40th" && (endpos[3][0] !== endpos[4][0]))
6638                    return {name: d, value: (endpos[3][0] + endpos[4][0]) / 2};
6639            }) // keep only the last value of each time series
6640
6641            .attr("transform", function (d) {
6642                if (d !== undefined) {
6643                    return "translate(" + (width - 15) + "," + (d.value + 2) + ")";
6644                }
6645
6646            }) // Put the text at the position of the last point
6647            .attr("x", 14) // shift the text a bit more right
6648            .text(function (d) {
6649                if (d !== undefined) {
6650                    return d.name;
6651                }
6652            })
6653            .style("fill", function (d) {
6654                if (d !== undefined) {
6655                    return myColor(d.name)
6656                }
6657            })
6658            .style("font-size", 15)
6659
6660
6661    }
6662
6663
6664    update(selected);
6665
6666
6667}
6668
6669function barChartRecords2(size, ana, type, podiums, width, height, type2) {
6670
6671    // Define the div for the tooltip
6672    var div = d3.select("body").append("div")
6673        .attr("class", "tooltip")
6674        .style("opacity", 0);
6675
6676    // set the dimensions and margins of the graph
6677    var margin = {top: 10, right: 75, bottom: 80, left: 35},
6678        width = width - margin.left - margin.right,
6679        height = height - margin.top - margin.bottom;
6680
6681    var svg = d3.select("div#" + size + "chartdiv")
6682        .append("div")
6683        // Container class to make it responsive.
6684        .classed("svg-container", true)
6685        .append("svg")
6686        .attr("preserveAspectRatio", "xMinYMin meet")
6687        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
6688        .classed("svg-content-responsive", true)
6689        .append("g")
6690        .attr("transform",
6691            "translate(" + margin.left + "," + margin.top + ")");
6692
6693    // Add X axis
6694    var x = d3.scaleBand()
6695        .range([0, width])
6696        .padding([0.2])
6697
6698    // Add Y axis
6699    var y = d3.scaleLinear()
6700        .range([height, 0]);
6701
6702    // color palette = one color per subgroup
6703    var color = d3.scaleOrdinal()
6704        .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
6705            "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
6706
6707    function update(selected) {
6708        var data = [];
6709        var headerlist = []
6710        var max = 0;
6711        ana.forEach(function (e, i) {
6712
6713            var item = {
6714                name: e.Discipline
6715            }
6716            if (disPercent === "Percent" && type2 === 1)
6717                item[chosenstat] = +e[chosenstat] / e.races * 100
6718            else if (disPercent === "Percent" && type2 === 2)
6719                item[chosenstat] = +e[chosenstat] / e.seasons * 100
6720            else
6721                item[chosenstat] = +e[chosenstat]
6722            if (item[chosenstat] !== 0) data.push(item)
6723
6724        })
6725
6726        //console.log(data)
6727
6728        d3.selectAll(".barchartbars" + type).remove();
6729        d3.selectAll(".endlabels" + type).remove();
6730        d3.selectAll(".grid2" + type).remove();
6731
6732        // List of groups = species here = value of the first column called group -> I show them on the X axis
6733        var groups = d3.map(data, function (d) {
6734            return (d.group)
6735        }).keys()
6736
6737        // X axis
6738        x.domain(data.map(function (d) {
6739            return d.name;
6740        }));
6741
6742        // Add Y axis
6743        y.domain([0, d3.max(data, function (d) {
6744            return d[chosenstat];
6745        })]);
6746
6747        // gridlines in x axis function
6748        function make_x_gridlines() {
6749            return d3.axisBottom(x).ticks(16)
6750        }
6751
6752        // gridlines in y axis function
6753        function make_y_gridlines() {
6754            return d3.axisLeft(y).ticks(10)
6755        }
6756
6757        svg.append("text")
6758            .attr("x", width - 94)
6759            .attr("y", 16)
6760            .attr("class", "svgdarktext")
6761            .text("realbiathlon.com")
6762            .style("font-size", "12px")
6763            .style("opacity", 0.4)
6764
6765        // add the X gridlines
6766        svg.append("g")
6767            .attr("class", "grid")
6768            .attr("transform", "translate(0," + height + ")")
6769            .call(make_x_gridlines()
6770                .tickSize(-height)
6771                .tickFormat("")
6772            )
6773        var g = svg.append("g")
6774            .attr("class", "grid")
6775            .call(make_y_gridlines()
6776                .tickSize(-width)
6777                .tickFormat("")
6778            )
6779
6780        svg.selectAll("bar")
6781            .data(data)
6782            .enter().append("rect")
6783            .style("fill", "#66C2A5")
6784            .attr("x", function (d) {
6785                return x(d.name);
6786            })
6787            .attr("width", x.bandwidth())
6788            .attr("y", function (d) {
6789                return y(d[chosenstat]);
6790            })
6791            .attr("height", function (d) {
6792                return height - y(d[chosenstat]);
6793            })
6794            .on("mouseover", function (d) {
6795                d3.select(event.currentTarget).style("opacity", 0.75);
6796                d3.select(event.currentTarget).style("cursor", "pointer");
6797
6798                let itemUp = chosenstat.charAt(0).toUpperCase() + chosenstat.slice(1)
6799                let catname = itemUp.match(/[A-Z][a-z]+|[0-9]+/g).join(" ").replace("Score", "zScore")
6800                div.style("opacity", .8);
6801                if (disPercent === "Percent" || chosenstat === "averageRank")
6802                    div.html(d.name.replace("Thingnes", "T.") + "<br/>" + catname + ":<br/>" + d[chosenstat].toFixed(3))
6803                else
6804                    div.html(d.name.replace("Thingnes", "T.") + "<br/>" + catname + ":<br/>" + d[chosenstat].toFixed(0))
6805            })
6806            .on("mousemove", function (d) {
6807                div
6808                    .style("left", (d3.event.pageX + 15) + "px")
6809                    .style("top", (d3.event.pageY + 15) + "px")
6810            })
6811            .on("mouseout", function (d) {
6812                d3.select(event.currentTarget).style("opacity", 1);
6813
6814                div
6815                    .style("opacity", 0);
6816            })
6817
6818        let divider = Math.ceil(data.length / 60);
6819
6820        // add the x Axis
6821        svg.append("g")
6822            .attr("transform", "translate(0," + height + ")").attr("class", "x axis")
6823            .call(d3.axisBottom(x)
6824                .tickValues(x.domain().filter(function (d, i) {
6825                    return !(i % divider)
6826                }))
6827                .tickFormat(function (d) {
6828                    return d.toString().replace("Commonwealth Of Independent States", "CIS")
6829                }).ticks(10)
6830            ).selectAll("text")
6831            .style("text-anchor", "start")
6832            .attr("dx", function (d) {
6833                return 4;
6834            })
6835            .attr("dy", 8)
6836            .attr("transform", function (d) {
6837                return "rotate(28)";
6838            });
6839
6840        // add the y Axis
6841        svg.append("g").attr("class", "y axis")
6842            .call(d3.axisLeft(y));
6843    }
6844
6845    update();
6846
6847
6848}
6849
6850
6851function radarChart(size, data, width, height, type2) {
6852
6853
6854    // set the dimensions and margins of the graph
6855    var margin = {top: 10, right: 40, bottom: 70, left: -20},
6856        width = width - margin.left - margin.right,
6857        height = height - margin.top - margin.bottom;
6858
6859    var svg = d3.select("div#radarchartdiv")
6860        .append("div")
6861        // Container class to make it responsive.
6862        .classed("svg-container-radar", true)
6863        .append("svg")
6864        .attr("preserveAspectRatio", "xMinYMin meet")
6865        .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
6866        .classed("svg-content-responsive2", true)
6867        .append("g")
6868        .attr("transform",
6869            "translate(" + margin.left + "," + margin.top + ")");
6870
6871    var margin = {top: 50, right: 80, bottom: 20, left: 1}
6872
6873    let colorsArray = []
6874    if (data.length > 3)
6875        colorsArray = Array(data.length - 3).fill("rgba(163,154,154,0.2)")
6876
6877    colorsArray.push("#fdae61")
6878    colorsArray.push("#abdda4")
6879    colorsArray.push("#66c2a5")
6880    colorsArray.push("#367fad")
6881    colorsArray.push("#5e4fa2")
6882    colorsArray.push("#9e0142")
6883
6884
6885    colorsArray.splice(0, colorsArray.length - data.length)
6886
6887    var color = d3.scaleOrdinal() // D3 Version 4
6888        .range(colorsArray)
6889
6890    var radarChartOptions = {
6891        w: width,
6892        h: height,
6893        margin: margin,
6894        color: color
6895    };
6896    Radar_Chart(".svg-content-responsive2", data, radarChartOptions);
6897
6898}
6899
6900function quartile(data, q) {
6901    data = array_Sort_Numbers(data);
6902    var pos = ((data.length) - 1) * q;
6903    var base = Math.floor(pos);
6904    var rest = pos - base;
6905    if ((data[base + 1] !== undefined)) {
6906        return data[base] + rest * (data[base + 1] - data[base]);
6907    } else {
6908        return data[base];
6909    }
6910}
6911
6912function array_Sort_Numbers(inputarray) {
6913    return inputarray.sort(function (a, b) {
6914        return a - b;
6915    });
6916}
6917
6918String.prototype.toHHMMSS = function () {
6919    if (isNaN(this) || this === null || this === "")
6920        return "";
6921    let sign = this < 0 ? "-" : "";
6922    var sec_num = Math.abs(this);
6923    var hours = Math.floor(sec_num / 3600);
6924    var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
6925    var seconds = sec_num - (hours * 3600) - (minutes * 60);
6926    if (hours < 1) {
6927        if (minutes < 1)
6928            return sign + parseFloat(seconds).toFixed(1);
6929        if (minutes >= 1 && parseFloat(seconds).toFixed(1) >= 10)
6930            return sign + minutes + ':' + parseFloat(seconds).toFixed(1);
6931        else
6932            return sign + minutes + ':' + "0" + parseFloat(seconds).toFixed(1);
6933    } else {
6934        if (minutes < 10) {
6935            minutes = "0" + minutes;
6936        }
6937        if (parseFloat(seconds).toFixed(1) < 1)
6938            return sign + hours + ':' + minutes + ':' + "0" + parseFloat(seconds).toFixed(1);
6939        if (seconds >= 1 && parseFloat(seconds).toFixed(1) < 10)
6940            return sign + hours + ':' + minutes + ':' + "0" + parseFloat(seconds).toFixed(1);
6941        else
6942            return sign + hours + ':' + minutes + ':' + parseFloat(seconds).toFixed(1);
6943    }
6944}
6945
6946function titleCase(str) {
6947    str = str.toLowerCase().split(' ');
6948    for (var i = 0; i < str.length; i++) {
6949        str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1);
6950    }
6951    return str.join(' ');
6952}
6953
6954const pSBC = (p, c0, c1, l) => {
6955    let r, g, b, P, f, t, h, i = parseInt, m = Math.round, a = typeof (c1) == "string";
6956    if (typeof (p) != "number" || p < -1 || p > 1 || typeof (c0) != "string" || (c0[0] != 'r' && c0[0] != '#') || (c1 && !a)) return null;
6957    if (!this.pSBCr) this.pSBCr = (d) => {
6958        let n = d.length, x = {};
6959        if (n > 9) {
6960            [r, g, b, a] = d = d.split(","), n = d.length;
6961            if (n < 3 || n > 4) return null;
6962            x.r = i(r[3] == "a" ? r.slice(5) : r.slice(4)), x.g = i(g), x.b = i(b), x.a = a ? parseFloat(a) : -1
6963        } else {
6964            if (n == 8 || n == 6 || n < 4) return null;
6965            if (n < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (n > 4 ? d[4] + d[4] : "");
6966            d = i(d.slice(1), 16);
6967            if (n == 9 || n == 5) x.r = d >> 24 & 255, x.g = d >> 16 & 255, x.b = d >> 8 & 255, x.a = m((d & 255) / 0.255) / 1000;
6968            else x.r = d >> 16, x.g = d >> 8 & 255, x.b = d & 255, x.a = -1
6969        }
6970        return x
6971    };
6972    h = c0.length > 9, h = a ? c1.length > 9 ? true : c1 == "c" ? !h : false : h, f = this.pSBCr(c0), P = p < 0, t = c1 && c1 != "c" ? this.pSBCr(c1) : P ? {
6973        r: 0,
6974        g: 0,
6975        b: 0,
6976        a: -1
6977    } : {r: 255, g: 255, b: 255, a: -1}, p = P ? p * -1 : p, P = 1 - p;
6978    if (!f || !t) return null;
6979    if (l) r = m(P * f.r + p * t.r), g = m(P * f.g + p * t.g), b = m(P * f.b + p * t.b);
6980    else r = m((P * f.r ** 2 + p * t.r ** 2) ** 0.5), g = m((P * f.g ** 2 + p * t.g ** 2) ** 0.5), b = m((P * f.b ** 2 + p * t.b ** 2) ** 0.5);
6981    a = f.a, t = t.a, f = a >= 0 || t >= 0, a = f ? a < 0 ? t : t < 0 ? a : a * P + t * p : 0;
6982    if (h) return "rgb" + (f ? "a(" : "(") + r + "," + g + "," + b + (f ? "," + m(a * 1000) / 1000 : "") + ")";
6983    else return "#" + (4294967296 + r * 16777216 + g * 65536 + b * 256 + (f ? m(a * 255) : 0)).toString(16).slice(1, f ? undefined : -2)
6984}
6985
6986function toDateString(epoch) {
6987    if (typeof epoch === "undefined" || epoch === undefined || epoch === "" || epoch === "\u2063") return "";
6988
6989    let date = "";
6990    try {
6991        date = new Date(epoch * 1000).toISOString().substring(0, 10);
6992    } catch (e) {
6993    }
6994    return date;
6995}
6996
6997function getSeason(id) {
6998    if (typeof id === "undefined" || id === undefined || id === "" || id === "\u2063") return "";
6999
7000    let seasonstring = "";
7001    if (id.substring(0, 1) > 4)
7002        seasonstring = "19" + id.substring(0, 2) + "–" + id.substring(2, 4);
7003    else
7004        seasonstring = "20" + id.substring(0, 2) + "–" + id.substring(2, 4);
7005    return seasonstring;
7006}
7007
7008function getEvent(id, raceId) {
7009    if (typeof id === "undefined" || id === undefined || id === "" || id === "\u2063") return "";
7010
7011    let level = id;
7012    let isYouth = raceId.includes("__Y")
7013    if (id === "BTSWRLCH")
7014        level = "WCH";
7015    else if (id === "BTSWRLOG")
7016        level = "Olympics";
7017    else if (id === "BTSIBUCP")
7018        level = "Cup";
7019    else if (id === "BTSCEUCH")
7020        level = "OECH";
7021    else if (id === "BTJCEUCH")
7022        level = "JECH";
7023    else if (id === "BTJWRLCH" && isYouth)
7024        level = "YWCH";
7025    else if (id === "BTJWRLCH")
7026        level = "JWCH";
7027    else
7028        level = "Cup";
7029
7030    return level;
7031}
7032
7033function getLevel(id) {
7034    if (typeof id === "undefined" || id === undefined || id === "" || id === "\u2063") return "";
7035
7036    let level = id;
7037    if (id === "1")
7038        level = "World";
7039    if (id === "2")
7040        level = "IBU";
7041    if (id === "3")
7042        level = "Y/J";
7043    return level;
7044}
7045
7046function lead_zero(dt) {
7047    return (dt < 10 ? '0' : '') + dt;
7048}
7049
7050(function () {
7051
7052// Inspired by http://informationandvisualization.de/blog/box-plot
7053    d3.box = function () {
7054        var width = 1,
7055            height = 1,
7056            duration = 0,
7057            singleboxwidth = rangeDropDownString !== undefined && rangeDropDownString === "perSeason" && columnsNo > 8 ? 8 : 20;
7058        domain = null,
7059            value = Number,
7060            whiskers = boxWhiskers,
7061            quartiles = boxQuartiles,
7062            showLabels = true, // whether or not to show text labels
7063            numBars = 4,
7064            curBar = 1,
7065            tickFormat = null;
7066
7067
7068        if (columnsNo > 12) singleboxwidth = 6;
7069        if (columnsNo > 15) singleboxwidth = 5;
7070        if (columnsNo > 20) singleboxwidth = 4;
7071
7072        var div = d3.select("body").append("div")
7073            .attr("class", 'tooltip6')
7074            .style("opacity", 0);
7075
7076        // For each small multiple…
7077        function box(g) {
7078            g.each(function (data, i) {
7079                //d = d.map(value).sort(d3.ascending);
7080                //var boxIndex = data[0];
7081                //var boxIndex = 1;
7082                var d = data[1].sort(d3.ascending);
7083
7084                // console.log(boxIndex);
7085                //console.log(d);
7086
7087                var g = d3.select(this),
7088                    n = d.length,
7089                    min = d[0],
7090                    max = d[n - 1];
7091
7092                // Compute quartiles. Must return exactly 3 elements.
7093                var quartileData = d.quartiles = quartiles(d);
7094
7095                // Compute whiskers. Must return exactly 2 elements, or null.
7096                var whiskerIndices = whiskers && whiskers.call(this, d, i),
7097                    whiskerData = whiskerIndices && whiskerIndices.map(function (i) {
7098                        return d[i];
7099                    });
7100
7101                // Compute outliers. If no whiskers are specified, all data are "outliers".
7102                // We compute the outliers as indices, so that we can join across transitions!
7103                var outlierIndices = whiskerIndices
7104                    ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
7105                    : d3.range(n);
7106
7107                // Compute the new x-scale.
7108                var x1 = d3.scaleLinear()
7109                    .domain(domain && domain.call(this, d, i) || [min, max])
7110                    .range([height, 0]);
7111
7112                // Retrieve the old x-scale, if this is an update.
7113                var x0 = this.__chart__ || d3.scaleLinear()
7114                    .domain([0, Infinity])
7115                    // .domain([0, max])
7116                    .range(x1.range());
7117
7118                // Stash the new scale.
7119                this.__chart__ = x1;
7120
7121                // Note: the box, median, and box tick elements are fixed in number,
7122                // so we only have to handle enter and update. In contrast, the outliers
7123                // and other elements are variable, so we need to exit them! Variable
7124                // elements also fade in and out.
7125
7126                // Update center line: the vertical line spanning the whiskers.
7127                var center = g.selectAll("line.center")
7128                    .data(whiskerData ? [whiskerData] : []);
7129
7130                //vertical line
7131                center.enter().insert("line", "rect")
7132                    .attr("class", "center")
7133                    .merge(center)
7134                    .attr("x1", width / 2)
7135                    .attr("y1", function (d) {
7136                        return x1(d[0]);
7137                    })
7138                    .attr("x2", width / 2)
7139                    .attr("y2", function (d) {
7140                        return x1(d[1]);
7141                    })
7142                    .style("opacity", 1)
7143
7144
7145                // Update innerquartile box.
7146                var box = g.selectAll("rect.box")
7147                    .data([quartileData]);
7148
7149                box.enter().append("rect")
7150                    .attr("class", "box")
7151                    .merge(box)
7152                    .attr("x", width / 2 - singleboxwidth)
7153                    .attr("width", singleboxwidth * 2)
7154
7155
7156                    .attr("y", function (d) {
7157                        if (x1(d[0]) - x1(d[2]) < 0)
7158                            return x1(d[0]);
7159                        else
7160                            return x1(d[2]);
7161                    })
7162                    .attr("height", function (d) {
7163                        return Math.abs(x1(d[0]) - x1(d[2]));
7164                    })
7165                    .on("mouseenter", function () {
7166                        d3.select(this).attr("opacity", 0.7);
7167                        d3.select(this).style("cursor", "pointer");
7168                        div.style("opacity", .8);
7169                        div.html("Maximum:  " + whiskerData[1].toFixed(1) + " <br/>Upper quartile: " + d.quartiles[2].toFixed(1) + "<br/>Median:  " + d.quartiles[1].toFixed(1) +
7170                            " <br/>Lower quartile:  " + d.quartiles[0].toFixed(1) + " <br/>Minimum :  " + whiskerData[0].toFixed(1))
7171                    })
7172                    .on("mousemove", function (d) {
7173                        div
7174                            .style("left", (d3.event.pageX + 15) + "px")
7175                            .style("top", (d3.event.pageY + 15) + "px")
7176                    })
7177                    .on("mouseout", function (d) {
7178                        d3.select(this).attr("opacity", 1);
7179                        div
7180                            .style("opacity", 0);
7181
7182                    })
7183
7184
7185                // Update median line.
7186                var medianLine = g.selectAll("line.median")
7187                    .data([quartileData[1]]);
7188
7189                medianLine.enter().append("line")
7190                    .attr("class", "median")
7191                    .merge(medianLine)
7192                    .attr("x1", width / 2 - singleboxwidth)
7193                    .attr("y1", x1)
7194                    .attr("x2", width / 2 + singleboxwidth)
7195                    .attr("y2", x1);
7196
7197
7198                // Update whiskers.
7199                var whisker = g.selectAll("line.whisker")
7200                    .data(whiskerData || []);
7201
7202                whisker.enter().insert("line", "circle, text")
7203                    .attr("class", "whisker")
7204                    .merge(whisker)
7205                    .attr("x1", width / 2 - singleboxwidth)
7206                    .attr("y1", x1)
7207                    .attr("x2", width / 2 + singleboxwidth)
7208                    .attr("y2", x1)
7209                    .style("opacity", 1);
7210
7211
7212                // Update outliers.
7213                var outlier = g.selectAll("circle.outlier")
7214                    .data(outlierIndices, Number);
7215
7216                outlier.enter().insert("circle", "text")
7217                    .attr("class", "outlier")
7218                    .merge(outlier)
7219
7220                    .attr("r", 3)
7221                    .attr("cx", width / 2)
7222                    .attr("cy", function (i) {
7223                        return x1(d[i]);
7224                    })
7225                    .style("opacity", 1)
7226                    .on("mouseenter", function (i) {
7227                        d3.select(this).attr("opacity", 0.7);
7228                        d3.select(this).style("cursor", "pointer");
7229                        div.style("height", "17px");
7230
7231                        div.style("opacity", .8);
7232                        div.html("Outlier: " + d[i].toFixed(1))
7233                    })
7234                    .on("mousemove", function (d) {
7235                        div
7236                            .style("left", (d3.event.pageX + 15) + "px")
7237                            .style("top", (d3.event.pageY + 15) + "px")
7238                    })
7239                    .on("mouseout", function (d) {
7240                        d3.select(this).attr("opacity", 1);
7241                        div
7242                            .style("opacity", 0);
7243                        div.style("height", "75px");
7244
7245                    })
7246
7247
7248                // Compute the tick format.
7249                var format = tickFormat || x1.tickFormat(0.1),
7250                    formatDecimal = d3.format(".1f");
7251
7252
7253                // Update box ticks.
7254                var boxTick = g.selectAll("text.box")
7255                    .data(quartileData);
7256                if (showLabels == true) {
7257                    boxTick.enter().append("text")
7258                        .attr("class", "box boxplottext")
7259                        .attr("dy", ".3em")
7260                        .attr("dx", function (d, i) {
7261                            return i & 1 ? 6 : -6
7262                        })
7263                        .attr("x", function (d, i) {
7264                            return i & 1 ? +width / 2 + singleboxwidth : width / 2 - (singleboxwidth + 2)
7265                        })
7266                        .attr("y", x1)
7267                        .attr("text-anchor", function (d, i) {
7268                            return i & 1 ? "start" : "end";
7269                        })
7270                        .html(function (d) {
7271                            if (d !== undefined && typeof d === 'number')
7272                                return d.toFixed(1)
7273                        })
7274
7275
7276                }
7277
7278
7279                // Update whisker ticks. These are handled separately from the box
7280                // ticks because they may or may not exist, and we want don't want
7281                // to join box ticks pre-transition with whisker ticks post-.
7282                var whiskerTick = g.selectAll("text.whisker")
7283                    .data(whiskerData || []);
7284                if (showLabels == true) {
7285                    whiskerTick.enter().append("text")
7286                        .attr("class", "whisker boxplottext")
7287                        .attr("dy", ".3em")
7288                        .attr("dx", 6)
7289                        .attr("x", width / 2 + singleboxwidth)
7290                        .attr("y", x1)
7291                        .style("stroke", "none")
7292                        .html(function (d) {
7293                            if (d !== undefined && typeof d === 'number')
7294                                return d.toFixed(1)
7295                        })
7296
7297                        .style("opacity", 1);
7298                }
7299
7300            });
7301            d3.timerFlush;
7302        }
7303
7304        box.width = function (x) {
7305            if (!arguments.length) return width;
7306            width = x;
7307            return box;
7308        };
7309
7310        box.height = function (x) {
7311            if (!arguments.length) return height;
7312            height = x;
7313            return box;
7314        };
7315
7316        box.tickFormat = function (x) {
7317            if (!arguments.length) return tickFormat;
7318            tickFormat = x;
7319            return box;
7320        };
7321
7322        box.duration = function (x) {
7323            if (!arguments.length) return duration;
7324            duration = x;
7325            return box;
7326        };
7327
7328        box.domain = function (x) {
7329            if (!arguments.length) return domain;
7330            domain = x == null ? x : constant(x);
7331            return box;
7332        };
7333
7334        box.value = function (x) {
7335            if (!arguments.length) return value;
7336            value = x;
7337            return box;
7338        };
7339
7340        box.whiskers = function (x) {
7341            if (!arguments.length) return whiskers;
7342            whiskers = x;
7343            return box;
7344        };
7345
7346        box.showLabels = function (x) {
7347            if (!arguments.length) return showLabels;
7348            showLabels = x;
7349            return box;
7350        };
7351
7352        box.quartiles = function (x) {
7353            if (!arguments.length) return quartiles;
7354            quartiles = x;
7355            return box;
7356        };
7357
7358        return box;
7359    };
7360
7361    function boxWhiskers(d) {
7362        return [0, d.length - 1];
7363    }
7364
7365    function boxQuartiles(d) {
7366        return [
7367            d3.quantile(d, .25),
7368            d3.quantile(d, .5),
7369            d3.quantile(d, .75)
7370        ];
7371    }
7372
7373    function constant(x) {
7374        return function () {
7375            return x;
7376        };
7377    }
7378})();

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.