// https://apexcharts.com/javascript-chart-demos/line-charts/zoomable-timeseries/ var options = { series: [], chart: { type: 'area', stacked: false, height: 490, zoom: { type: 'x', enabled: true, autoScaleYaxis: true }, // background: '#2B2D3E', foreColor: '#fff' }, dataLabels: { enabled: false }, stroke: { curve: 'smooth', }, markers: { size: 0, }, title: { text: 'Channel points (dates are displayed in UTC)', align: 'left' }, colors: ["#f9826c"], fill: { type: 'gradient', gradient: { shadeIntensity: 1, inverseColors: false, opacityFrom: 0.5, opacityTo: 0, stops: [0, 90, 100] }, }, yaxis: { title: { text: 'Channel points' }, }, xaxis: { type: 'datetime', labels: { datetimeUTC: false } }, tooltip: { theme: 'dark', shared: false, x: { show: true, format: 'HH:mm:ss dd MMM', }, custom: ({ series, seriesIndex, dataPointIndex, w }) => { return (`
${w.globals.seriesNames[seriesIndex]}
Points: ${series[seriesIndex][dataPointIndex]}
Reason: ${w.globals.seriesZ[seriesIndex][dataPointIndex] ? w.globals.seriesZ[seriesIndex][dataPointIndex] : ''}
`) } }, noData: { text: 'Loading...' } }; var chart = new ApexCharts(document.querySelector("#chart"), options); var currentStreamer = null; var annotations = []; var streamersList = []; var sortBy = "Name ascending"; var sortField = 'name'; var startDate = new Date(); startDate.setDate(startDate.getDate() - daysAgo); var endDate = new Date(); $(document).ready(function () { // Variable to keep track of whether log checkbox is checked var isLogCheckboxChecked = $('#log').prop('checked'); // Variable to keep track of whether auto-update log is active var autoUpdateLog = true; // Variable to keep track of the last received log index var lastReceivedLogIndex = 0; $('#auto-update-log').click(() => { autoUpdateLog = !autoUpdateLog; $('#auto-update-log').text(autoUpdateLog ? '⏸️' : '▶️'); if (autoUpdateLog) { getLog(); } }); // Function to get the full log content function getLog() { if (isLogCheckboxChecked) { $.get(`/log?lastIndex=${lastReceivedLogIndex}`, function (data) { // Process and display the new log entries received $("#log-content").append(data); // Scroll to the bottom of the log content $("#log-content").scrollTop($("#log-content")[0].scrollHeight); // Update the last received log index lastReceivedLogIndex += data.length; if (autoUpdateLog) { // Call getLog() again after a certain interval (e.g., 1 second) setTimeout(getLog, 1000); } }); } } // Retrieve the saved header visibility preference from localStorage var headerVisibility = localStorage.getItem('headerVisibility'); // Set the initial header visibility based on the saved preference or default to 'visible' if (headerVisibility === 'hidden') { $('#toggle-header').prop('checked', false); $('#header').hide(); } else { $('#toggle-header').prop('checked', true); $('#header').show(); } // Handle the toggle header change event $('#toggle-header').change(function () { if (this.checked) { $('#header').show(); // Save the header visibility preference as 'visible' in localStorage localStorage.setItem('headerVisibility', 'visible'); } else { $('#header').hide(); // Save the header visibility preference as 'hidden' in localStorage localStorage.setItem('headerVisibility', 'hidden'); } }); chart.render(); if (!localStorage.getItem("annotations")) localStorage.setItem("annotations", true); if (!localStorage.getItem("dark-mode")) localStorage.setItem("dark-mode", true); if (!localStorage.getItem("sort-by")) localStorage.setItem("sort-by", "Name ascending"); // Restore settings from localStorage on page load $('#annotations').prop("checked", localStorage.getItem("annotations") === "true"); $('#dark-mode').prop("checked", localStorage.getItem("dark-mode") === "true"); // Handle the annotation toggle click event $('#annotations').click(() => { var isChecked = $('#annotations').prop("checked"); localStorage.setItem("annotations", isChecked); updateAnnotations(); }); // Handle the dark mode toggle click event $('#dark-mode').click(() => { var isChecked = $('#dark-mode').prop("checked"); localStorage.setItem("dark-mode", isChecked); toggleDarkMode(); }); $('#startDate').val(formatDate(startDate)); $('#endDate').val(formatDate(endDate)); sortBy = localStorage.getItem("sort-by"); if (sortBy.includes("Points")) sortField = 'points'; else if (sortBy.includes("Last activity")) sortField = 'last_activity'; else sortField = 'name'; $('#sorting-by').text(sortBy); getStreamers(); updateAnnotations(); toggleDarkMode(); // Retrieve log checkbox state from localStorage and update UI accordingly var logCheckboxState = localStorage.getItem('logCheckboxState'); $('#log').prop('checked', logCheckboxState === 'true'); if (logCheckboxState === 'true') { isLogCheckboxChecked = true; $('#auto-update-log').show(); $('#log-box').show(); // Start continuously updating the log content getLog(); } // Handle the log checkbox change event $('#log').change(function () { isLogCheckboxChecked = $(this).prop('checked'); localStorage.setItem('logCheckboxState', isLogCheckboxChecked); if (isLogCheckboxChecked) { $('#log-box').show(); $('#auto-update-log').show(); getLog(); $('html, body').scrollTop($(document).height()); } else { $('#log-box').hide(); $('#auto-update-log').hide(); // Clear log content when checkbox is unchecked // $("#log-content").text(''); } }); }); function formatDate(date) { var d = new Date(date), month = '' + (d.getMonth() + 1), day = '' + d.getDate(), year = d.getFullYear(); if (month.length < 2) month = '0' + month; if (day.length < 2) day = '0' + day; return [year, month, day].join('-'); } function changeStreamer(streamer, index) { $("li").removeClass("is-active") $("li").eq(index - 1).addClass('is-active'); currentStreamer = streamer; // Update the chart title with the current streamer's name options.title.text = `${streamer.replace(".json", "")}'s channel points (dates are displayed in UTC)`; chart.updateOptions(options); // Save the selected streamer in localStorage localStorage.setItem("selectedStreamer", currentStreamer); getStreamerData(streamer); } function getStreamerData(streamer) { if (currentStreamer == streamer) { $.getJSON(`./json/${streamer}`, { startDate: formatDate(startDate), endDate: formatDate(endDate) }, function (response) { chart.updateSeries([{ name: streamer.replace(".json", ""), data: response["series"] }], true) clearAnnotations(); annotations = response["annotations"]; updateAnnotations(); setTimeout(function () { getStreamerData(streamer); }, 300000); // 5 minutes }); } } function getAllStreamersData() { $.getJSON(`./json_all`, function (response) { for (var i in response) { chart.appendSeries({ name: response[i]["name"].replace(".json", ""), data: response[i]["data"]["series"] }, true) } }); } function getStreamers() { $.getJSON('streamers', function (response) { streamersList = response; sortStreamers(); // Restore the selected streamer from localStorage on page load var selectedStreamer = localStorage.getItem("selectedStreamer"); if (selectedStreamer) { currentStreamer = selectedStreamer; } else { // If no selected streamer is found, default to the first streamer in the list currentStreamer = streamersList.length > 0 ? streamersList[0].name : null; } // Ensure the selected streamer is still active and scrolled into view renderStreamers(); }); } function renderStreamers() { $("#streamers-list").empty(); var promised = new Promise((resolve, reject) => { streamersList.forEach((streamer, index, array) => { displayname = streamer.name.replace(".json", ""); if (sortField == 'points') displayname = "" + streamer['points'] + " " + displayname; else if (sortField == 'last_activity') displayname = "" + formatDate(streamer['last_activity']) + " " + displayname; var isActive = currentStreamer === streamer.name; if (!isActive && localStorage.getItem("selectedStreamer") === null && index === 0) { isActive = true; currentStreamer = streamer.name; } var activeClass = isActive ? 'is-active' : ''; var listItem = `
  • ${displayname}
  • `; $("#streamers-list").append(listItem); if (isActive) { // Scroll the selected streamer into view document.getElementById(`streamer-${streamer.name}`).scrollIntoView({ behavior: 'smooth', block: 'center' }); } if (index === array.length - 1) resolve(); }); }); promised.then(() => { changeStreamer(currentStreamer, streamersList.findIndex(streamer => streamer.name === currentStreamer) + 1); }); } function sortStreamers() { streamersList = streamersList.sort((a, b) => { return (a[sortField] > b[sortField] ? 1 : -1) * (sortBy.includes("ascending") ? 1 : -1); }); } function changeSortBy(option) { sortBy = option.innerText.trim(); if (sortBy.includes("Points")) sortField = 'points' else if (sortBy.includes("Last activity")) sortField = 'last_activity' else sortField = 'name'; sortStreamers(); renderStreamers(); $('#sorting-by').text(sortBy); localStorage.setItem("sort-by", sortBy); } function updateAnnotations() { if ($('#annotations').prop("checked") === true) { clearAnnotations() if (annotations && annotations.length > 0) annotations.forEach((annotation, index) => { annotations[index]['id'] = `id-${index}` chart.addXaxisAnnotation(annotation, true) }) } else clearAnnotations() } function clearAnnotations() { if (annotations && annotations.length > 0) annotations.forEach((annotation, index) => { chart.removeAnnotation(annotation['id']) }) chart.clearAnnotations(); } // Toggle $('#annotations').click(() => { updateAnnotations(); }); $('#dark-mode').click(() => { toggleDarkMode(); }); $('.dropdown').click(() => { $('.dropdown').toggleClass('is-active'); }); // Input date $('#startDate').change(() => { startDate = new Date($('#startDate').val()); getStreamerData(currentStreamer); }); $('#endDate').change(() => { endDate = new Date($('#endDate').val()); getStreamerData(currentStreamer); });