YCharts Simple Chart Explainer

Adds a button to explain charts in simple terms with a copy-to-email feature

Size

12.5 KB

Version

1.1.3

Created

Oct 16, 2025

Updated

7 days ago

1// ==UserScript==
2// @name		YCharts Simple Chart Explainer
3// @description		Adds a button to explain charts in simple terms with a copy-to-email feature
4// @version		1.1.3
5// @match		https://*.ycharts.com/*
6// @icon		https://static.ycharts.com/images/icons/favicon.637225eac278.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10    
11    console.log('YCharts Simple Chart Explainer loaded');
12    
13    // Debounce function to avoid multiple rapid calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25    
26    // Function to extract chart data
27    function extractChartData() {
28        const chartTitle = document.querySelector('.page-name-text')?.textContent?.trim() || 'Chart';
29        
30        const securities = Array.from(document.querySelectorAll('#securities-list .options-list-item')).map(item => {
31            const name = item.querySelector('.custom-control-description')?.textContent?.trim();
32            return name;
33        }).filter(Boolean);
34        
35        const legendItems = Array.from(document.querySelectorAll('.chart-legend-item')).slice(1).map(item => {
36            const name = item.querySelector('.chart-legend-item-title')?.textContent?.trim();
37            const date = item.querySelector('.chart-legend-item-date')?.textContent?.trim();
38            const values = Array.from(item.querySelectorAll('[data-test-id^="chart-legend-item-value"]')).map(v => v.textContent.trim());
39            return { name, date, values };
40        }).filter(item => item.name);
41        
42        // Extract the selected timeframe
43        const timeframeElement = document.querySelector('ycn-fund-chart-date-zoom .chart-control-item.current .chart-control-link');
44        const timeframe = timeframeElement?.textContent?.trim() || 'unknown';
45        
46        return { chartTitle, securities, legendItems, timeframe };
47    }
48    
49    // Function to create and show the explanation popup
50    function showExplanationPopup(explanation) {
51        // Remove existing popup if any
52        const existingPopup = document.getElementById('chart-explainer-popup');
53        if (existingPopup) {
54            existingPopup.remove();
55        }
56        
57        // Create popup overlay
58        const overlay = document.createElement('div');
59        overlay.id = 'chart-explainer-popup';
60        overlay.style.cssText = `
61            position: fixed;
62            top: 0;
63            left: 0;
64            width: 100%;
65            height: 100%;
66            background: rgba(0, 0, 0, 0.5);
67            display: flex;
68            align-items: center;
69            justify-content: center;
70            z-index: 10000;
71        `;
72        
73        // Create popup content
74        const popup = document.createElement('div');
75        popup.style.cssText = `
76            background: white;
77            border-radius: 8px;
78            padding: 24px;
79            max-width: 600px;
80            max-height: 80vh;
81            overflow-y: auto;
82            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
83            position: relative;
84        `;
85        
86        // Create header
87        const header = document.createElement('div');
88        header.style.cssText = `
89            display: flex;
90            justify-content: space-between;
91            align-items: center;
92            margin-bottom: 16px;
93            padding-bottom: 12px;
94            border-bottom: 2px solid #e0e0e0;
95        `;
96        
97        const title = document.createElement('h3');
98        title.textContent = 'Chart Explanation';
99        title.style.cssText = `
100            margin: 0;
101            font-size: 20px;
102            font-weight: 600;
103            color: #333;
104        `;
105        
106        const closeButton = document.createElement('button');
107        closeButton.textContent = '×';
108        closeButton.style.cssText = `
109            background: none;
110            border: none;
111            font-size: 32px;
112            cursor: pointer;
113            color: #666;
114            padding: 0;
115            width: 32px;
116            height: 32px;
117            line-height: 32px;
118            text-align: center;
119        `;
120        closeButton.onclick = () => overlay.remove();
121        
122        header.appendChild(title);
123        header.appendChild(closeButton);
124        
125        // Create explanation text area
126        const explanationText = document.createElement('div');
127        explanationText.textContent = explanation;
128        explanationText.style.cssText = `
129            font-size: 16px;
130            line-height: 1.6;
131            color: #333;
132            margin-bottom: 20px;
133            white-space: pre-wrap;
134        `;
135        
136        // Create copy button
137        const copyButton = document.createElement('button');
138        copyButton.textContent = 'Copy to Clipboard';
139        copyButton.style.cssText = `
140            background: #007bff;
141            color: white;
142            border: none;
143            padding: 10px 20px;
144            border-radius: 5px;
145            cursor: pointer;
146            font-size: 14px;
147            font-weight: 500;
148            transition: background 0.2s;
149        `;
150        copyButton.onmouseover = () => copyButton.style.background = '#0056b3';
151        copyButton.onmouseout = () => copyButton.style.background = '#007bff';
152        copyButton.onclick = async () => {
153            try {
154                await navigator.clipboard.writeText(explanation);
155                copyButton.textContent = '✓ Copied!';
156                copyButton.style.background = '#28a745';
157                setTimeout(() => {
158                    copyButton.textContent = 'Copy to Clipboard';
159                    copyButton.style.background = '#007bff';
160                }, 2000);
161            } catch (err) {
162                console.error('Failed to copy:', err);
163                copyButton.textContent = 'Failed to copy';
164                copyButton.style.background = '#dc3545';
165                setTimeout(() => {
166                    copyButton.textContent = 'Copy to Clipboard';
167                    copyButton.style.background = '#007bff';
168                }, 2000);
169            }
170        };
171        
172        // Assemble popup
173        popup.appendChild(header);
174        popup.appendChild(explanationText);
175        popup.appendChild(copyButton);
176        overlay.appendChild(popup);
177        
178        // Close on overlay click
179        overlay.onclick = (e) => {
180            if (e.target === overlay) {
181                overlay.remove();
182            }
183        };
184        
185        document.body.appendChild(overlay);
186    }
187    
188    // Function to generate explanation using AI
189    async function generateExplanation() {
190        const button = document.getElementById('explain-chart-btn');
191        if (!button) return;
192        
193        // Show loading state
194        const originalText = button.textContent;
195        button.textContent = 'Generating...';
196        button.disabled = true;
197        button.style.opacity = '0.6';
198        button.style.cursor = 'wait';
199        
200        try {
201            const chartData = extractChartData();
202            console.log('Chart data extracted:', chartData);
203            
204            // Check if RM.aiCall is available
205            if (typeof RM === 'undefined' || typeof RM.aiCall !== 'function') {
206                throw new Error('AI service is not available. Please refresh the page and try again.');
207            }
208            
209            // Create prompt for AI
210            const prompt = `You are explaining a financial chart to a 70-year-old person who may not be familiar with investment terminology. 
211
212Chart Title: ${chartData.chartTitle}
213Timeframe: ${chartData.timeframe}
214
215Securities being compared:
216${chartData.securities.join('\n')}
217
218Current performance data (as of ${chartData.legendItems[0]?.date || 'latest'}):
219${chartData.legendItems.map(item => `- ${item.name}: ${item.values.join(' (Annualized: ')})`).join('\n')}
220
221Please provide a clear, simple explanation that:
2221. Explains what this chart is showing in plain English, specifically mentioning the timeframe (${chartData.timeframe})
2232. Provides context about what the timeframe means (e.g., "over the past 3 years" or "year-to-date" or "over the past month")
2243. Highlights which investments are performing best and worst over this specific time period
2254. Explains what the percentages mean in practical terms for this timeframe
2265. Avoids jargon and uses everyday language
2276. Keeps it concise (3-4 paragraphs maximum)
2287. Is suitable for pasting into an email
229
230Write in a warm, conversational tone as if you're explaining this to a friend or family member. Make sure to naturally incorporate the timeframe context throughout the explanation.`;
231
232            console.log('Calling AI with prompt...');
233            const explanation = await RM.aiCall(prompt);
234            console.log('AI explanation received');
235            
236            // Show popup with explanation
237            showExplanationPopup(explanation);
238            
239        } catch (error) {
240            console.error('Error generating explanation:', error);
241            const errorMessage = error.message || 'Unknown error occurred';
242            alert('Sorry, there was an error generating the explanation:\n\n' + errorMessage + '\n\nThis appears to be a platform issue. Please try:\n1. Refreshing the page\n2. Restarting your browser\n3. Opening the chart in a new tab');
243        } finally {
244            // Restore button state
245            button.textContent = originalText;
246            button.disabled = false;
247            button.style.opacity = '1';
248            button.style.cursor = 'pointer';
249        }
250    }
251    
252    // Function to add the explain button
253    function addExplainButton() {
254        // Check if button already exists
255        if (document.getElementById('explain-chart-btn')) {
256            console.log('Explain button already exists');
257            return;
258        }
259        
260        // Find the Chart Options dropdown container
261        const chartOptionsContainer = document.querySelector('.chart-options-cover');
262        if (!chartOptionsContainer) {
263            console.log('Chart Options container not found yet');
264            return;
265        }
266        
267        console.log('Adding explain button next to Chart Options');
268        
269        // Create a wrapper for our button
270        const buttonWrapper = document.createElement('div');
271        buttonWrapper.className = 'chart-options-cover col-i-3';
272        buttonWrapper.style.cssText = `
273            margin-left: 8px;
274        `;
275        
276        // Create the explain button
277        const explainButton = document.createElement('button');
278        explainButton.id = 'explain-chart-btn';
279        explainButton.className = 'btn btn-secondary btn-block';
280        explainButton.textContent = '📊 Explain Chart';
281        explainButton.style.cssText = `
282            background: #28a745;
283            border-color: #28a745;
284            color: white;
285            font-weight: 500;
286            cursor: pointer;
287            transition: background 0.2s;
288            white-space: nowrap;
289        `;
290        explainButton.onmouseover = () => {
291            if (!explainButton.disabled) {
292                explainButton.style.background = '#218838';
293                explainButton.style.borderColor = '#1e7e34';
294            }
295        };
296        explainButton.onmouseout = () => {
297            if (!explainButton.disabled) {
298                explainButton.style.background = '#28a745';
299                explainButton.style.borderColor = '#28a745';
300            }
301        };
302        explainButton.onclick = generateExplanation;
303        
304        buttonWrapper.appendChild(explainButton);
305        
306        // Insert after Chart Options
307        chartOptionsContainer.parentNode.insertBefore(buttonWrapper, chartOptionsContainer.nextSibling);
308        
309        console.log('Explain button added successfully');
310    }
311    
312    // Initialize the extension
313    function init() {
314        console.log('Initializing YCharts Simple Chart Explainer');
315        
316        // Try to add button immediately
317        addExplainButton();
318        
319        // Watch for DOM changes in case the page loads dynamically
320        const observer = new MutationObserver(debounce(() => {
321            addExplainButton();
322        }, 500));
323        
324        observer.observe(document.body, {
325            childList: true,
326            subtree: true
327        });
328        
329        console.log('Observer set up to watch for Chart Options');
330    }
331    
332    // Wait for page to be ready
333    if (document.readyState === 'loading') {
334        document.addEventListener('DOMContentLoaded', init);
335    } else {
336        init();
337    }
338})();
YCharts Simple Chart Explainer | Robomonkey