User:Isaac (WMF)/clickstream viz.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Isaac (WMF)/clickstream viz. |
// <pre> Avoid triggering: https://en.wikipedia.org/wiki/Special:WantedTemplates
// Code largely borrowed from: https://en.wikipedia.org/wiki/User:TayIorRobinson/wikigender.js
/**
* @typedef {{title: string, views: number}} ClickstreamLink
* @typedef {{limit: string, month: string, next: string, results: ClickstreamLink[]}} ClickstreamData
*/
// </pre>
(async() => {
// https://wikinav.wmcloud.org/api/v1/en/Chocolate/destinations/latest?limit=10
const CLICKSTREAM_API = "https://wikinav.wmcloud.org/api/v1/";
async function fetchClickstreamLangs() {
let response = await fetch("https://wikinav.wmcloud.org/api/v1/latest/meta");
let data = await response.json();
return data.languages;
}
/**
* Requests the clickstream data for the current page.
* @param {String} lang Wikipedia language to query.
* @returns {Promise<ClickstreamData>} The latest clickstream data for the page.
*/
async function fetchData(lang) {
let title = mw.config.get("wgPageName");
let response = await fetch(CLICKSTREAM_API + lang + "/" + title + "/destinations/latest?limit=1000");
let data = await response.json();
return data;
}
async function fetchRedirects() {
let redirects = {};
let linksToRedirects = document.querySelectorAll("a.mw-redirect");
if (linksToRedirects.length > 0) {
let joinedTitles = "";
let titlesInQuery = new Set();
for (let i = 0; i < linksToRedirects.length; i++) {
if (titlesInQuery.has(linksToRedirects[i].title)) continue;
titlesInQuery.add(linksToRedirects[i].title);
joinedTitles += linksToRedirects[i].title + "|";
// Process first 50 (single API call max)
if (titlesInQuery.size == 50) break;
}
// remove final | character
joinedTitles = joinedTitles.slice(0, -1);
let lang = mw.config.get("wgPageContentLanguage");
let response = await fetch("https://" + lang + ".wikipedia.org/w/api.php?action=query&format=json&formatversion=2&redirects&titles=" + joinedTitles);
let data = await response.json();
for (let rd of data.query.redirects) {
redirects[rd.from.toLowerCase().replace(/_/g, " ")] = rd.to.toLowerCase().replace(/_/g, " ");
}
}
return redirects;
}
/**
* Requests the clickstream data for the current page.
* @param {Number} num_views Number of clicks to that link.
* @param {Number} max_views Maximum number of clicks to any link.
* @returns {String} Alpha value as string based on proportion of pageviews via link. [0.2 - 1]
*/
function propPageviewsToAlpha(num_views, max_views) {
if (num_views == 0) {
return '0';
} else {
let alpha = 0.1 + (0.9 * num_views / max_views);
return alpha.toFixed(2);
}
}
/**
* Applies colour to links on the page
* @param {ClickstreamLink[]} links The links to colour.
*/
function applyLinkColours(links, redirects) {
let pageLinks = document.querySelector("#bodyContent").querySelectorAll("a[href^=\"/wiki/\"]");
let max_views = 0;
// Create an object of all the links on the page.
let linksOnPage = {};
for (let link of pageLinks) {
let linkText = decodeURIComponent(link.href.split("/")[4].toLowerCase().replace(/_/g, " "));
// map redirects to canonical title (used by clickstream)
if (linkText in redirects) {
linkText = redirects[linkText];
}
// Build array of all instances of a link appearing on page
if (!(linkText in linksOnPage)) {
linksOnPage[linkText] = [];
}
linksOnPage[linkText].push(link);
}
for (let link of links) {
max_views = Math.max(max_views, link.views);
}
// Colour the links.
for (let link of links) {
let linkOnPage = linksOnPage[link.title.toLowerCase().replace(/_/g, " ")];
if (!linkOnPage) continue;
let alpha = propPageviewsToAlpha(link.views, max_views);
for (let l of linkOnPage) {
l.style.backgroundColor = "rgba(216,127,165," + alpha + ")";
}
}
}
// article but not Main Page (links change too frequently to be relevant to data)
if (mw.config.get('wgNamespaceNumber') == 0 && mw.config.get('wgWikibaseItemId') != 'Q5296') {
let lang = mw.config.get("wgDBname").replace("wiki","");
let csLangs = await fetchClickstreamLangs();
if (csLangs.includes(lang)) {
let mwIndicator = document.createElement("div");
mwIndicator.className = "mw-indicator";
mwIndicator.id = "mw-indicator-clickstream";
document.querySelector(".mw-indicators").appendChild(mwIndicator);
let link = document.createElement("a");
link.href = "javascript:void(0)";
link.onclick = async() => {
link.remove();
let data = await fetchData(lang);
let redirects = await fetchRedirects();
applyLinkColours(data.results, redirects);
};
link.title = "Visualize the clicks to the links on the page";
mwIndicator.appendChild(link);
let img = document.createElement("img");
img.alt = "Data analytics icon";
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Cs%2B.svg/41px-Cs%2B.svg.png";
img.decoding = "async";
img.height = 20;
img.width = 20;
link.appendChild(img);
}
}
})();