您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Visualize Wikipedia Articles that you've visited in an interactive graph
// ==UserScript== // @name Wikipedia History and History Visualizer // @namespace https://en.wikipedia.org/wiki/* // @description Visualize Wikipedia Articles that you've visited in an interactive graph // @author Sidem // @version 0.1 // @match https://en.wikipedia.org/wiki/* // @grant none // @license GPL-3.0-only // ==/UserScript== const dbName = "WikipediaHistoryDB"; const objectStoreName = "wikipediaHistory"; function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(dbName, 1); request.onupgradeneeded = (event) => { const db = event.target.result; db.createObjectStore(objectStoreName, { keyPath: "id", autoIncrement: true }); }; request.onsuccess = (event) => resolve(event.target.result); request.onerror = (event) => reject(event.target.error); }); } async function getWikipediaHistoryData() { const db = await openDatabase(); const transaction = db.transaction(objectStoreName, "readonly"); const objectStore = transaction.objectStore(objectStoreName); const request = objectStore.getAll(); return new Promise((resolve, reject) => { request.onsuccess = (event) => resolve(event.target.result); request.onerror = (event) => reject(event.target.error); }); } async function setWikipediaHistoryData(data) { const db = await openDatabase(); const transaction = db.transaction(objectStoreName, "readwrite"); const objectStore = transaction.objectStore(objectStoreName); const request = objectStore.clear(); request.onsuccess = () => { if (data && data.length) { // Add this condition for (const item of data) { objectStore.add(item); } } }; return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = (event) => reject(event.target.error); }); } const linkToTitle = (link) => { return link.split('#')[0].split('/wiki/')[1] }; const createHistoryButton = () => { const button = document.createElement('button'); button.innerText = 'View History Graph'; button.style.position = 'fixed'; button.style.top = '10px'; button.style.right = '10px'; button.style.zIndex = 1000; button.onclick = () => { window.location = '/wiki/History_visualization_script'; }; document.body.appendChild(button); }; let tooltipTimer; const addStyles = () => { const style = document.createElement("style"); style.innerHTML = ` svg { font-family: sans-serif; overflow: visible; } circle { stroke: #fff; stroke-width: 1.5px; } line { stroke: #999; stroke-opacity: 0.6; stroke-width: 2; } text { /*make unselectable*/ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; pointer-events: none; } body { overflow-y: hidden !important; overflow-x: hidden !important; } `; document.head.appendChild(style); }; const forceCluster = () => { const strength = 0.3; const clusters = new Map(); let nodes; function force(alpha) { for (const node of nodes) { const cluster = clusters.get(node.clusterId); if (!cluster) continue; let { x: cx, y: cy } = cluster; node.vx -= (node.x - cx) * strength * alpha; node.vy -= (node.y - cy) * strength * alpha; } } force.initialize = (_) => (nodes = _); force.clusters = (_) => { clusters.clear(); for (const node of _) { clusters.set(node.clusterId, node); } return force; }; return force; }; function drag(simulation) { function dragstarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(event, d) { d.fx = event.x; d.fy = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } return d3 .drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended); } const clearGraph = () => { d3.select("#graphContainer").remove(); }; let isMouseOverNode = false; const createGraph = (wikipediaHistoryData) => { const deleteEntry = (id) => { const updatedHistoryData = wikipediaHistoryData.filter((_, index) => index !== id); localStorage.setItem('wikipediaHistory', JSON.stringify(updatedHistoryData)); clearGraph(); createGraph(updatedHistoryData); }; const links = wikipediaHistoryData.flatMap((entry, index) => entry.links.map((link) => { const targetIndex = wikipediaHistoryData.findIndex((e) => e.link === link.link); return targetIndex !== -1 ? { source: index, target: targetIndex } : null; }) ).filter((link) => link !== null); const nodes = wikipediaHistoryData.map((entry, index) => { const connectedNodes = links.filter(link => link.source === index || link.target === index); const clusterId = connectedNodes.length > 0 ? index : null; return { id: index, label: entry.title, clusterId }; }); const svg = d3.select("#graph"); const width = +svg.attr("width"); const height = +svg.attr("height"); const g = svg.append("g").attr("id", "graphContainer"); svg.append("defs") .append("marker") .attr("id", "arrowhead") .attr("viewBox", "-0 -5 10 10") .attr("refX", 13) .attr("refY", 0) .attr("orient", "auto") .attr("markerWidth", 10) .attr("markerHeight", 10) .attr("xoverflow", "visible") .append("svg:path") .attr("d", "M 0,-5 L 10 ,0 L 0,5") .attr("fill", "#999") .style("stroke", "none"); const simulation = d3 .forceSimulation(nodes) .force("link", d3.forceLink(links).id((d) => d.id).distance(50)) .force("charge", d3.forceManyBody().strength(-200)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collide", d3.forceCollide(60)) .force("cluster", forceCluster().clusters(nodes)); // Add cluster force const link = g .selectAll("line") .data(links) .join("line") .attr("stroke", "#999") .attr("stroke-opacity", 0.6) .attr("stroke-width", 2) .attr("marker-end", (d) => { const sourceLinks = wikipediaHistoryData[d.source.index].links; const targetLinks = wikipediaHistoryData[d.target.index].links; const sourceToTarget = sourceLinks.some((link) => link.link === wikipediaHistoryData[d.target.index].link); const targetToSource = targetLinks.some((link) => link.link === wikipediaHistoryData[d.source.index].link); return sourceToTarget && !targetToSource ? "url(#arrowhead)" : ""; }); const node = g .selectAll("circle") .data(nodes) .join("circle") .attr("r", 5) .attr("fill", "#69b3a2"); node.on("dblclick", (event, d) => { window.open(`https://en.wikipedia.org/wiki/${wikipediaHistoryData[d.id].link}`, "_blank"); }); const tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0) .style("background-color", "white") .style("border", "solid") .style("border-width", "1px") .style("border-radius", "5px") .style("padding", "10px") .style("position", "absolute") .style("pointer-events", "auto"); node.on("mouseover", (event, d) => { isMouseOverNode = true; tooltip.transition() .duration(200) .style("opacity", 1); tooltip.html(`Title: ${wikipediaHistoryData[d.id].title}<br/>URL: https://en.wikipedia.org/wiki/${wikipediaHistoryData[d.id].link}<br/>Dates Accessed: ${wikipediaHistoryData[d.id].dates_accessed.join(', ')}<br/><button id="deleteButton">Delete</button>`) .style("left", `${event.pageX + 10}px`) .style("top", `${event.pageY + 10}px`); tooltip.select("#deleteButton").on("click", () => deleteEntry(d.id)); }) // ... .on("mousemove", (event) => { tooltip.style("left", `${event.pageX + 10}px`).style("top", `${event.pageY + 10}px`); }); tooltip.on("mouseover", () => { clearTimeout(tooltipTimer); if (isMouseOverNode) { tooltip.style("opacity", 1); // move tooltip back to mouse position tooltip.style("left", `${d3.event.pageX + 10}px`).style("top", `${d3.event.pageY + 10}px`); } }) .on("mouseout", () => { clearTimeout(tooltipTimer); tooltipTimer = setTimeout(() => { tooltip.transition() .duration(200) .style("opacity", 0); //also move invisible tooltip out of screen so it doesn't block mouse events tooltip.style("left", "-1000px").style("top", "-1000px"); }, 150); }); node.on("mouseout", () => { isMouseOverNode = false; clearTimeout(tooltipTimer); tooltipTimer = setTimeout(() => { tooltip.transition() .duration(200) .style("opacity", 0); //also move invisible tooltip out of screen so it doesn't block mouse events tooltip.style("left", "-1000px").style("top", "-1000px"); }, 150); }); const label = g .selectAll("text") .data(nodes) .join("text") .text((d) => d.label) .attr("font-size", "10px") .attr("dx", 8) .attr("dy", "0.35em"); const zoomBehavior = d3.zoom() .scaleExtent([0.1, 5]) .on('zoom', (event) => { g.attr('transform', event.transform); }); svg.call(zoomBehavior); simulation.on("tick", () => { link .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y); node .attr("cx", (d) => d.x) .attr("cy", (d) => d.y); label .attr("x", (d) => d.x) .attr("y", (d) => d.y); }); }; window.addEventListener('load', () => { let links = document.querySelector('#bodyContent').querySelectorAll('a[href*="/wiki/"]'); let title = document.querySelector('#firstHeading').innerText; let link = linkToTitle(window.location.pathname); let linkEntries = []; for (let i = 0; i < links.length; i++) { if (links[i].href.includes('/wiki/')) { let linkEntry = { title: links[i].title, link: linkToTitle(links[i].href) }; linkEntries.push(linkEntry); } } let entry = { title: title, link: link, links: linkEntries }; let date = new Date(); let dateString = `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; let database; getWikipediaHistoryData().then(data => { database = data; if (database) { let entryIndex = -1; for (let i = 0; i < database.length; i++) { if (database[i].title === entry.title) { entryIndex = i; break; } } if (entryIndex < 0) { entry.dates_accessed = [dateString]; database.push(entry); } else { let lastDate = database[entryIndex].dates_accessed[database[entryIndex].dates_accessed.length - 1]; if (lastDate.split(' ')[0] !== dateString.split(' ')[0]) { database[entryIndex].dates_accessed.push(dateString); } database[entryIndex].links = entry.links; } } else { entry.dates_accessed = [dateString]; database = [entry]; } if (link === 'History_visualization_script') { addStyles(); const bodyContent = document.querySelector('#bodyContent'); bodyContent.innerHTML = ''; document.body.innerHTML = ''; document.body.appendChild(bodyContent); let script = document.createElement('script'); script.src = 'https://d3js.org/d3.v7.min.js'; document.querySelector('#bodyContent').appendChild(script); let script2 = document.createElement('script'); script2.src = 'https://unpkg.com/d3-force-cluster@latest'; document.querySelector('#bodyContent').appendChild(script2); script.onload = () => { let svgElement = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); svgElement.setAttribute('width', window.innerWidth); svgElement.setAttribute('height', window.innerHeight); svgElement.setAttribute('id', 'graph'); document.querySelector('#bodyContent').appendChild(svgElement); getWikipediaHistoryData().then(wikipediaHistoryData => { createGraph(wikipediaHistoryData); }); } } else { setWikipediaHistoryData(database); createHistoryButton(); } }); }, false);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址