NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name iRacing Series Participation // @namespace http://www.fuzzwahracing.com/p/participation.html // @version 0.6 // @downloadURL https://raw.githubusercontent.com/fuzzwah/iRacing-Series-Participation/master/iRacing-Series-Participation.user.js // @updateURL https://raw.githubusercontent.com/fuzzwah/iRacing-Series-Participation/master/iRacing-Series-Participation.user.js // @license MIT; https://raw.githubusercontent.com/fuzzwah/iRacing-Series-Participation/master/LICENSE // @description Visualize the participation (drivers and SOF) of any series in the series home page. // @match https://members.iracing.com/membersite/member/SeriesNews.do* // @copyright 2016, Nick Thissen - 2019, Rob Crouch // ==/UserScript== // Add Highcharts (function(){ if (typeof unsafeWindow.Highcharts == 'undefined') { console.log('NT_SeriesParticipation > Getting Highcharts'); var GM_Head = document.getElementsByTagName('head')[0] || document.documentElement, GM_JQ = document.createElement('script'); GM_JQ.src = 'https://code.highcharts.com/stock/2.1.10/highstock.js'; GM_JQ.type = 'text/javascript'; GM_JQ.async = true; GM_Head.insertBefore(GM_JQ, GM_Head.firstChild); } GM_wait(); })(); // Check if Highcharts is loaded function GM_wait() { if (typeof unsafeWindow.Highcharts == 'undefined') { window.setTimeout(GM_wait, 100); } else { //$ = unsafeWindow.jQuery.noConflict(true); // Add script to start var script = document.createElement("script"); script.textContent = "(" + NT_seriesParticipation.toString() + ")();"; document.body.appendChild(script); } } function NT_seriesParticipation() { var NT_debug = false; var NT_participationData = {}; var NT_averageData = {}; var NT_dataHeaders = null; var graphData = {}; var seriesId = 0; var tzOffset = 0; var graphType = 'AVG'; var NT_SP_options = { useGMT: {value: false, key: 'NT_useGMT', control: '#NT_chkUseGMT'}, useColors: {value: true, key: 'NT_useColors', control: '#NT_chkUseColors'}, useClassColors: {value: true, key: 'NT_useClassColors', control: '#NT_chkUseClassColors'} }; var NT_optionsShowing = false; // Logging function NT_log(msg, forceLog) { forceLog = forceLog || false; if ((NT_debug || forceLog) && typeof console !== 'undefined') { console.log('NT_SeriesParticipation > ' + msg); } } // CSS if (typeof GM_addStyle !== 'function') { GM_addStyle = function(css) { var style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); }; } function NT_loadOptions() { for (var name in NT_SP_options) { var opt = NT_SP_options[name]; var value = localStorage[opt.key] === 'true'; NT_SP_options[name].value = value; $(opt.control).attr('checked', value); NT_log('Set control ' + opt.control + ' to ' + value); } NT_log('Loaded options: ' + JSON.stringify(NT_SP_options)); } function NT_saveOptions() { NT_log('Saving options: ' + JSON.stringify(NT_SP_options)); for (var name in NT_SP_options) { var opt = NT_SP_options[name]; localStorage[opt.key] = opt.value; } } function NT_runSeriesParticipation() { seriesId = NT_getSeriesId(); NT_log('Season ID: ' + seasonId + ', Series ID: ' + seriesId); var date = new Date(); tzOffset = date.getTimezoneOffset(); if (seasonId && seasonId > 0) { // Create container NT_createContainer(); // Load options NT_loadOptions(); // Load data NT_getSeriesParticipationData(); } } function NT_getSeriesParticipationData() { // Reset data NT_participationData = {}; NT_averageData = {}; // Start processing week 0 NT_processWeek(0); } function NT_getDataHeaders(data) { if (NT_dataHeaders) return; NT_dataHeaders = {}; for (var key in data) { var name = data[key]; NT_dataHeaders[name] = key; } } function NT_processWeek(week) { NT_log('Start processing week ' + week); var url = '/memberstats/member/GetSeriesRaceResults?seasonid=' + seasonId + '&raceweek=' + week; $.getJSON(url, function(json) { if (!json.d || json.d.length === 0) { // No more data found, stop looking NT_log('No data found for week ' + week); $('#NT_loadingData').hide(); //NT_updateGraph(); return; } NT_log('Data found'); // Get header names NT_getDataHeaders(json.m); var sessionHeader = NT_dataHeaders.sessionid; var ssidHeader = NT_dataHeaders.subsessionid; var sofHeader = NT_dataHeaders.strengthoffield; var sizeHeader = NT_dataHeaders.sizeoffield; var trackHeader = NT_dataHeaders.trackid; var startHeader = NT_dataHeaders.start_time; var officialHeader = NT_dataHeaders.officialsession; var classHeader = NT_dataHeaders.carclassid; // Loop through data for (var i = 0; i < json.d.length; i++) { var result = json.d[i]; // Get/create class data var classId = result[classHeader]; var classData = NT_participationData[classId]; if (!classData) { classData = { name: getCarClassById(classId).shortname, sessions: {}, order: NT_getClassOrder(classId) }; } // Get/create session data var sessionId = result[sessionHeader]; var sessionData = classData.sessions[sessionId]; if (!sessionData) { sessionData = { hasSplits: false, drivers: 0, ssid: 0, sof: 0, week: week + 1 }; } else { sessionData.hasSplits = true; } // Add drivers var drivers = result[sizeHeader]; sessionData.drivers += drivers; sessionData.unofficial = result[officialHeader] === 0; // SOF / ssid: highest split only var sof = result[sofHeader]; var ssid = result[ssidHeader]; if (sof > sessionData.sof) { sessionData.sof = sof; sessionData.ssid = ssid; } // Date/time var start = result[startHeader]; if (!NT_SP_options.useGMT.value) { start = start - tzOffset * 60 * 1000; } sessionData.startDateTime = start; // Set data classData.sessions[sessionId] = sessionData; NT_participationData[classId] = classData; // --------------------------- // Normalize start date/time to one day of the week // 1970-01-06 is the first tuesday in 1970 // Tue: 1970-01-06 // Wed: 1970-01-07 // Thu: 1970-01-08 // Mon: 1970-01-12 // getDay: Sun = 0, Mon = 1, ... // Required day: getDay + 4 var date = new Date(start); var day = date.getDay() + 4; // shift sun and mon to next week because tue is raceweek start if (day < 6) day += 7; var normalizedStart = new Date(1970, 0, day, date.getHours(), date.getMinutes(), date.getSeconds()).getTime(); if (!NT_averageData[normalizedStart]) NT_averageData[normalizedStart] = { date: normalizedStart, classes: {}, sessions: {}, sessionCount: 0, drivers: 0, sof: 0}; // Do not count splits as separate sessions (we want total drivers, not drivers per split) if (!NT_averageData[normalizedStart].sessions[sessionId]) { // this session was not present yet, new session NT_averageData[normalizedStart].sessions[sessionId] = true; NT_averageData[normalizedStart].sessionCount += 1; } NT_averageData[normalizedStart].drivers += drivers; NT_averageData[normalizedStart].sof += sof; } // Update view NT_updateData(); // Continue with next week NT_processWeek(week + 1); }); } function NT_updateData() { NT_log('Start updating data'); graphData = {}; // Create data for graph graphData.seasonSeries = []; var order = 0; var sortedClassIds = []; if (!NT_multiclasses[seriesId]) { // Just copy over order for (var classId in NT_participationData) { sortedClassIds.push(classId); } } else { // Sort by custom defined order var sortlist = []; for (var classId in NT_participationData) { sortlist.push([classId, NT_participationData[classId].order]); } sortlist.sort(function(a,b){ return a[1] - b[1]; }); for (var i = 0; i < sortlist.length; i++) { sortedClassIds.push(sortlist[i][0]); } } for (var i = 0; i < sortedClassIds.length; i++) { var classId = sortedClassIds[i]; if (classId === null || !NT_participationData.hasOwnProperty(classId)) continue; var classData = NT_participationData[classId]; if (classData === null) continue; var classSeries = { name: classData.name, data: [], stack: 'drivers' }; // Is multiclass? if (sortedClassIds.length > 1) classSeries.color = NT_getClassColor(classId, order); NT_log('Class ' + classId + ': ' + classData.name); for (var sessionId in classData.sessions) { if (sessionId === null) continue; var session = classData.sessions[sessionId]; if (session === null) continue; NT_log('Start making point: ' + JSON.stringify(session)); var point = { x: session.startDateTime, y: session.drivers, week: session.week, ssid: session.ssid, sof: session.sof }; NT_log('Point: ' + JSON.stringify(point)); if (NT_SP_options.useColors.value) { if (session.unofficial) point.color = '#ffb4a9'; } classSeries.data.push(point); } order += 1; graphData.seasonSeries.push(classSeries); } //// Add sof series //graphData.seasonSeries.push({ // name: 'SOF', // data: sofData, // yAxis: 1, // color: '#aaa', // pointPadding: 0.45, // stack: 'sof' //}); // Averaged data graphData.avgDriverData = []; graphData.avgSofData = []; for (var date in NT_averageData) { if (date == null) continue; if (!NT_averageData.hasOwnProperty(date)) continue; var result = NT_averageData[date]; var count = result.sessionCount; graphData.avgDriverData.push([result.date, Math.round(result.drivers / count)]); graphData.avgSofData.push([result.date, Math.round(result.sof / count)]); } NT_log('Updated data complete'); NT_updateGraph(); } function NT_updateGraph() { NT_log('Updating graph'); if (graphType === 'AVG') { NT_createWeekGraph(); } else { NT_createSeasonGraph(); } } function NT_createWeekGraph() { $('#NT_participation_graph').highcharts('StockChart', { chart: { type: 'column', zoomType: 'x' }, rangeSelector: { buttons: [{ type: 'day', count: 1, text: 'day' }, { type: 'all', text: 'week' }], selected: 1, inputEnabled: false }, navigator: { xAxis: { dateTimeLabelFormats: { month: '%a', week: '%a', day: '%a', hour: '%a %H:%M', minute: '%a %H:%M' } } }, title: { text: 'Averaged Series Participation' }, xAxis: { type: 'datetime', ordinal: false, dateTimeLabelFormats: { month: '%a', week: '%a', day: '%a', hour: '%a %H:%M', minute: '%a %H:%M' }, title: { text: 'Day' } }, yAxis: { title: { text: 'Drivers' }, min: 0, opposite: false }, //yAxis: [{ // title: { // text: 'Drivers' // }, // min: 0, // opposite: false //}, { // title: { // text: 'SOF', // style: { // color: '#777' // } // }, // labels: { // style: { // color: '#777' // } // }, // min: 0, // opposite: true //}], tooltip: { formatter: function() { var s = []; s.push(Highcharts.dateFormat('%A %H:%M', new Date(this.x))); $.each(this.points, function(i, point) { s.push('<span style="color:' + point.series.color + '">\u25CF</span> ' + point.series.name + ': ' + point.y); }); return s.join('<br />'); }, shared: true }, plotOptions: { column: { grouping: false }, series: { animation: false, turboThreshold: 10000, cursor: 'pointer', dataGrouping: { enabled: false } } }, series: [{ name: 'Drivers', data: graphData.avgDriverData }] // { // name: 'SOF', // data: graphData.avgSofData, // yAxis: 1, // color: '#777', // pointPadding: 0.45 //}] }); } function NT_createSeasonGraph() { $('#NT_participation_graph').highcharts('StockChart', { chart: { type: 'column', zoomType: 'x' }, rangeSelector: { buttons: [{ type: 'day', count: 1, text: 'day' }, { type: 'week', count: 1, text: 'week' }, { type: 'all', text: 'all' }], selected: 1 }, title: { text: 'Series Participation' }, xAxis: { type: 'datetime', ordinal: false, dateTimeLabelFormats: { month: '%e. %b', year: '%b', day: '%a', hour: '%a %H:%M', minute: '%a %H:%M' }, title: { text: 'Date' } }, //yAxis: [{ // title: { // text: 'Drivers' // }, // min: 0, // opposite: false //}, { // title: { // text: 'SOF', // style: { // color: '#777' // } // }, // labels: { // style: { // color: '#777' // } // }, // min: 0, // opposite: true //}], yAxis: { title: { text: 'Drivers' }, min: 0, opposite: false }, tooltip: { formatter: function() { var week = '?'; var s = []; $.each(this.points, function(i, p) { if (week === '?') week = p.point.week; s.push('<span style="color:' + p.series.color + '">\u25CF</span> ' + p.series.name + ': ' + p.y); }); var date = new Date(this.x); var dateString = Highcharts.dateFormat('%a', date) + ' W' + week + ', ' + Highcharts.dateFormat('%H:%M', date); return dateString + '<br />' + s.join('<br />'); }, shared: true }, plotOptions: { column: { stacking: 'normal' }, series: { animation: false, turboThreshold: 10000, cursor: 'pointer', dataGrouping: { enabled: false }, point: { events: { click: function() { var ssid = this.ssid; window.open('http://members.iracing.com/membersite/member/EventResult.do?&subsessionid=' + ssid); } } } } }, series: graphData.seasonSeries }); } function NT_createContainer() { $("<div id='NT_participation_container'>" + "<div><button id='NT_btnAverageGraph' type='button'>Averaged</button>" + "<button id='NT_btnSeasonGraph' type='button'>Season</button>" + "<button id='NT_btnOptions' type='button'>Show options</button>" + "<span id='NT_loadingData'>Loading...</span></div>" + "<div id='NT_optionsPanel'>" + "<input id='NT_chkUseGMT' type='checkbox' /><label for='NT_chkUseGMT'>Times in GMT</label><br />" + "<input id='NT_chkUseColors' type='checkbox' /><label for='NT_chkUseColors'>Highlight unofficial races</label><br />" + //"<input id='NT_chkUseClassColors' type='checkbox' /><label for='NT_chkUseClassColors'>Show multi-class participation</label>" + "</div>" + "<div><div id='NT_participation_graph'></div></div></div>").insertAfter('.serieshome_info'); $('#NT_btnAverageGraph').click(function() { NT_toggleGraph(true); }); $('#NT_btnSeasonGraph').click(function() { NT_toggleGraph(false); }); $('#NT_btnOptions').click(function() { NT_toggleOptions(); }); $('#NT_chkUseGMT').change(function() { NT_SP_options.useGMT.value = $(this).is(':checked'); NT_saveOptions(); NT_getSeriesParticipationData(); }); $('#NT_chkUseColors').change(function() { NT_SP_options.useColors.value = $(this).is(':checked'); NT_saveOptions(); NT_updateData(); }); $('#NT_chkUseClassColors').change(function() { NT_SP_options.useClassColors.value = $(this).is(':checked'); NT_saveOptions(); NT_updateData(); }); $('#NT_optionsPanel').hide(); NT_optionsShowing = false; NT_log('Container created'); } function NT_toggleGraph(average) { if (average) { graphType = 'AVG'; } else { graphType = 'SEASON'; } NT_updateGraph(); } function NT_toggleOptions() { if (NT_optionsShowing) { $('#NT_btnOptions').text('Show options'); $('#NT_optionsPanel').hide(); NT_optionsShowing = false; } else { $('#NT_btnOptions').text('Hide options'); $('#NT_optionsPanel').show(); NT_optionsShowing = true; } } function NT_getSeriesId() { return getSeasonById(seasonId).seriesid; //already exists on page } var NT_defaultColors = ['#7cb5ec', '#90ed7d', '#f7a35c', '#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1']; var NT_classColors = ['#ffda59', '#33ceff', '#ff5888', '#ae6bff', '#53ff77']; var NT_multiclasses = { '227': { name: 'WSCS', classes: { '40': { order: 0, car: 'HPD' }, '18': { order: 1, car: 'Riley DP' }, '41': { order: 2, car: 'Ford GT' }, '58': { order: 3, car: 'RUF' } } } }; function NT_getClassColor(classId, order) { // Is there a series override defined? var series = NT_multiclasses[seriesId]; if (series) { if (series.classes[classId]) { return NT_classColors[series.classes[classId].order]; } } return NT_classColors[order]; } function NT_getClassOrder(classId) { var series = NT_multiclasses[seriesId]; if (series) { if (series.classes[classId]) { return series.classes[classId].order; } } return 0; } $(document).ready(function() { NT_log('Starting'); // CSS GM_addStyle('#NT_participation_container{padding:5px;height:455px;} ' + '#NT_participation_graph{height:450px;} ' + '#NT_loadingData {margin-left: 10px;} ' + '#NT_btnOptions {margin-left: 10px;} ' + '#NT_optionsPanel {margin-top: 5px;}'); NT_log('Highcharts: ' + Highcharts.version); NT_runSeriesParticipation(); }); }