Easily display profit/loss on the profitability chart.
// ==UserScript== // @name Fintual Goal Variation // @name:es Fintual variación de objetivos // @namespace http://tampermonkey.net/ // @version 0.4 // @description Easily display profit/loss on the profitability chart. // @description:es Muestra fácilmente la ganancia/pérdida en el gráfico de rentabilidad. // @author IgnaV // @match https://fintual.cl/app/goals/* // @icon http://fintual.cl/favicon.ico // @grant none // ==/UserScript== (function() { 'use strict'; const showHistory = false; const containerHeightStyle = document.createElement('style'); document.head.appendChild(containerHeightStyle); const updateContainerHeight = elements => { containerHeightStyle.textContent = `div.nvtooltip.performance-tooltip { top: -${22 * elements}px !important }`; }; updateContainerHeight(2); const history = {}; let diff, date, clickDiff, clickDate; const calculatePercentage = (value, total) => ((value / total) * 100).toFixed(1); const getLegendBgColor = value => (value >= 0 ? 'rgb(43, 214, 0)' : 'rgb(214, 0, 0)'); const formatNumber = number => number.toLocaleString('es-CL', { useGrouping: true }); const addRowToTable = (tbody, label, percentage, value, color) => { const row = document.createElement('tr'); row.innerHTML = ` <td class="legend-color-guide"><div style="background-color: ${color};"></div></td> <td class="key">${label}</td> <td class="value">(${percentage}%) $ ${value}</td>`; tbody.appendChild(row); }; const updateDateContent = (tdDate, esDateStr, weekDayName) => { tdDate.textContent = `${esDateStr} | ${weekDayName}`; if (clickDate !== undefined) { const betweenDays = Math.round((date - clickDate) / (1000 * 60 * 60 * 24)); tdDate.textContent += ` | ${betweenDays} ${betweenDays === 1 ? "día" : "días"}`; } }; const observeTableChanges = targetElement => { const tableObserver = new MutationObserver(mutationsList => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.tagName === 'TABLE') { const table = node; const tdDate = table.querySelector('thead > tr > td > strong'); const tbody = table.querySelector('tbody'); const existingRows = tbody.querySelectorAll('tr'); const depositAmount = parseFloat(existingRows[0].querySelectorAll('td')[2].textContent.replace(/[^0-9-]+/g, '')); const fintualBalance = parseFloat(existingRows[1].querySelectorAll('td')[2].textContent.replace(/[^0-9-]+/g, '')); diff = fintualBalance - depositAmount; const diffPercentage = calculatePercentage(diff, depositAmount); const legendBgColor = getLegendBgColor(diff); const formattedDiff = formatNumber(diff); addRowToTable(tbody, 'Balance', diffPercentage, formattedDiff, legendBgColor); let [esDateStr, weekDayName] = tdDate.textContent.split(' '); const dateParts = esDateStr.split('/'); const year = '20' + dateParts[2]; const month = dateParts[1]; const day = dateParts[0]; const enDateStr = `${year}-${month}-${day}`; if (!weekDayName) { date = new Date(year, month - 1, day); const weekDayNames = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado']; weekDayName = weekDayNames[date.getDay()]; updateDateContent(tdDate, esDateStr, weekDayName); } history[enDateStr] = diff; if (showHistory) { console.log(Object.keys(history).sort().reduce((acc, key) => (acc[key] = history[key], acc), {})); } const previousDate = Object.keys(history).sort().reverse().find(key => key < enDateStr); if (previousDate !== undefined) { const previousDiff = history[previousDate]; const balanceDiff = diff - previousDiff; const balanceDiffPercentage = calculatePercentage(balanceDiff, depositAmount); const balanceLegendBgColor = getLegendBgColor(balanceDiff); const balanceFormattedDiff = formatNumber(balanceDiff); addRowToTable(tbody, 'Diferencia', balanceDiffPercentage, balanceFormattedDiff, balanceLegendBgColor); } if (clickDiff !== undefined) { const balanceDiff = diff - clickDiff; const balanceDiffPercentage = calculatePercentage(balanceDiff, depositAmount); const balanceLegendBgColor = getLegendBgColor(balanceDiff); const balanceFormattedDiff = formatNumber(balanceDiff); addRowToTable(tbody, 'Diferencia click', balanceDiffPercentage, balanceFormattedDiff, balanceLegendBgColor); updateContainerHeight(3); } else { updateContainerHeight(2); } } } } } }); const tableObserverOptions = { childList: true, subtree: true }; tableObserver.observe(targetElement, tableObserverOptions); return tableObserver; }; let prev; const rootObserver = new MutationObserver(mutationsList => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { const targetElement = document.querySelector('div.nvtooltip.performance-tooltip'); if (targetElement) { if (prev) prev.disconnect(); prev = observeTableChanges(targetElement); rootObserver.disconnect(); } } } }); const rootObserverOptions = { childList: true, subtree: true }; rootObserver.observe(document.body, rootObserverOptions); const removeLastClick = () => { clickDiff = clickDate = undefined; const lastClickElement = document.querySelector('.last-click'); if (lastClickElement) { lastClickElement.remove(); } }; document.addEventListener('click', event => { if (event.target.matches('g.nv-focus, g.nv-focus *')) { removeLastClick(); clickDiff = diff; clickDate = date; const guideLine = document.querySelector('.nv-interactiveGuideLine'); const clonedGuideLine = guideLine.cloneNode(true); clonedGuideLine.classList.add('last-click'); guideLine.parentNode.insertBefore(clonedGuideLine, guideLine.nextSibling); } }); document.addEventListener('dblclick', removeLastClick); })();