您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script to plot graphs with improved plotting and axis markings
// ==UserScript== // @name Dynamic Graph (Calc Extension) // @version 1.7 // @description A script to plot graphs with improved plotting and axis markings // @match *://*/* // @namespace https://greasyfork.org/en/users/1291009 // @author BadOrBest // @license MIT // @icon https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRbUKCIRZQujx1f9j9HfzO0igzHyCAFjAxYKQ&s // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @grant GM.registerMenuCommand // ==/UserScript== (function() { 'use strict'; const defaultViewport = { x: 0, y: 0, width: 600, height: 400, scale: 1 }; let viewport = { ...defaultViewport }; let isDragging = false; let dragStart = { x: 0, y: 0 }; let renderingInterval; // Create canvas and zoom control elements const canvas = document.createElement('canvas'); canvas.id = 'graph-canvas'; canvas.width = 600; canvas.height = 400; canvas.style.border = '24px solid black'; canvas.style.borderRadius = '12px'; canvas.style.backgroundColor = 'white'; canvas.style.display = 'none'; canvas.style.position = 'absolute'; canvas.style.left = '100px'; canvas.style.top = '100px'; document.body.appendChild(canvas); const zoomBar = document.createElement('div'); zoomBar.id = 'zoom-bar'; zoomBar.style.position = 'absolute'; zoomBar.style.top = '10px'; zoomBar.style.left = '10px'; zoomBar.style.padding = '5px'; zoomBar.style.backgroundColor = 'white'; zoomBar.style.border = '1px solid black'; zoomBar.style.borderRadius = '8px'; zoomBar.style.zIndex = '1001'; zoomBar.textContent = `Zoom: ${viewport.scale.toFixed(2)}x`; document.body.appendChild(zoomBar); function plotGraph(data) { const context = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; context.clearRect(0, 0, width, height); // Draw x and y axes only (no grid) context.strokeStyle = 'black'; context.lineWidth = 2; context.beginPath(); context.moveTo(width / 2, 0); // Y axis context.lineTo(width / 2, height); context.moveTo(0, height / 2); // X axis context.lineTo(width, height / 2); context.stroke(); // Draw graph line and plot points only when in view context.strokeStyle = 'blue'; context.lineWidth = 2; context.beginPath(); let first = true; data.forEach(point => { const screenX = (point.x - viewport.x) * viewport.scale + width / 2; const screenY = height / 2 - (point.y - viewport.y) * viewport.scale; // Plot the line and points only when they're in view if (screenX >= 0 && screenX <= width && screenY >= 0 && screenY <= height) { if (first) { context.moveTo(screenX, screenY); first = false; } else { context.lineTo(screenX, screenY); } // Plot the point context.fillStyle = 'red'; context.beginPath(); context.arc(screenX, screenY, 3, 0, Math.PI * 2); context.fill(); } }); context.stroke(); } function parseExpression(expression) { const dataPoints = []; const step = 1 / viewport.scale; // Increased granularity for better detail let lastX = viewport.x - viewport.width / 2; while (lastX <= viewport.x + viewport.width / 2) { try { const y = evaluateExpression(expression, lastX); if (Math.abs(lastX) <= viewport.width / 2 && Math.abs(y) <= viewport.height / 2) { dataPoints.push({ x: lastX, y }); } } catch (e) { console.error('Error evaluating expression:', e); } lastX += step; } return filterPlotPoints(dataPoints); } function filterPlotPoints(points) { const filteredPoints = []; let lastPoint = null; points.forEach(point => { if (!lastPoint || Math.abs(point.y - lastPoint.y) >= 2) { // Only add points with a gap of 2 filteredPoints.push(point); lastPoint = point; } }); return filteredPoints; } function evaluateExpression(expr, x) { return Function('x', `return ${expr.replace('^', '**')}`)(x); } function addGraphData() { const expression = prompt('Enter a mathematical expression (e.g., x^2 + 2*x + 1):'); if (expression) { const data = parseExpression(expression); plotGraph(data); saveGraphData(expression, data); } } function clearGraphData() { localStorage.removeItem('graphExpression'); localStorage.removeItem('graphData'); plotGraph([]); // Clear the graph } function saveGraphData(expression, data) { try { localStorage.setItem('graphExpression', expression); localStorage.setItem('graphData', JSON.stringify(data)); } catch (error) { console.error('Error saving graph data to localStorage:', error); } } function loadGraphData() { try { const expression = localStorage.getItem('graphExpression'); const data = JSON.parse(localStorage.getItem('graphData')); if (expression && data) { plotGraph(data); } } catch (error) { console.error('Error loading graph data from localStorage:', error); } } function toggleGraph() { const canvas = document.getElementById('graph-canvas'); if (canvas.style.display === 'none') { canvas.style.display = 'block'; updateZoomBar(); loadGraphData(); // Load saved graph data } else { canvas.style.display = 'none'; } } function handleWheel(event) { event.preventDefault(); const zoomFactor = 0.1; const zoomIn = event.deltaY < 0; const newScale = zoomIn ? viewport.scale * (1 + zoomFactor) : viewport.scale / (1 + zoomFactor); viewport.scale = Math.max(Math.min(newScale, 20), 0.1); // Clamp scale between 0.1 and 20 const rect = canvas.getBoundingClientRect(); const mouseX = (event.clientX - rect.left - canvas.width / 2) / viewport.scale + viewport.x; const mouseY = (event.clientY - rect.top - canvas.height / 2) / viewport.scale + viewport.y; viewport.x = mouseX - (event.clientX - rect.left - canvas.width / 2) / viewport.scale; viewport.y = mouseY - (event.clientY - rect.top - canvas.height / 2) / viewport.scale; updateZoomBar(); const expression = localStorage.getItem('graphExpression'); if (expression) { const data = parseExpression(expression); plotGraph(data); startRenderingInterval(true); // Faster rendering while zooming } } function handleMouseDown(event) { if (event.button === 0) { // Left mouse button isDragging = true; dragStart.x = event.clientX; dragStart.y = event.clientY; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } } // Updated to sync canvas dragging with viewport function handleMouseMove(event) { if (!isDragging) return; const dx = event.clientX - dragStart.x; const dy = event.clientY - dragStart.y; viewport.x -= dx / viewport.scale; viewport.y += dy / viewport.scale; dragStart.x = event.clientX; dragStart.y = event.clientY; plotGraph(parseExpression(localStorage.getItem('graphExpression'))); } function handleMouseUp() { isDragging = false; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); } function updateZoomBar() { zoomBar.textContent = `Zoom: ${viewport.scale.toFixed(2)}x`; } function startRenderingInterval(zooming = false) { stopRenderingInterval(); renderingInterval = setInterval(() => { const expression = localStorage.getItem('graphExpression'); if (expression) { const data = parseExpression(expression); plotGraph(data); } }, zooming ? 500 : 1000); // Adjusted faster rendering when zooming } function stopRenderingInterval() { if (renderingInterval) { clearInterval(renderingInterval); renderingInterval = null; } } canvas.addEventListener('wheel', handleWheel); canvas.addEventListener('mousedown', handleMouseDown); const button = document.createElement('button'); button.textContent = 'Graph'; button.style.position = 'fixed'; button.style.top = '10px'; button.style.right = '10px'; button.style.zIndex = '1000'; button.addEventListener('click', toggleGraph); document.body.appendChild(button); GM_registerMenuCommand('Add Equation', addGraphData); GM_registerMenuCommand('Clear Graph', clearGraphData); updateZoomBar(); loadGraphData(); // Load saved graph data on script load })();