また前提の.htmlを見えるようにする設定の部分がちゃんと見えていませんでした。
これも貼っておきます。
メモ帳 & グラフ (マルチ数式・範囲分割対応)
グラフメモ帳
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>メモ帳 & グラフ (マルチ数式・範囲分割対応)</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
font-family: sans-serif;
}
h1 {
margin-bottom: 10px;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-btn {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
border: 1px solid #ccc;
background-color: #f0f0f0;
border-radius: 4px;
}
.tab-btn.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.view {
display: none;
width: 90%;
max-width: 750px;
flex-direction: column;
align-items: center;
}
.view.active {
display: flex;
}
.info {
font-size: 14px;
color: #666;
margin-bottom: 15px;
text-align: center;
}
textarea, input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
}
textarea {
height: 50vh;
}
.formula-group {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 15px;
background: #fdfdfd;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
}
.formula-row {
display: flex;
align-items: center;
gap: 8px;
}
.formula-row label {
font-weight: bold;
width: 30px;
}
.formula-row input[type="text"] {
flex: 1;
margin-bottom: 0;
padding: 6px;
}
.range-subrow {
display: flex;
align-items: center;
gap: 6px;
padding-left: 38px;
font-size: 13px;
color: #555;
}
.range-subrow input[type="number"] {
width: 60px;
padding: 4px;
text-align: center;
}
.range-subrow input[type="checkbox"] {
margin-right: 4px;
cursor: pointer;
}
.range-inputs {
display: inline-flex;
align-items: center;
gap: 6px;
}
details summary {
cursor: pointer;
font-weight: bold;
color: #007bff;
padding: 6px 0;
user-select: none;
}
.controls {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
background: #f9f9f9;
padding: 15px;
border-radius: 6px;
box-sizing: border-box;
}
.control-row {
display: flex;
align-items: center;
gap: 8px;
}
.control-row label {
width: 20px;
font-weight: bold;
}
.control-row input[type="number"] {
width: 55px;
padding: 4px;
text-align: center;
font-size: 14px;
}
.control-row input[type="range"] {
flex: 1;
cursor: pointer;
}
.zero-btn {
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
background-color: #e0e0e0;
border: 1px solid #ccc;
border-radius: 3px;
}
.zero-btn:hover {
background-color: #d0d0d0;
}
.control-row span {
width: 45px;
text-align: right;
}
.output-box {
width: 100%;
background: #eef6ff;
padding: 12px;
border-radius: 6px;
font-size: 18px;
font-weight: bold;
color: #007bff;
text-align: center;
margin-bottom: 15px;
box-sizing: border-box;
border: 1px solid #bce8f1;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
button.action-btn {
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
}
.chart-container {
width: 100%;
margin-top: 10px;
}
</style>
<!-- Chart.jsライブラリ -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>グラフメモ帳</h1>
<!-- タブ切り替えボタン -->
<div class="tabs">
<button class="tab-btn" onclick="switchTab('memo')">メモ帳</button>
<button class="tab-btn active" onclick="switchTab('chart')">グラフ (マルチ数式)</button>
</div>
<!-- メモ帳機能 -->
<div id="memoView" class="view">
<div class="info">入力内容はURLに自動保存されます。このURLを共有すればメモとグラフの両方が共有されます。</div>
<textarea id="memo" placeholder="ここにメモを入力してください..." autocomplete="off"></textarea>
</div>
<!-- グラフ機能 -->
<div id="chartView" class="view active">
<div class="info">f1は範囲なし(ベース)、f2〜f5は範囲分割を設定できます</div>
<!-- 式設定エリア (1〜5) -->
<div class="formula-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
<span style="font-weight: bold; font-size: 14px;">数式設定</span>
<div style="display: flex; gap: 5px;">
<button type="button" class="action-btn" onclick="copyFormula1()" style="padding: 4px 10px; font-size: 12px;">f1の式をコピー</button>
<button type="button" class="action-btn" onclick="setDefaultRanges()" style="padding: 4px 10px; font-size: 12px;">デフォルト範囲を適用</button>
</div>
</div>
<!-- 式 1 (範囲なし専用) -->
<div class="formula-row">
<label>f1</label>
<input type="text" id="formula1" value="10x" placeholder="例: ax + b (ベースの式・範囲なし専用)" autocomplete="off">
</div>
<div class="range-subrow" style="color: #888; font-style: italic;">
<span>※ f1 は範囲なし(全域)で適用されます</span>
</div>
<!-- 式 2〜5 の折りたたみエリア -->
<details id="formulaDetails" style="margin-top: 8px;">
<summary>式 2 〜 5 の設定を開閉</summary>
<!-- 式 2 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f2</label>
<input type="text" id="formula2" value="" placeholder="例: x * x (空欄なら無効)" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f2UseRange"> 範囲あり</label>
<span class="range-inputs" id="f2RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f2MinX" value="-20">
<span>から</span>
<input type="number" id="f2MaxX" value="-10">
<span>まで</span>
</span>
</div>
<!-- 式 3 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f3</label>
<input type="text" id="formula3" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f3UseRange"> 範囲あり</label>
<span class="range-inputs" id="f3RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f3MinX" value="-10">
<span>から</span>
<input type="number" id="f3MaxX" value="0">
<span>まで</span>
</span>
</div>
<!-- 式 4 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f4</label>
<input type="text" id="formula4" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f4UseRange"> 範囲あり</label>
<span class="range-inputs" id="f4RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f4MinX" value="0">
<span>から</span>
<input type="number" id="f4MaxX" value="10">
<span>まで</span>
</span>
</div>
<!-- 式 5 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f5</label>
<input type="text" id="formula5" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f5UseRange"> 範囲あり</label>
<span class="range-inputs" id="f5RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f5MinX" value="10">
<span>から</span>
<input type="number" id="f5MaxX" value="20">
<span>まで</span>
</span>
</div>
</details>
</div>
<!-- yの計算結果表示エリア -->
<div class="output-box" id="outputY">
y = 0.00
</div>
<div class="controls">
<!-- x -->
<div class="control-row" style="background: #eef6ff; padding: 6px; border-radius: 4px;">
<label for="rangeX" style="color: #007bff;">x</label>
<input type="number" id="minX" value="-20">
<input type="range" id="rangeX" min="-20" max="20" step="0.5" value="0">
<input type="number" id="maxX" value="20">
<button class="zero-btn" onclick="resetVar('X')">0</button>
<span id="valX" style="color: #007bff;">0.0</span>
</div>
<!-- a -->
<div class="control-row">
<label for="rangeA">a</label>
<input type="number" id="minA" value="-15">
<input type="range" id="rangeA" min="-15" max="15" step="0.5" value="1">
<input type="number" id="maxA" value="15">
<button class="zero-btn" onclick="resetVar('A')">0</button>
<span id="valA">1.0</span>
</div>
<!-- b -->
<div class="control-row">
<label for="rangeB">b</label>
<input type="number" id="minB" value="-15">
<input type="range" id="rangeB" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxB" value="15">
<button class="zero-btn" onclick="resetVar('B')">0</button>
<span id="valB">0.0</span>
</div>
<!-- c -->
<div class="control-row">
<label for="rangeC">c</label>
<input type="number" id="minC" value="-15">
<input type="range" id="rangeC" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxC" value="15">
<button class="zero-btn" onclick="resetVar('C')">0</button>
<span id="valC">0.0</span>
</div>
<!-- d -->
<div class="control-row">
<label for="rangeD">d</label>
<input type="number" id="minD" value="-15">
<input type="range" id="rangeD" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxD" value="15">
<button class="zero-btn" onclick="resetVar('D')">0</button>
<span id="valD">0.0</span>
</div>
</div>
<div class="chart-container">
<canvas id="myChart"></canvas>
</div>
</div>
<div class="buttons">
<button class="action-btn" id="copyBtn">共有用URLをコピー</button>
<button class="action-btn" id="screenshotBtn">グラフ画像を保存</button>
</div>
<script>
const memo = document.getElementById('memo');
const formulas = [
{ text: document.getElementById('formula1'), useRange: null, minX: null, maxX: null, rangeBox: null },
{ text: document.getElementById('formula2'), useRange: document.getElementById('f2UseRange'), minX: document.getElementById('f2MinX'), maxX: document.getElementById('f2MaxX'), rangeBox: document.getElementById('f2RangeBox') },
{ text: document.getElementById('formula3'), useRange: document.getElementById('f3UseRange'), minX: document.getElementById('f3MinX'), maxX: document.getElementById('f3MaxX'), rangeBox: document.getElementById('f3RangeBox') },
{ text: document.getElementById('formula4'), useRange: document.getElementById('f4UseRange'), minX: document.getElementById('f4MinX'), maxX: document.getElementById('f4MaxX'), rangeBox: document.getElementById('f4RangeBox') },
{ text: document.getElementById('formula5'), useRange: document.getElementById('f5UseRange'), minX: document.getElementById('f5MinX'), maxX: document.getElementById('f5MaxX'), rangeBox: document.getElementById('f5RangeBox') }
];
const outputY = document.getElementById('outputY');
const formulaDetails = document.getElementById('formulaDetails');
const rangeX = document.getElementById('rangeX'), minX = document.getElementById('minX'), maxX = document.getElementById('maxX'), valX = document.getElementById('valX');
const rangeA = document.getElementById('rangeA'), minA = document.getElementById('minA'), maxA = document.getElementById('maxA'), valA = document.getElementById('valA');
const rangeB = document.getElementById('rangeB'), minB = document.getElementById('minB'), maxB = document.getElementById('maxB'), valB = document.getElementById('valB');
const rangeC = document.getElementById('rangeC'), minC = document.getElementById('minC'), maxC = document.getElementById('maxC'), valC = document.getElementById('valC');
const rangeD = document.getElementById('rangeD'), minD = document.getElementById('minD'), maxD = document.getElementById('maxD'), valD = document.getElementById('valD');
const memoView = document.getElementById('memoView');
const chartView = document.getElementById('chartView');
const tabBtns = document.querySelectorAll('.tab-btn');
// f1の式をf2〜f5にコピーする関数
function copyFormula1() {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
for (let i = 1; i < 5; i++) {
formulas[i].text.value = f1Val;
}
updateChart();
updateHash();
} else {
alert('f1に式が入力されていません。');
}
}
// デフォルト範囲および任意のf1式適用関数
function setDefaultRanges() {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
let allEmpty = true;
for (let i = 1; i < 5; i++) {
if (formulas[i].text.value.trim() !== '') {
allEmpty = false;
break;
}
}
if (allEmpty) {
for (let i = 1; i < 5; i++) {
formulas[i].text.value = f1Val;
}
}
}
// f2〜f5のデフォルト範囲設定
formulas[1].useRange.checked = true;
formulas[1].minX.value = -20;
formulas[1].maxX.value = -10;
formulas[2].useRange.checked = true;
formulas[2].minX.value = -10;
formulas[2].maxX.value = 0;
formulas[3].useRange.checked = true;
formulas[3].minX.value = 0;
formulas[3].maxX.value = 10;
formulas[4].useRange.checked = true;
formulas[4].minX.value = 10;
formulas[4].maxX.value = 20;
updateRangeUI();
updateChart();
updateHash();
}
// 折りたたみ展開時のイベント
formulaDetails.addEventListener('toggle', () => {
if (formulaDetails.open) {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
let allEmpty = true;
for (let i = 1; i < 5; i++) {
if (formulas[i].text.value.trim() !== '') {
allEmpty = false;
break;
}
}
if (allEmpty) {
setDefaultRanges();
}
}
}
});
// グラフのセットアップ
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{ label: 'f1', data: [], borderColor: 'rgba(54, 162, 235, 1)', backgroundColor: 'rgba(54, 162, 235, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f2', data: [], borderColor: 'rgba(255, 159, 64, 1)', backgroundColor: 'rgba(255, 159, 64, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f3', data: [], borderColor: 'rgba(75, 192, 192, 1)', backgroundColor: 'rgba(75, 192, 192, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f4', data: [], borderColor: 'rgba(153, 102, 255, 1)', backgroundColor: 'rgba(153, 102, 255, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f5', data: [], borderColor: 'rgba(255, 99, 132, 1)', backgroundColor: 'rgba(255, 99, 132, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: '選択中のポイント', data: [], borderColor: 'rgba(255, 99, 132, 1)', backgroundColor: 'rgba(255, 99, 132, 1)', pointRadius: 6, pointHoverRadius: 8, showLine: false }
]
},
options: {
responsive: true,
scales: {
x: {
type: 'linear',
position: 'center',
title: { display: true, text: 'x' },
min: -20,
max: 20
},
y: {
type: 'linear',
title: { display: true, text: 'y' }
}
}
}
});
function updateRangeUI() {
for (let i = 1; i < formulas.length; i++) {
const f = formulas[i];
if (f.useRange.checked) {
f.rangeBox.style.display = 'inline-flex';
} else {
f.rangeBox.style.display = 'none';
}
}
}
function resetVar(name) {
const range = document.getElementById('range' + name);
range.value = 0;
updateChart();
updateHash();
}
function setupRangeBounds(minInput, rangeInput, maxInput) {
const update = () => {
const minVal = parseFloat(minInput.value);
const maxVal = parseFloat(maxInput.value);
if (!isNaN(minVal) && !isNaN(maxVal) && minVal < maxVal) {
rangeInput.min = minVal;
rangeInput.max = maxVal;
if (parseFloat(rangeInput.value) < minVal) rangeInput.value = minVal;
if (parseFloat(rangeInput.value) > maxVal) rangeInput.value = maxVal;
}
updateChart();
updateHash();
};
minInput.addEventListener('input', update);
maxInput.addEventListener('input', update);
}
setupRangeBounds(minX, rangeX, maxX);
setupRangeBounds(minA, rangeA, maxA);
setupRangeBounds(minB, rangeB, maxB);
setupRangeBounds(minC, rangeC, maxC);
setupRangeBounds(minD, rangeD, maxD);
function compileFormula(rawFormula) {
let processed = rawFormula
.replace(/(\d)\s*([xabcd])/gi, '$1 * $2')
.replace(/([xabcd])\s*(\d)/gi, '$1 * $2')
.replace(/([xabcd])\s*([xabcd])/gi, '$1 * $2');
return new Function('x', 'a', 'b', 'c', 'd', 'return ' + processed);
}
function updateChart() {
updateRangeUI();
const xVal = parseFloat(rangeX.value);
const a = parseFloat(rangeA.value);
const b = parseFloat(rangeB.value);
const c = parseFloat(rangeC.value);
const d = parseFloat(rangeD.value);
valX.textContent = xVal.toFixed(1);
valA.textContent = a.toFixed(1);
valB.textContent = b.toFixed(1);
valC.textContent = c.toFixed(1);
valD.textContent = d.toFixed(1);
const xMinLimit = parseFloat(rangeX.min) || -20;
const xMaxLimit = parseFloat(rangeX.max) || 20;
myChart.options.scales.x.min = xMinLimit;
myChart.options.scales.x.max = xMaxLimit;
const parsedFormulas = [];
for (let i = 0; i < formulas.length; i++) {
const text = formulas[i].text.value.trim();
if (text === '') {
parsedFormulas.push(null);
continue;
}
try {
const func = compileFormula(text);
if (i === 0) {
parsedFormulas.push({ func, min: xMinLimit, max: xMaxLimit, useRange: false });
} else {
const useRange = formulas[i].useRange.checked;
let min = xMinLimit;
let max = xMaxLimit;
if (useRange) {
const fMin = parseFloat(formulas[i].minX.value);
const fMax = parseFloat(formulas[i].maxX.value);
min = Math.min(fMin, fMax);
max = Math.max(fMin, fMax);
}
parsedFormulas.push({ func, min, max, useRange });
}
} catch (e) {
parsedFormulas.push(null);
}
}
const allYValues = [];
const steps = 100;
const stepSize = (xMaxLimit - xMinLimit) / steps;
const datasetLines = [[], [], [], [], []];
for (let step = 0; step <= steps; step++) {
const x = xMinLimit + stepSize * step;
let activeIndex = -1;
for (let i = 4; i >= 1; i--) {
if (parsedFormulas[i] !== null) {
if (!parsedFormulas[i].useRange || (x >= parsedFormulas[i].min && x <= parsedFormulas[i].max)) {
activeIndex = i;
break;
}
}
}
if (activeIndex === -1 && parsedFormulas[0] !== null) {
activeIndex = 0;
}
if (activeIndex !== -1) {
try {
const y = parsedFormulas[activeIndex].func(x, a, b, c, d);
if (!isNaN(y) && isFinite(y)) {
datasetLines[activeIndex].push({ x: x, y: y });
allYValues.push(y);
}
} catch (e) {}
}
}
for (let i = 0; i < 5; i++) {
myChart.data.datasets[i].data = datasetLines[i];
}
let currentY = NaN;
let currentActiveIndex = -1;
for (let i = 4; i >= 1; i--) {
if (parsedFormulas[i] !== null) {
if (!parsedFormulas[i].useRange || (xVal >= parsedFormulas[i].min && xVal <= parsedFormulas[i].max)) {
currentActiveIndex = i;
break;
}
}
}
if (currentActiveIndex === -1 && parsedFormulas[0] !== null) {
currentActiveIndex = 0;
}
if (currentActiveIndex !== -1) {
try {
currentY = parsedFormulas[currentActiveIndex].func(xVal, a, b, c, d);
} catch (e) {}
}
const pointDataset = myChart.data.datasets[5];
pointDataset.data = (!isNaN(currentY) && isFinite(currentY)) ? [{ x: xVal, y: currentY }] : [];
if (!isNaN(currentY) && isFinite(currentY)) allYValues.push(currentY);
if (!isNaN(currentY) && isFinite(currentY)) {
outputY.textContent = `x = ${xVal.toFixed(1)} (f${currentActiveIndex + 1}) のときの y = ${currentY.toFixed(2)}`;
} else {
outputY.textContent = `y = 範囲外または計算エラー`;
}
allYValues.push(0);
if (allYValues.length > 0) {
let minY = Math.min(...allYValues);
let maxY = Math.max(...allYValues);
if (minY === maxY) {
minY -= 1;
maxY += 1;
}
const margin = (maxY - minY) * 0.15;
myChart.options.scales.y.min = minY - margin;
myChart.options.scales.y.max = maxY + margin;
}
myChart.update();
}
function updateHash() {
const params = new URLSearchParams({
memo: memo.value,
f1: formulas[0].text.value,
f2: formulas[1].text.value, f2Use: formulas[1].useRange.checked, f2Min: formulas[1].minX.value, f2Max: formulas[1].maxX.value,
f3: formulas[2].text.value, f3Use: formulas[2].useRange.checked, f3Min: formulas[2].minX.value, f3Max: formulas[2].maxX.value,
f4: formulas[3].text.value, f4Use: formulas[3].useRange.checked, f4Min: formulas[3].minX.value, f4Max: formulas[3].maxX.value,
f5: formulas[4].text.value, f5Use: formulas[4].useRange.checked, f5Min: formulas[4].minX.value, f5Max: formulas[4].maxX.value,
x: rangeX.value, minX: minX.value, maxX: maxX.value,
a: rangeA.value, minA: minA.value, maxA: maxA.value,
b: rangeB.value, minB: minB.value, maxB: maxB.value,
c: rangeC.value, minC: minC.value, maxC: maxC.value,
d: rangeD.value, minD: minD.value, maxD: maxD.value
});
history.replaceState(null, '', '#' + params.toString());
}
function restoreFromHash() {
const hash = location.hash.substring(1);
if (hash) {
const params = new URLSearchParams(hash);
if (params.has('memo')) memo.value = params.get('memo');
if (params.has('f1')) formulas[0].text.value = params.get('f1');
for (let i = 1; i < 5; i++) {
const idx = i + 1;
if (params.has('f' + idx)) formulas[i].text.value = params.get('f' + idx);
if (params.has('f' + idx + 'Use')) formulas[i].useRange.checked = params.get('f' + idx + 'Use') === 'true';
if (params.has('f' + idx + 'Min')) formulas[i].minX.value = params.get('f' + idx + 'Min');
if (params.has('f' + idx + 'Max')) formulas[i].maxX.value = params.get('f' + idx + 'Max');
}
const restoreVar = (minEl, rangeEl, maxEl, pName, minName, maxName) => {
if (params.has(minName)) minEl.value = params.get(minName);
if (params.has(maxName)) maxEl.value = params.get(maxName);
rangeEl.min = minEl.value;
rangeEl.max = maxEl.value;
if (params.has(pName)) rangeEl.value = params.get(pName);
};
restoreVar(minX, rangeX, maxX, 'x', 'minX', 'maxX');
restoreVar(minA, rangeA, maxA, 'a', 'minA', 'maxA');
restoreVar(minB, rangeB, maxB, 'b', 'minB', 'maxB');
restoreVar(minC, rangeC, maxC, 'c', 'minC', 'maxC');
restoreVar(minD, rangeD, maxD, 'd', 'minD', 'maxD');
} else {
memo.value = '';
formulas[0].text.value = '10x';
for (let i = 1; i < 5; i++) {
formulas[i].text.value = '';
formulas[i].useRange.checked = false;
}
}
updateChart();
}
function switchTab(tabName) {
tabBtns.forEach(btn => btn.classList.remove('active'));
memoView.classList.remove('active');
chartView.classList.remove('active');
if (tabName === 'memo') {
tabBtns[0].classList.add('active');
memoView.classList.add('active');
} else {
tabBtns[1].classList.add('active');
chartView.classList.add('active');
updateChart();
}
}
memo.addEventListener('input', () => { updateHash(); });
formulas[0].text.addEventListener('input', () => { updateHash(); updateChart(); });
for (let i = 1; i < formulas.length; i++) {
formulas[i].text.addEventListener('input', () => { updateHash(); updateChart(); });
formulas[i].useRange.addEventListener('change', () => { updateHash(); updateChart(); });
formulas[i].minX.addEventListener('input', () => { updateHash(); updateChart(); });
formulas[i].maxX.addEventListener('input', () => { updateHash(); updateChart(); });
}
[rangeX, rangeA, rangeB, rangeC, rangeD].forEach(range => {
range.addEventListener('input', () => { updateHash(); updateChart(); });
});
document.getElementById('copyBtn').addEventListener('click', () => {
navigator.clipboard.writeText(location.href).then(() => alert('共有用URLをコピーしました!'));
});
document.getElementById('screenshotBtn').addEventListener('click', () => {
const imageURL = myChart.toBase64Image();
const link = document.createElement('a');
link.href = imageURL;
link.download = 'graph_screenshot.png';
link.click();
});
restoreFromHash();
</script>
</body>
</html>