HTML
<div class="form-group">
<label>投資元本(万円):</label>
<input type="range" class="initial-investment" min="0" max="1000" step="10" value="100">
<span class="initial-investment-value">100万円</span>
</div>
<div class="form-group">
<label>月間投資額(万円):</label>
<input type="range" class="monthly-investment" min="0" max="100" step="1" value="0">
<span class="monthly-investment-value">0万円</span>
</div>
<div class="form-group">
<label>年利率 (%):</label>
<select class="annual-return-rate">
<option value="3">3%</option>
<option value="5">5%</option>
<option value="8">8%</option>
</select>
</div>
<div class="form-group">
<label>リスクレベル:</label>
<select class="risk-level">
<option value="low">低リスク</option>
<option value="medium" selected>中リスク</option>
<option value="high">高リスク</option>
</select>
</div>
</div>
</div>
<button type="button" id="add-product-button">投資商品を追加</button>
<div class="form-group">
<label for="target-amount">目標金額(万円):</label>
<input type="range" id="target-amount" name="targetAmount" min="0" max="10000" step="100" value="2000">
<span id="target-amount-value">2000万円</span>
</div>
<button type="button" id="simulate-button">シミュレーション開始</button>
<button type="reset">リセット</button>
</form>
</div>
<!-- ポートフォリオ表示 -->
<div class="portfolio-container">
<h2>ポートフォリオ</h2>
<table id="portfolio-table">
<thead>
<tr>
<th>商品名</th>
<th>投資元本(万円)</th>
<th>月間投資額(万円)</th>
<th>年利率(%)</th>
<th>リスクレベル</th>
</tr>
</thead>
<tbody id="portfolio-body">
</tbody>
</table>
</div>
<div class="result-container">
<h2>シミュレーション結果</h2>
<div class="tabs">
<button class="tab-button active" data-scenario="normal">通常</button>
<button class="tab-button" data-scenario="expansion">景気拡大</button>
<button class="tab-button" data-scenario="recession">リセッション</button>
</div>
<p id="result-text"></p>
<canvas id="result-chart"></canvas>
<button id="download-pdf">結果をPDFでダウンロード</button>
<table id="result-table">
<thead>
<tr>
<th>年数</th>
<th>元本</th>
<th>追加投資</th>
<th>利息収益</th>
<th>合計 (元本+追加投資+利息収益)</th>
</tr>
</thead>
<tbody id="result-body">
</tbody>
</table>
</div>
</div>
<!-- JavaScriptファイル -->
<script src="script.js"></script>
CSS
/* styles.css / body { font-family: ‘Roboto’, sans-serif; margin: 0; background-color: #f5f7fa; color: #333; font-size: 16px; / 文字サイズを大きく */
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1, h2, h3 {
text-align: center;
color: #333;
}
.form-container, .portfolio-container, .result-container {
background-color: #fff;
padding: 20px;
border-radius: 5px;
margin-top: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type=”range”], input[type=”text”], select {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 5px;
}
span {
display: block;
margin-top: 5px;
color: #555;
}
button {
margin-top: 20px;
padding: 12px 24px;
background-color: #007BFF;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
button[type=”reset”] {
background-color: #6c757d;
margin-left: 10px;
}
download-pdf {
background-color: #28a745;
}
.result-container {
margin-top: 30px;
}
result-text {
text-align: center;
font-size: 20px;
margin-bottom: 20px;
}
result-chart {
max-width: 100%;
height: 400px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
table th, table td {
border: 1px solid #dee2e6;
padding: 12px;
text-align: center;
}
table th {
background-color: #e9ecef;
}
.tabs {
display: flex;
margin-bottom: 20px;
}
.tab-button {
flex: 1;
padding: 10px;
cursor: pointer;
background-color: #e9ecef;
border: none;
outline: none;
transition: background-color 0.3s;
font-size: 16px;
}
.tab-button.active {
background-color: #007BFF;
color: #fff;
}
.investment-product {
border: 1px solid #dee2e6;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
background-color: #f8f9fa;
}
.portfolio-container {
margin-top: 30px;
}
Java
// script.js
// 投資商品のカウンター
let productCount = 1;
// スライダーの値を表示する関数
function updateSliderValue(slider, valueDisplay, unit) {
valueDisplay.textContent = slider.value + unit;
slider.addEventListener(‘input’, function() {
valueDisplay.textContent = this.value + unit;
});
}
// 投資商品を追加する関数
function addInvestmentProduct() {
productCount++;
const investmentProductsDiv = document.getElementById(‘investment-products’);
const productDiv = document.createElement('div');
productDiv.classList.add('investment-product');
const productTitle = document.createElement('h3');
productTitle.textContent = `投資商品${productCount}`;
productDiv.appendChild(productTitle);
// 商品名
const nameGroup = document.createElement('div');
nameGroup.classList.add('form-group');
const nameLabel = document.createElement('label');
nameLabel.textContent = '商品名:';
nameGroup.appendChild(nameLabel);
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.classList.add('product-name');
nameInput.value = `投資商品${productCount}`;
nameGroup.appendChild(nameInput);
productDiv.appendChild(nameGroup);
// 投資元本
const initialInvestmentGroup = document.createElement('div');
initialInvestmentGroup.classList.add('form-group');
const initialInvestmentLabel = document.createElement('label');
initialInvestmentLabel.textContent = '投資元本(万円):';
initialInvestmentGroup.appendChild(initialInvestmentLabel);
const initialInvestmentSlider = document.createElement('input');
initialInvestmentSlider.type = 'range';
initialInvestmentSlider.classList.add('initial-investment');
initialInvestmentSlider.min = '0';
initialInvestmentSlider.max = '1000';
initialInvestmentSlider.step = '10';
initialInvestmentSlider.value = '100';
initialInvestmentGroup.appendChild(initialInvestmentSlider);
const initialInvestmentValue = document.createElement('span');
initialInvestmentValue.classList.add('initial-investment-value');
initialInvestmentValue.textContent = '100万円';
initialInvestmentGroup.appendChild(initialInvestmentValue);
productDiv.appendChild(initialInvestmentGroup);
// 月間投資額
const monthlyInvestmentGroup = document.createElement('div');
monthlyInvestmentGroup.classList.add('form-group');
const monthlyInvestmentLabel = document.createElement('label');
monthlyInvestmentLabel.textContent = '月間投資額(万円):';
monthlyInvestmentGroup.appendChild(monthlyInvestmentLabel);
const monthlyInvestmentSlider = document.createElement('input');
monthlyInvestmentSlider.type = 'range';
monthlyInvestmentSlider.classList.add('monthly-investment');
monthlyInvestmentSlider.min = '0';
monthlyInvestmentSlider.max = '100';
monthlyInvestmentSlider.step = '1';
monthlyInvestmentSlider.value = '0';
monthlyInvestmentGroup.appendChild(monthlyInvestmentSlider);
const monthlyInvestmentValue = document.createElement('span');
monthlyInvestmentValue.classList.add('monthly-investment-value');
monthlyInvestmentValue.textContent = '0万円';
monthlyInvestmentGroup.appendChild(monthlyInvestmentValue);
productDiv.appendChild(monthlyInvestmentGroup);
// 年利率
const annualReturnGroup = document.createElement('div');
annualReturnGroup.classList.add('form-group');
const annualReturnLabel = document.createElement('label');
annualReturnLabel.textContent = '年利率 (%):';
annualReturnGroup.appendChild(annualReturnLabel);
const annualReturnSelect = document.createElement('select');
annualReturnSelect.classList.add('annual-return-rate');
const options = [
{ value: '3', text: '3%' },
{ value: '5', text: '5%' },
{ value: '8', text: '8%' }
];
options.forEach(optionData => {
const option = document.createElement('option');
option.value = optionData.value;
option.textContent = optionData.text;
annualReturnSelect.appendChild(option);
});
annualReturnGroup.appendChild(annualReturnSelect);
productDiv.appendChild(annualReturnGroup);
// リスクレベル
const riskGroup = document.createElement('div');
riskGroup.classList.add('form-group');
const riskLabel = document.createElement('label');
riskLabel.textContent = 'リスクレベル:';
riskGroup.appendChild(riskLabel);
const riskSelect = document.createElement('select');
riskSelect.classList.add('risk-level');
const riskOptions = [
{ value: 'low', text: '低リスク' },
{ value: 'medium', text: '中リスク' },
{ value: 'high', text: '高リスク' }
];
riskOptions.forEach(optionData => {
const option = document.createElement('option');
option.value = optionData.value;
option.textContent = optionData.text;
riskSelect.appendChild(option);
});
riskSelect.value = 'medium'; // デフォルトを中リスクに
riskGroup.appendChild(riskSelect);
productDiv.appendChild(riskGroup);
investmentProductsDiv.appendChild(productDiv);
// スライダーの値を初期化
updateSliderValue(initialInvestmentSlider, initialInvestmentValue, '万円');
updateSliderValue(monthlyInvestmentSlider, monthlyInvestmentValue, '万円');
}
// 初期投資商品のスライダー値を初期化
document.querySelectorAll(‘.investment-product’).forEach(product => {
const initialInvestmentSlider = product.querySelector(‘.initial-investment’);
const initialInvestmentValue = product.querySelector(‘.initial-investment-value’);
updateSliderValue(initialInvestmentSlider, initialInvestmentValue, ‘万円’);
const monthlyInvestmentSlider = product.querySelector('.monthly-investment');
const monthlyInvestmentValue = product.querySelector('.monthly-investment-value');
updateSliderValue(monthlyInvestmentSlider, monthlyInvestmentValue, '万円');
});
// 目標金額のスライダー値を初期化
const targetAmountSlider = document.getElementById(‘target-amount’);
const targetAmountValue = document.getElementById(‘target-amount-value’);
updateSliderValue(targetAmountSlider, targetAmountValue, ‘万円’);
// 投資商品追加ボタンのイベントリスナー
document.getElementById(‘add-product-button’).addEventListener(‘click’, addInvestmentProduct);
// タブの機能を追加
const tabButtons = document.querySelectorAll(‘.tab-button’);
tabButtons.forEach(button => {
button.addEventListener(‘click’, () => {
tabButtons.forEach(btn => btn.classList.remove(‘active’));
button.classList.add(‘active’);
const scenario = button.getAttribute(‘data-scenario’);
simulate(scenario);
});
});
// シミュレーション開始ボタンのイベントリスナー
document.getElementById(‘simulate-button’).addEventListener(‘click’, function() {
// 最初のタブをアクティブに
tabButtons.forEach(btn => btn.classList.remove(‘active’));
tabButtons[0].classList.add(‘active’);
simulate(‘normal’);
});
// シミュレーションを実行する関数
function simulate(scenario) {
const targetAmount = parseFloat(targetAmountSlider.value) * 10000; // 万円を円に変換
// 経済シナリオに基づく年利の調整
let annualReturnAdjustment = 0;
if (scenario === 'expansion') {
annualReturnAdjustment = 0.02; // 年利+2%
} else if (scenario === 'recession') {
annualReturnAdjustment = -0.02; // 年利-2%
}
// 投資商品の情報を取得
const products = [];
document.querySelectorAll('.investment-product').forEach(product => {
const name = product.querySelector('.product-name').value;
const initialInvestment = parseFloat(product.querySelector('.initial-investment').value) * 10000;
const monthlyInvestment = parseFloat(product.querySelector('.monthly-investment').value) * 10000;
let annualReturnRate = parseFloat(product.querySelector('.annual-return-rate').value) / 100;
const riskLevel = product.querySelector('.risk-level').value;
// リスクレベルに応じてリターン率を調整
if (riskLevel === 'low') {
annualReturnRate -= 0.01; // 年利-1%
} else if (riskLevel === 'high') {
annualReturnRate += 0.01; // 年利+1%
}
// 経済シナリオによる調整を適用
annualReturnRate += annualReturnAdjustment;
products.push({
name,
initialInvestment,
monthlyInvestment,
annualReturnRate,
riskLevel
});
});
// ポートフォリオを表示
const portfolioBody = document.getElementById('portfolio-body');
portfolioBody.innerHTML = '';
products.forEach(product => {
const row = document.createElement('tr');
const nameCell = document.createElement('td');
nameCell.textContent = product.name;
row.appendChild(nameCell);
const initialInvestmentCell = document.createElement('td');
initialInvestmentCell.textContent = (product.initialInvestment / 10000).toLocaleString() + '万円';
row.appendChild(initialInvestmentCell);
const monthlyInvestmentCell = document.createElement('td');
monthlyInvestmentCell.textContent = (product.monthlyInvestment / 10000).toLocaleString() + '万円';
row.appendChild(monthlyInvestmentCell);
const annualReturnCell = document.createElement('td');
annualReturnCell.textContent = (product.annualReturnRate * 100).toFixed(2) + '%';
row.appendChild(annualReturnCell);
const riskLevelCell = document.createElement('td');
if (product.riskLevel === 'low') {
riskLevelCell.textContent = '低リスク';
} else if (product.riskLevel === 'medium') {
riskLevelCell.textContent = '中リスク';
} else {
riskLevelCell.textContent = '高リスク';
}
row.appendChild(riskLevelCell);
portfolioBody.appendChild(row);
});
// シミュレーション期間を20年に設定
const maxYears = 20;
// シミュレーション結果の初期化
const resultBody = document.getElementById('result-body');
resultBody.innerHTML = '';
const labels = [];
const principalData = [];
const additionalInvestmentData = [];
const interestData = [];
const totalData = [];
let totalPrincipal = 0;
let totalAdditionalInvestment = 0;
let totalAmount = 0;
let achieved = false;
let achievedYear = null;
for (let year = 0; year <= maxYears; year++) {
let yearlyPrincipal = 0;
let yearlyAdditionalInvestment = 0;
let yearlyInterest = 0;
let yearlyAmount = 0;
products.forEach(product => {
let principal = product.initialInvestment;
let totalInvestment = product.initialInvestment + product.monthlyInvestment * 12 * year;
let amount = 0;
// 元本と追加投資の合計額
let totalContributions = product.initialInvestment + product.monthlyInvestment * 12 * year;
// 合計金額を計算
amount = totalContributions * Math.pow(1 + product.annualReturnRate, year);
// 利息収益を計算
let interest = amount - totalContributions;
yearlyPrincipal += product.initialInvestment;
yearlyAdditionalInvestment += product.monthlyInvestment * 12 * year;
yearlyInterest += interest;
yearlyAmount += amount;
});
// テーブルにデータを追加
let row = document.createElement('tr');
let yearCell = document.createElement('td');
yearCell.textContent = `${year}年目`;
row.appendChild(yearCell);
let principalCell = document.createElement('td');
principalCell.textContent = Math.round(yearlyPrincipal).toLocaleString() + '円';
row.appendChild(principalCell);
let additionalInvestmentCell = document.createElement('td');
additionalInvestmentCell.textContent = Math.round(yearlyAdditionalInvestment).toLocaleString() + '円';
row.appendChild(additionalInvestmentCell);
let interestCell = document.createElement('td');
interestCell.textContent = Math.round(yearlyInterest).toLocaleString() + '円';
row.appendChild(interestCell);
let totalCell = document.createElement('td');
totalCell.textContent = Math.round(yearlyAmount).toLocaleString() + '円';
row.appendChild(totalCell);
resultBody.appendChild(row);
// グラフ用データを追加
labels.push(`${year}`);
principalData.push(Math.round(yearlyPrincipal));
additionalInvestmentData.push(Math.round(yearlyAdditionalInvestment));
interestData.push(Math.round(yearlyInterest));
totalData.push(Math.round(yearlyAmount));
// 目標金額に到達したか確認
if (!achieved && yearlyAmount >= targetAmount) {
achieved = true;
achievedYear = year;
}
}
// 結果テキストを表示
let resultText = '';
if (achieved) {
resultText = `目標金額に${achievedYear}年で到達します。`;
} else {
resultText = `目標金額に到達できませんでした。`;
}
document.getElementById('result-text').innerText = resultText;
// グラフを描画
const ctx = document.getElementById('result-chart').getContext('2d');
if (window.resultChart) {
window.resultChart.destroy();
}
window.resultChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: '元本',
data: principalData,
backgroundColor: '#007BFF',
},
{
label: '追加投資',
data: additionalInvestmentData,
backgroundColor: '#28a745',
},
{
label: '利息収益',
data: interestData,
backgroundColor: '#ffc107',
},
]
},
options: {
responsive: true,
scales: {
x: {
stacked: true,
title: {
display: true,
text: '年数',
font: {
size: 16
}
}
},
y: {
stacked: true,
title: {
display: true,
text: '金額(円)',
font: {
size: 16
}
},
ticks: {
callback: function(value) {
return value.toLocaleString();
},
font: {
size: 14
}
}
},
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
label += context.parsed.y.toLocaleString() + '円';
return label;
}
}
},
legend: {
labels: {
font: {
size: 16
}
}
}
}
}
});
}
// PDF出力機能の実装
document.getElementById(‘download-pdf’).addEventListener(‘click’, function() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// 結果テキストをPDFに追加
doc.setFontSize(16);
doc.text(document.getElementById('result-text').innerText, 10, 20);
// キャンバスを画像として取得
const canvas = document.getElementById('result-chart');
const canvasImg = canvas.toDataURL('image/png', 1.0);
// グラフをPDFに追加
doc.addImage(canvasImg, 'PNG', 10, 30, 190, 80);
// テーブルの内容をPDFに追加
let finalY = 120;
doc.autoTable({ html: '#result-table', startY: finalY });
// PDFをダウンロード
doc.save('simulation_result.pdf');
});