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("' + d.ibuId + '");" 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.