Accurately measure your investment performance by removing the distorting effects of cash inflows and outflows. This calculator helps you understand the true growth of your capital over specific periods.
Investment Performance Calculator
To calculate the Time-Weighted Rate of Return (TWRR), we need to know the value of your investment at the beginning of each sub-period, the value of any withdrawals, the value of any deposits, and the value of your investment at the end of each sub-period. The calculator assumes sub-periods are defined by cash flows (deposits or withdrawals).
Your Results
— %
—
Total Return
—
Geometric Mean Return
—
Avg. Period Return
Formula Used: TWRR = [Π (1 + Ri)] – 1, where Ri is the return for each sub-period. The return for each sub-period (Ri) is calculated as (Ending Value – Beginning Value – Cash Flows) / (Beginning Value + Cash Flows). More precisely, Ri = (Ve – Vb – CF) / (Vb + CF) where Ve is ending market value, Vb is beginning market value, and CF is net cash flow during the period (deposits – withdrawals).
Investment Growth Over Sub-Periods (Illustrative)
Period
Beginning Value
Deposits
Withdrawals
Net Cash Flow
Ending Value
Sub-Period Return (%)
Detailed Breakdown of Period Returns
What is Time-Weighted Rate of Return (TWRR)?
The Time-Weighted Rate of Return (TWRR) is a critical metric used in investment management to measure the performance of a portfolio or investment manager over a specific period. Unlike a money-weighted rate of return, which is influenced by the timing and size of cash flows into and out of the portfolio, the TWRR isolates the performance generated by the investment decisions themselves. It effectively eliminates the impact of investor contributions and withdrawals, providing a standardized measure that allows for fair comparisons between different investment managers or strategies.
Who Should Use It?
TWRR is particularly valuable for:
Investment Managers: To demonstrate their skill and performance to clients, free from the distortions caused by client-initiated cash flows.
Institutional Investors: To benchmark the performance of various external fund managers objectively.
Portfolio Analysts: To evaluate the effectiveness of investment strategies over time.
Individual Investors: To understand the underlying growth rate of their investments when they have made frequent deposits or withdrawals.
Common Misconceptions
TWRR is the same as Portfolio Growth: While related, TWRR measures rate of return, not absolute dollar growth. Significant cash flows can lead to large absolute growth even with moderate TWRR.
TWRR is always higher than MWRR: This is not necessarily true. If an investor consistently adds money during strong performance periods and withdraws during weak periods, the MWRR might appear higher than the TWRR.
TWRR ignores cash flows: TWRR doesn't ignore cash flows; it neutralizes their impact by calculating returns for sub-periods between cash flows.
Time-Weighted Rate of Return Formula and Mathematical Explanation
The core idea behind the Time-Weighted Rate of Return (TWRR) is to break down the overall measurement period into smaller sub-periods, typically defined by the dates of external cash flows (deposits and withdrawals). For each sub-period, the rate of return is calculated independently. These individual sub-period returns are then linked geometrically to arrive at the overall TWRR for the entire measurement period.
Step-by-Step Calculation:
Identify Sub-Periods: Divide the total measurement period into smaller sub-periods. Each sub-period begins either at the start of the measurement period or immediately after an external cash flow (deposit or withdrawal) and ends just before the next cash flow or at the end of the measurement period.
Calculate Sub-Period Returns: For each sub-period, calculate the rate of return (Ri). The formula for a single sub-period return is:
Ri = (Ve - Vb - CF) / (Vb + CF)
Where:
Ve = Market value of the portfolio at the end of the sub-period.
Vb = Market value of the portfolio at the beginning of the sub-period.
CF = Net cash flow during the sub-period (Deposits – Withdrawals).
Note: If there are no cash flows within a sub-period, CF = 0, and the formula simplifies to Ri = (Ve – Vb) / Vb.
Link Sub-Period Returns Geometrically: Once you have the return for each sub-period (R1, R2, …, Rn), link them geometrically using the following formula:
This can be expressed more concisely using product notation:
TWRR = [ Πi=1n (1 + Ri) ] - 1
Variable Explanations:
Variable
Meaning
Unit
Typical Range
Vb
Market Value at the Beginning of a Sub-Period
Currency (e.g., USD, EUR)
≥ 0
Ve
Market Value at the End of a Sub-Period
Currency (e.g., USD, EUR)
≥ 0
CF
Net Cash Flow during the Sub-Period (Deposits – Withdrawals)
Currency (e.g., USD, EUR)
Can be positive (net deposit), negative (net withdrawal), or zero.
Ri
Rate of Return for Sub-Period 'i'
Decimal or Percentage
Typically -100% to positive infinity (though practically much narrower).
TWRR
Time-Weighted Rate of Return
Decimal or Percentage
Typically -100% to positive infinity.
n
Number of Sub-Periods
Count
≥ 1
Practical Examples (Real-World Use Cases)
Example 1: Simple Performance Measurement
An investment starts with $10,000 at the beginning of the year. There are no deposits or withdrawals throughout the year. The investment grows to $12,000 by year-end.
Inputs:
Period 1: Beginning Value = $10,000, Deposits = $0, Withdrawals = $0, Ending Value = $12,000
Interpretation: The investment manager generated a 20% return over the year, independent of any cash flows.
Example 2: Impact of Cash Flows
An investment starts with $50,000 on January 1st. On April 1st, a deposit of $10,000 is made. On July 1st, a withdrawal of $5,000 is made. The investment value at these points are: January 1st ($50,000), April 1st ($55,000 before deposit), April 1st ($65,000 after deposit), July 1st ($60,000 before withdrawal), July 1st ($55,000 after withdrawal), and December 31st ($62,000).
Inputs:
Period 1 (Jan 1 – Mar 31): Beginning Value = $50,000, Deposits = $10,000, Withdrawals = $0, Ending Value = $65,000 (Value just before deposit is irrelevant for calculation of sub-period return IF the value is taken after the deposit. A better way is to use the value *before* the cashflow as end of prior period and *after* the cashflow as start of new period. Let's refine this: The critical points are valuations *before* and *after* cash flows. Let's assume valuations are taken *immediately before* cash flows and *immediately after*.)
Revised approach for clarity:
Sub-Period 1 (Jan 1 – Mar 31): Start: $50,000. End (before deposit): $53,000. Deposit: $10,000.
Interpretation: Despite a large deposit and a subsequent withdrawal, the underlying investment strategy generated a positive Time-Weighted Rate of Return of 3.31% over the year. The actual dollar gain was $12,000 ($62,000 end value – $50,000 initial value), but this figure is heavily influenced by the timing and amount of cash flows.
How to Use This Time-Weighted Rate of Return Calculator
Add Periods: Click the "Add Period" button for each distinct investment period you want to analyze. A period is typically defined by a cash flow event (deposit or withdrawal) or simply by the start and end dates if no cash flows occurred.
Input Data: For each period, carefully enter the following:
Beginning Market Value: The value of your investment at the start of that specific sub-period.
Deposits: The total amount added to the investment during that sub-period.
Withdrawals: The total amount taken out of the investment during that sub-period.
Ending Market Value: The value of your investment at the end of that specific sub-period (after any deposits/withdrawals for that period have been factored in, if using the `(Ve – Vb – CF) / (Vb + CF)` approach).
View Results: As you enter the data, the calculator will update in real-time.
Primary Highlighted Result: The overall Time-Weighted Rate of Return (TWRR) for the entire measurement period.
Intermediate Values: You'll see the Total Return (simple dollar change relative to initial investment, not TWRR), the Geometric Mean Return (average periodic return assuming consistent reinvestment), and the Average Period Return (simple average of all sub-period returns).
Detailed Table: A table breaking down the Net Cash Flow and Sub-Period Return (%) for each period you entered.
Chart: A visual representation showing the calculated returns for each sub-period.
Interpret: Use the TWRR to understand how effectively your investment grew, independent of your own investment activity. Compare this to benchmarks or other managers.
Copy & Reset: Use the "Copy Results" button to save your findings or the "Reset" button to start a new calculation.
Key Factors That Affect TWRR Results
While TWRR aims to neutralize the impact of cash flows, several underlying factors significantly influence its calculation and the resulting performance:
Investment Selection: The fundamental driver of TWRR. Choosing assets that appreciate in value contributes positively. Poor choices lead to negative returns.
Market Volatility: Fluctuations in the broader market directly impact the market values (Vb and Ve) at the start and end of sub-periods. High volatility can lead to larger swings in TWRR, especially if cash flows occur near market peaks or troughs.
Timing of Cash Flows (Indirect Impact): While TWRR is designed to be independent of cash flows, the *valuation points* used are often determined by these flows. If a deposit is made right before a market downturn or a withdrawal occurs after a significant rally, the calculation of that specific sub-period's return can be negatively or positively skewed, respectively. The geometric linking helps average this out over time, but isolated impactful events matter.
Sub-Period Length: Shorter sub-periods (due to frequent cash flows) can lead to more granular data but also potentially more volatile intermediate returns. Longer sub-periods might mask short-term performance blips. The choice of sub-period (often dictated by cash flow dates) is crucial.
Fees and Expenses: Management fees, trading commissions, and other expenses directly reduce the portfolio's value. They decrease Ve (or increase Vb if prepaid) and thus lower the calculated sub-period returns (Ri), ultimately reducing the TWRR.
Taxes: Realized capital gains taxes reduce the net proceeds from selling investments. While TWRR is typically calculated on a pre-tax basis, considering the impact of taxes on actual realized returns is important for holistic performance assessment. Tax implications affect the actual wealth generated.
Inflation: TWRR is a nominal return. High inflation erodes the purchasing power of investment gains. A high TWRR might still result in a loss of real wealth if inflation is higher than the calculated return. Real return = TWRR – Inflation Rate.
Frequently Asked Questions (FAQ)
Q1: How is TWRR different from Money-Weighted Rate of Return (MWRR)?
A1: TWRR measures the compound rate of growth in a portfolio, stripping out the effects of cash inflows and outflows. MWRR, conversely, measures the rate of return based on the timing and amount of cash flows, essentially reflecting the investor's personal return.
Q2: Why is TWRR preferred for evaluating investment managers?
A2: TWRR allows managers to showcase their performance independent of client decisions regarding deposits and withdrawals. This provides a fair comparison of their investment skill across different clients or over time.
Q3: What does a negative TWRR mean?
A3: A negative TWRR indicates that the investment lost value during the measurement period due to poor investment decisions, adverse market conditions, or high fees, irrespective of any cash flows.
Q4: Can TWRR be greater than 100%?
A4: Yes. If an investment doubles in value within a sub-period (and all other factors are neutral), the return for that sub-period would be 100%. The TWRR can exceed 100% if performance is exceptionally strong.
Q5: How often should I calculate TWRR?
A5: For accurate measurement, TWRR should ideally be calculated whenever external cash flows occur. At a minimum, it's often calculated quarterly or annually for reporting purposes.
Q6: Do I need exact market values for TWRR?
A6: Yes, accurate market valuations at the beginning and end of each sub-period (especially around cash flow dates) are crucial for a precise TWRR calculation. Using estimated values can lead to significant inaccuracies.
Q7: How does TWRR relate to investment benchmarks?
A7: TWRR is commonly compared against relevant market indices (benchmarks) to assess whether the investment manager has outperformed or underperformed the market on a risk-adjusted basis, controlling for cash flow timing.
Q8: Is TWRR a guarantee of future performance?
A8: No. Past performance, including TWRR, is not indicative of future results. It's a historical measure of performance under specific conditions.
var periodCounter = 1;
var initialValues = {};
function formatCurrency(num) {
if (isNaN(num) || num === null) return "$–";
return "$" + num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
function formatPercent(num) {
if (isNaN(num) || num === null) return "–%";
return num.toFixed(2) + "%";
}
function validateInput(inputId, errorId, minValue = -Infinity, maxValue = Infinity) {
var input = document.getElementById(inputId);
var error = document.getElementById(errorId);
var value = parseFloat(input.value);
if (input.value.trim() === "") {
error.textContent = "This field is required.";
input.style.borderColor = "red";
return false;
}
if (isNaN(value)) {
error.textContent = "Please enter a valid number.";
input.style.borderColor = "red";
return false;
}
if (value maxValue) {
error.textContent = "Value cannot exceed " + maxValue.toFixed(2);
input.style.borderColor = "red";
return false;
}
error.textContent = "";
input.style.borderColor = ""; // Reset border color
return true;
}
function validateAllInputs() {
var allValid = true;
var periodInputs = document.querySelectorAll('.period-inputs');
periodInputs.forEach(function(periodDiv, index) {
var periodNum = index + 1;
if (!validateInput('beginningValue_' + periodNum, 'errorBeginningValue_' + periodNum)) allValid = false;
if (!validateInput('deposits_' + periodNum, 'errorDeposits_' + periodNum)) allValid = false;
if (!validateInput('withdrawals_' + periodNum, 'errorWithdrawals_' + periodNum)) allValid = false;
if (!validateInput('endingValue_' + periodNum, 'errorEndingValue_' + periodNum)) allValid = false;
});
return allValid;
}
function calculateTWRR() {
if (!validateAllInputs()) {
document.getElementById('primaryResult').textContent = "– %";
document.getElementById('totalReturn').textContent = "–";
document.getElementById('geometricMean').textContent = "–";
document.getElementById('averagePeriodReturn').textContent = "–";
clearCanvas();
document.getElementById('resultsTableBody').innerHTML = ";
return;
}
var periodsData = [];
var subPeriodReturns = [];
var totalNetChange = 0;
var initialInvestment = 0;
var periodDivs = document.querySelectorAll('.period-inputs');
if (periodDivs.length === 0) return; // No periods added yet
var firstPeriodBeginningValue = parseFloat(document.getElementById('beginningValue_1').value);
initialInvestment = firstPeriodBeginningValue; // Use the very first beginning value as the initial investment base
var tableHtml = ";
periodDivs.forEach(function(periodDiv, index) {
var periodNum = index + 1;
var vb = parseFloat(document.getElementById('beginningValue_' + periodNum).value);
var deposits = parseFloat(document.getElementById('deposits_' + periodNum).value);
var withdrawals = parseFloat(document.getElementById('withdrawals_' + periodNum).value);
var ve = parseFloat(document.getElementById('endingValue_' + periodNum).value);
var netCashFlow = deposits – withdrawals;
var subPeriodReturn = 0;
// Handle the case where the denominator might be zero or very close to it
var denominator = vb + netCashFlow;
if (Math.abs(denominator) > 1e-9) { // Use a small epsilon to avoid division by zero
subPeriodReturn = (ve – vb – netCashFlow) / denominator;
} else if (ve === vb + netCashFlow) { // If numerator is also zero, return is 0%
subPeriodReturn = 0;
} else { // If denominator is zero but numerator is not, it's an undefined or infinite return scenario (e.g., starting with $0 and ending with >$0 after cashflow)
subPeriodReturn = Infinity; // Or handle as an error/special case
console.warn("Potential division by zero or infinite return scenario in period " + periodNum);
}
if (!isNaN(subPeriodReturn) && isFinite(subPeriodReturn)) {
subPeriodReturns.push(subPeriodReturn);
} else if (subPeriodReturn === Infinity) {
// Decide how to handle infinite returns – perhaps cap it or report an error
// For simplicity here, we might skip it or add a large placeholder
// Let's skip for now to avoid breaking geometric calculation
console.warn("Skipping infinite return for period " + periodNum);
}
tableHtml += '
';
tableHtml += '
Period ' + periodNum + '
';
tableHtml += '
' + formatCurrency(vb) + '
';
tableHtml += '
' + formatCurrency(deposits) + '
';
tableHtml += '
' + formatCurrency(withdrawals) + '
';
tableHtml += '
' + formatCurrency(netCashFlow) + '
';
tableHtml += '
' + formatCurrency(ve) + '
';
tableHtml += '
' + formatPercent(subPeriodReturn * 100) + '
';
tableHtml += '
';
// Track total net change for the simple total return calculation
// This needs to be done carefully. Total return is (End Value – Start Value – Total Deposits + Total Withdrawals) / Start Value
// Or simply (Final Portfolio Value – Initial Portfolio Value – Net Contributions) / Initial Portfolio Value
});
document.getElementById('resultsTableBody').innerHTML = tableHtml;
// Calculate Overall Metrics
var totalPortfolioValue = parseFloat(document.getElementById('endingValue_' + periodDivs.length).value);
var overallNetCashFlow = 0;
var totalDeposits = 0;
var totalWithdrawals = 0;
periodDivs.forEach(function(periodDiv, index) {
var periodNum = index + 1;
totalDeposits += parseFloat(document.getElementById('deposits_' + periodNum).value);
totalWithdrawals += parseFloat(document.getElementById('withdrawals_' + periodNum).value);
});
overallNetCashFlow = totalDeposits – totalWithdrawals;
var totalReturnPercent = 0;
// Ensure initialInvestment is not zero to avoid division by zero
if (initialInvestment !== 0) {
totalReturnPercent = ((totalPortfolioValue – initialInvestment – overallNetCashFlow) / initialInvestment) * 100;
} else if (totalPortfolioValue > 0) {
// If initial investment was 0 but ended positive, it's a huge return
totalReturnPercent = Infinity;
}
var twrr = 0;
var geometricMean = 0;
var sumOfReturns = 0;
if (subPeriodReturns.length > 0) {
var productOfReturnsPlusOne = 1;
subPeriodReturns.forEach(function(r) {
productOfReturnsPlusOne *= (1 + r);
sumOfReturns += r;
});
twrr = (productOfReturnsPlusOne – 1) * 100;
geometricMean = (Math.pow(productOfReturnsPlusOne, 1 / subPeriodReturns.length) – 1) * 100;
} else {
twrr = totalReturnPercent; // Fallback if no sub-periods calculated properly
geometricMean = totalReturnPercent;
}
var averagePeriodReturn = (sumOfReturns / subPeriodReturns.length) * 100;
if (isNaN(averagePeriodReturn)) averagePeriodReturn = 0;
document.getElementById('primaryResult').textContent = formatPercent(twrr);
document.getElementById('totalReturn').textContent = formatPercent(totalReturnPercent);
document.getElementById('geometricMean').textContent = formatPercent(geometricMean);
document.getElementById('averagePeriodReturn').textContent = formatPercent(averagePeriodReturn);
updateChart(subPeriodReturns.map(r => r * 100), periodDivs.length);
// Store initial values for copy functionality
initialValues = {
periods: periodDivs.length,
totalReturn: formatPercent(totalReturnPercent),
twrr: formatPercent(twrr),
geometricMean: formatPercent(geometricMean),
avgPeriodReturn: formatPercent(averagePeriodReturn)
};
}
function addPeriod() {
var container = document.getElementById('periodInputsContainer');
var periodNum = periodCounter++;
var newPeriodDiv = document.createElement('div');
newPeriodDiv.className = 'period-inputs';
newPeriodDiv.id = 'period_' + periodNum;
newPeriodDiv.innerHTML = `
Period ${periodNum}
Value at the start of this sub-period.
Total added during this sub-period.
Total removed during this sub-period.
Value at the end of this sub-period.
`;
container.appendChild(newPeriodDiv);
// If this is the first period, ensure its value is set correctly
if (periodNum === 1) {
document.getElementById('beginningValue_1').value = '10000'; // Sensible default
document.getElementById('endingValue_1').value = '10000';
}
handleInput(periodNum); // Trigger initial calculation and validation
}
function removePeriod(periodId) {
var periodDiv = document.getElementById('period_' + periodId);
if (periodDiv) {
periodDiv.remove();
// Re-calculate everything after removal
// Need to re-index periods if we want strict numbering, but for calculation it's fine
// Let's reset counter if the removed ID was the last one to avoid gaps
if (periodId === periodCounter – 1) {
periodCounter–;
}
// Re-calculate
calculateTWRR();
}
}
function resetCalculator() {
document.getElementById('periodInputsContainer').innerHTML = ";
periodCounter = 1;
addPeriod(); // Add the first default period
calculateTWRR(); // Calculate initial state
// Clear previous results display
document.getElementById('primaryResult').textContent = "– %";
document.getElementById('totalReturn').textContent = "–";
document.getElementById('geometricMean').textContent = "–";
document.getElementById('averagePeriodReturn').textContent = "–";
clearCanvas();
document.getElementById('resultsTableBody').innerHTML = ";
}
function handleInput(periodNum) {
// Validate current period inputs
var vbValid = validateInput('beginningValue_' + periodNum, 'errorBeginningValue_' + periodNum);
var depositsValid = validateInput('deposits_' + periodNum, 'errorDeposits_' + periodNum);
var withdrawalsValid = validateInput('withdrawals_' + periodNum, 'errorWithdrawals_' + periodNum);
var veValid = validateInput('endingValue_' + periodNum, 'errorEndingValue_' + periodNum);
// Only recalculate if all inputs in *this* period are valid, and potentially all other periods too
// Recalculate everything to ensure smooth updates
calculateTWRR();
}
function copyResults() {
var resultsText = "— Time-Weighted Rate of Return Results —\n\n";
resultsText += "Key Assumptions:\n";
resultsText += "- Number of Periods Analyzed: " + initialValues.periods + "\n";
resultsText += "\n";
resultsText += "Performance Metrics:\n";
resultsText += "Time-Weighted Rate of Return (TWRR): " + initialValues.twrr + "\n";
resultsText += "Total Return (Simple): " + initialValues.totalReturn + "\n";
resultsText += "Geometric Mean Return: " + initialValues.geometricMean + "\n";
resultsText += "Average Period Return: " + initialValues.avgPeriodReturn + "\n\n";
resultsText += "Detailed Period Data:\n";
var tableRows = document.querySelectorAll('#resultsTableBody tr');
tableRows.forEach(function(row) {
var cells = row.querySelectorAll('td');
resultsText += `${cells[0].textContent.padEnd(15)} | ${cells[1].textContent.padEnd(15)} | ${cells[2].textContent.padEnd(15)} | ${cells[3].textContent.padEnd(15)} | ${cells[4].textContent.padEnd(15)} | ${cells[5].textContent.padEnd(15)} | ${cells[6].textContent}\n`;
});
try {
navigator.clipboard.writeText(resultsText).then(function() {
alert('Results copied to clipboard!');
}).catch(function(err) {
console.error('Failed to copy results: ', err);
prompt('Copy manually:', resultsText); // Fallback for browsers without clipboard API or permissions
});
} catch (e) {
console.error('Clipboard API not available: ', e);
prompt('Copy manually:', resultsText);
}
}
// Charting Logic
var myChart = null;
function clearCanvas() {
var canvas = document.getElementById('returnChart');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (myChart) {
myChart.destroy(); // Destroy previous chart instance if it exists
myChart = null;
}
}
function updateChart(periodReturns, numPeriods) {
clearCanvas(); // Clear previous chart before drawing new one
var canvas = document.getElementById('returnChart');
var ctx = canvas.getContext('2d');
// Dynamically set canvas size based on container or viewport if needed, or keep fixed aspect ratio
// For simplicity, let's assume a reasonable aspect ratio
var chartWidth = canvas.parentElement.clientWidth * 0.95; // Use 95% of parent width
var chartHeight = chartWidth * 0.5; // Maintain aspect ratio
canvas.width = chartWidth;
canvas.height = chartHeight;
var labels = [];
for (var i = 1; i <= numPeriods; i++) {
labels.push('Period ' + i);
}
// Prepare data series – Sub-period returns
var dataSeries1 = periodReturns; // Sub-period returns in %
// Example of a second series: Cumulative TWRR up to each period
var cumulativeTWRR = [0];
var currentTWRR = 1;
for (var i = 0; i r * 100); // Remove initial 0, convert to %
myChart = new Chart(ctx, {
type: 'bar', // Use bar chart for periods
data: {
labels: labels,
datasets: [{
label: 'Sub-Period Return (%)',
data: dataSeries1,
backgroundColor: 'rgba(0, 74, 153, 0.6)', // Primary color
borderColor: 'rgba(0, 74, 153, 1)',
borderWidth: 1,
yAxisID: 'y-axis-1' // Assign to the primary y-axis
},
{
label: 'Cumulative TWRR (%)',
data: cumulativeTWRRPercent,
type: 'line', // Line chart for cumulative
borderColor: 'rgba(40, 167, 69, 1)', // Success color
backgroundColor: 'rgba(40, 167, 69, 0.2)',
fill: false,
tension: 0.1,
yAxisID: 'y-axis-2' // Assign to a secondary y-axis
}]
},
options: {
responsive: true,
maintainAspectRatio: false, // Important for dynamic sizing
scales: {
x: {
title: {
display: true,
text: 'Investment Period'
}
},
'y-axis-1': { // Primary Y-axis for Sub-Period Returns
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Sub-Period Return (%)'
},
ticks: {
callback: function(value) {
return value + '%';
}
}
},
'y-axis-2': { // Secondary Y-axis for Cumulative TWRR
type: 'linear',
position: 'right',
title: {
display: true,
text: 'Cumulative TWRR (%)'
},
ticks: {
callback: function(value) {
return value + '%';
}
},
grid: {
drawOnChartArea: false, // Only display on the right side
}
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
var label = context.dataset.label || ";
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += context.parsed.y.toFixed(2) + '%';
}
return label;
}
}
},
legend: {
display: true,
position: 'top'
}
}
}
});
}
// Initial setup when the page loads
document.addEventListener('DOMContentLoaded', function() {
resetCalculator(); // Initialize with default settings
});
// Simple Chart.js implementation (add this script tag in your or before closing )
// NOTE: In a real production environment, you'd load Chart.js from a CDN or include it locally.
// For this self-contained HTML, we assume Chart.js is available globally.
// You would typically need:
// Placeholder for Chart.js library if not already included
if (typeof Chart === 'undefined') {
console.warn("Chart.js library not found. Chart will not render. Please include Chart.js.");
// In a real scenario, you might dynamically load it or display a message.
// For this exercise, we proceed assuming it's available.
}
<!– Add this script tag in your or before closing : –>
<!– –>