// ==UserScript==
// @name KDE Store: Graphs
// @namespace https://github.com/Zren/
// @description Misc
// @icon https://store.kde.org/images_sys/store_logo/kde-store.ico
// @author Zren
// @version 5
// @match https://www.opendesktop.org/member/*/plings*
// @match https://www.opendesktop.org/u/*/plings*
// @match https://store.kde.org/member/*/plings*
// @match https://store.kde.org/u/*/plings*
// @grant none
// @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js
// ==/UserScript==
var el = function(html) {
var e = document.createElement('div');
e.innerHTML = html;
return e.removeChild(e.firstChild);
}
function daysLeftMultiplier() {
var now = new Date()
var startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
var endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
var totalTime = endOfMonth.valueOf() - startOfMonth.valueOf()
var timeProcessed = now.valueOf() - startOfMonth.valueOf()
var timeLeft = endOfMonth.valueOf() - now.valueOf()
if (timeProcessed == 0) {
return 1
} else {
return 1 + (timeLeft / timeProcessed)
}
//var timeLeftInMonth = timeLeft / totalTime
// return 1 + timeLeftInMonth
if (timeLeftInMonth <= 0) {
return 1
} else {
return totalTime / timeLeft
}
}
function getProductDownloadsOverTime() {
var currentMonthMultiplier = daysLeftMultiplier()
var projectCurrentMonth = false
var graphData = {}
var tabs = document.querySelectorAll('.tab-content .tab-pane')
for (var i = 0; i < tabs.length; i++) {
var tabPane = tabs[i]
for (var row of tabPane.querySelectorAll('.row:not(.row-total')) {
var productName = row.children[1].querySelector('span').textContent
var productDownloads = row.children[2].querySelector('span').textContent
//console.log('graphData', productName, productDownloads, parseInt(productDownloads, 10))
productDownloads = parseInt(productDownloads, 10)
if (i == 0 && projectCurrentMonth) { // Current Month
productDownloads = Math.round(productDownloads * currentMonthMultiplier)
}
var productData = graphData[productName]
if (!graphData[productName]) {
productData = new Array(tabs.length).fill(0)
/*
for (var j = 0; j < i; j++) {
productData[j] = null
}
*/
graphData[productName] = productData
}
productData[i] = productDownloads
}
}
return graphData
}
function randomColor() {
// Based on the Random Pastel code from StackOverflow
// https://stackoverflow.com/a/43195379/947742
return "hsl(" + 360 * Math.random() + ', ' + // Hue: Any
(25 + 70 * Math.random()) + '%, ' + // Saturation: 25-95
(40 + 30 * Math.random()) + '%)'; // Lightness: 40-70
}
// https://stackoverflow.com/a/44134328/947742
function hslToRgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
var r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { r:r, g:g, b:b }
}
function rgbToHex(c) {
function toHex(x) {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
return '#' + toHex(c.r) + toHex(c.g) + toHex(c.b)
}
function hslToHex(h, s, l) {
var c = hslToRgb(h, s, l)
function toHex(x) {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
return '#' + toHex(c.r) + toHex(c.g) + toHex(c.b)
}
function rgbToRgba(c, a) {
return 'rgba(' + Math.round(c.r * 255) + ', ' + Math.round(c.g * 255) + ', ' + Math.round(c.b * 255) + ', ' + a + ')'
}
function getPastelColor(i, n) {
return hslToRgb(i / n * 360, 55, 70)
}
function convertToDatasets(graphData) {
var datasets = []
var products = Object.keys(graphData)
products.sort()
for (var i = 0; i < products.length; i++) {
var productName = products[i]
var productData = graphData[productName]
var dataset = {}
dataset.label = productName
dataset.data = Array.from(productData).reverse()
dataset.fill = false
var datasetColor = getPastelColor(i, products.length)
dataset.accentColor = rgbToHex(datasetColor)
dataset.accentFadedColor = rgbToRgba(datasetColor, 0.2)
dataset.backgroundColor = dataset.accentColor
dataset.borderColor = dataset.accentColor
dataset.lineTension = 0.1
datasets.push(dataset)
}
return datasets
}
var graphData = window.graphData = getProductDownloadsOverTime()
var datasets = window.datasets = convertToDatasets(graphData)
var labels = document.querySelectorAll('#my-payout-list ul.nav-tabs li a')
labels = Array.prototype.map.call(labels, function(e){ return e.textContent })
labels = labels.reverse()
console.log('datasets', JSON.stringify(datasets))
console.log('labels', JSON.stringify(labels))
var tabContent = document.querySelector('.tab-content')
var graphTabPane = el('<div class="tab-pane fade" id="graphs" />')
var graphCanvas = el('<canvas id="myChart" width="100vw" height="30vh"></canvas>')
graphTabPane.appendChild(graphCanvas)
var warningAlert = tabContent.querySelector('#le-alert')
tabContent.insertBefore(graphTabPane, warningAlert)
var navTabs = document.querySelector('#my-payout-list ul.nav-tabs')
var graphTab = el('<li><a href="#graphs" data-toggle="tab">Graphs</a></li>')
navTabs.insertBefore(graphTab, navTabs.firstChild)
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = window.myChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets,
},
options: {
title: {
display: true,
text: 'Product Downloads Over Time',
},
tooltips: {
mode: 'index',
intersect: false,
itemSort: function (a, b, data) {
return b.yLabel - a.yLabel // descending
}
},
legend: {
onHover: function(e, legendItem) {
if (myChart.hoveringLegendIndex != legendItem.datasetIndex) {
myChart.hoveringLegendIndex = legendItem.datasetIndex
for (var i = 0; i < myChart.data.datasets.length; i++) {
var dataset = myChart.data.datasets[i]
if (i == legendItem.datasetIndex) {
dataset.borderColor = dataset.accentColor
dataset.pointBackgroundColor = dataset.accentColor
} else {
dataset.borderColor = dataset.accentFadedColor
dataset.pointBackgroundColor = dataset.accentFadedColor
}
}
myChart.options.tooltips.enabled = false
myChart.update()
}
}
},
hover: {
mode: 'nearest',
intersect: true,
},
scales: {
yAxes: [{
//type: 'logarithmic',
ticks: {
//stepSize: 5,
//beginAtZero:true,
}
}]
}
}
});
myChart.hoveringLegendIndex = -1
myChart.canvas.addEventListener('mousemove', function(e) {
if (myChart.hoveringLegendIndex >= 0) {
if (e.layerX < myChart.legend.left || myChart.legend.right < e.layerX
|| e.layerY < myChart.legend.top || myChart.legend.bottom < e.layerY
) {
myChart.hoveringLegendIndex = -1
for (var i = 0; i < myChart.data.datasets.length; i++) {
var dataset = myChart.data.datasets[i]
dataset.borderColor = dataset.accentColor
dataset.pointBackgroundColor = dataset.accentColor
}
myChart.options.tooltips.enabled = true
myChart.update()
}
}
})