这是一个非常实用的HTML文件,可以脱离复杂的构建环境直接在任何浏览器中双击运行。
请将以下代码保存为 MagneticAnalyzer.html,然后双击在浏览器中打开即可:
使用时请查看左侧专门的「数据格式与使用说明」面板及输入数据的格式(用空格、制表符分隔均可,可直接从 Origin 或 TXT 中复制数据)。 1/M 可以是由用户提供(第三列),或者程序自动计算(仅有两列时)。
有效磁矩 (mu_eff) 计算的量纲前提:如果需要绝对数值准确,必须采用摩尔磁化率(emu/mol/Oe)。
示意数据及图例

源代码如下
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>磁性数据全谱分析仪</title>
<!-- 引入 Tailwind CSS 用于界面排版 -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 ECharts 用于数据绘图 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<style>
/* 简单自定义滚动条 */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #f1f5f9; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body class="bg-slate-50 text-slate-800 font-sans h-screen flex flex-col overflow-hidden">
<!-- 顶部导航栏 -->
<header class="bg-white border-b border-slate-200 px-6 py-4 flex items-center justify-between shadow-sm shrink-0 z-20">
<div class="flex items-center gap-2">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>
<h1 class="text-xl font-bold text-slate-800">磁性数据全谱分析仪 (HTML独立版)</h1>
</div>
<div class="text-sm text-slate-500">
支持 M-T 曲线导数分析与居里-外斯 (Curie-Weiss) 拟合
</div>
</header>
<div class="flex flex-1 overflow-hidden">
<!-- 左侧控制面板 -->
<div class="w-96 bg-white border-r border-slate-200 flex flex-col shadow-sm z-10 shrink-0 overflow-y-auto">
<!-- 使用说明区块 -->
<div class="p-5 bg-blue-50/50 border-b border-blue-100">
<h2 class="font-bold text-blue-800 mb-3 flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
数据格式与使用说明
</h2>
<div class="text-xs text-slate-600 space-y-3 leading-relaxed">
<div>
<strong class="text-slate-800 block mb-1">【数据要求】</strong>
请提供 2 列或 3 列数值数据,各列之间用<span class="text-blue-600 font-semibold">空格、Tab键或逗号</span>分隔:<br/>
<ul class="list-disc pl-4 mt-1">
<li>第 1 列:<b>温度 T</b> (K)</li>
<li>第 2 列:<b>磁化强度 M</b> 或 <b>磁化率 χ</b></li>
<li>第 3 列:<b>1/M</b> (可选,程序默认自动计算 1/M)</li>
</ul>
</div>
<div>
<strong class="text-slate-800 block mb-1">【物理单位提示】</strong>
要获得准确的<b>有效磁矩 (μ_eff)</b>,输入的 M 必须是<b>摩尔磁化率 (emu/mol·Oe)</b>。<br/>
若输入为质量磁化率 (emu/g) 或原始磁矩,需自行转换后方可使 μ_eff 的绝对值具有物理意义 (相变点与外斯温度的判定不受影响)。
</div>
<div>
<strong class="text-slate-800 block mb-1">【操作步骤】</strong>
1. 在下方粘贴数据。<br/>
2. 在右侧图表中观察顺磁区(高温直线区)。<br/>
3. 调整下方的"拟合温区"滑块,使拟合虚线与实际的高温直线部分完美重合。
</div>
</div>
</div>
<!-- 数据输入区块 -->
<div class="flex-1 p-5 flex flex-col min-h-[300px]">
<h2 class="font-semibold text-sm mb-2 flex items-center gap-2 text-slate-700">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
粘贴原始数据
</h2>
<textarea id="dataInput" class="flex-1 w-full p-3 text-xs font-mono border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none resize-none whitespace-pre overflow-x-auto shadow-inner bg-slate-50" placeholder="例如:
3.0002 0.5686
4.2090 0.5694
5.1382 0.5717
..."></textarea>
</div>
<!-- 拟合控制区块 -->
<div class="p-5 border-t border-slate-200 bg-slate-50">
<h2 class="font-semibold text-sm mb-4 flex items-center gap-2 text-slate-700">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"></path></svg>
居里-外斯拟合温区 (K)
</h2>
<div class="flex items-center gap-3">
<input type="number" id="fitMin" value="150" class="w-full px-3 py-2 text-sm border border-slate-300 rounded shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 text-center" />
<span class="text-slate-400 font-bold">~</span>
<input type="number" id="fitMax" value="300" class="w-full px-3 py-2 text-sm border border-slate-300 rounded shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 text-center" />
</div>
</div>
</div>
<!-- 右侧图表与结果展示区 -->
<div class="flex-1 flex flex-col bg-slate-100 overflow-y-auto relative">
<!-- 结果指标卡片 -->
<div class="grid grid-cols-3 gap-5 p-6 pb-2 shrink-0">
<div class="bg-white rounded-xl p-5 shadow-sm border border-slate-200 flex flex-col relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-blue-500"></div>
<span class="text-sm text-slate-500 font-medium mb-1">相变温度 (Tc)</span>
<div class="flex items-baseline gap-1 mt-1">
<span id="tcVal" class="text-3xl font-bold text-slate-800">--</span>
<span class="text-sm font-medium text-slate-400">K</span>
</div>
<span class="text-xs text-blue-600/80 mt-2 bg-blue-50 px-2 py-1 rounded w-max">基于 dM/dT 极小值</span>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm border border-slate-200 flex flex-col relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-indigo-500"></div>
<span class="text-sm text-slate-500 font-medium mb-1">峰值/阻挡温度 (Tp)</span>
<div class="flex items-baseline gap-1 mt-1">
<span id="tpVal" class="text-3xl font-bold text-slate-800">--</span>
<span class="text-sm font-medium text-slate-400">K</span>
</div>
<span class="text-xs text-indigo-600/80 mt-2 bg-indigo-50 px-2 py-1 rounded w-max">基于 M 极大值</span>
</div>
<div class="bg-white rounded-xl p-5 shadow-sm border border-slate-200 flex flex-col relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-teal-500"></div>
<span class="text-sm text-slate-500 font-medium mb-1">外斯温度 (θ_CW)</span>
<div class="flex items-baseline gap-1 mt-1">
<span id="thetaVal" class="text-3xl font-bold text-slate-800">--</span>
<span class="text-sm font-medium text-slate-400">K</span>
</div>
<span class="text-xs text-teal-600/80 mt-2 bg-teal-50 px-2 py-1 rounded w-max">基于设定的高温区拟合</span>
</div>
</div>
<!-- 图表容器 -->
<div class="p-6 flex-1 flex flex-col gap-6 min-h-[700px]">
<!-- 图表 1: M 和 dM/dT -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200 flex-1 min-h-[300px] flex flex-col">
<h3 class="text-base font-bold text-slate-700 mb-2 border-l-4 border-slate-400 pl-2">磁化强度 M & 微商 dM/dT 随温度演化</h3>
<div id="chart1" class="flex-1 w-full h-full"></div>
</div>
<!-- 图表 2: 1/M 居里外斯拟合 -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200 flex-1 min-h-[300px] flex flex-col relative">
<h3 class="text-base font-bold text-slate-700 mb-3 border-l-4 border-teal-500 pl-2 flex justify-between items-center">
居里-外斯定律拟合 (1/M vs T)
<span id="fitRangeDisplay" class="text-xs font-normal text-slate-500 bg-slate-100 px-3 py-1 rounded-full border border-slate-200"></span>
</h3>
<!-- 拟合详细参数展示框 -->
<div class="mb-3 p-3 bg-teal-50/50 border border-teal-100 rounded-lg text-sm text-teal-900 grid grid-cols-2 gap-y-2">
<div><strong class="opacity-75">拟合方程:</strong> <span id="fitEquation" class="font-mono">--</span></div>
<div><strong class="opacity-75">决定系数 R²:</strong> <span id="r2Val" class="font-mono">--</span></div>
<div><strong class="opacity-75">居里常数 C:</strong> <span id="cVal" class="font-mono">--</span></div>
<div><strong class="opacity-75">有效磁矩 μ_eff:</strong> <span id="muEffVal" class="font-mono text-teal-700 font-bold">--</span> μB <span class="text-xs opacity-50">(需确保M为摩尔量)</span></div>
</div>
<div id="chart2" class="flex-1 w-full h-full"></div>
</div>
</div>
</div>
</div>
<script>
// DOM 元素引用
const dataInput = document.getElementById('dataInput');
const fitMinInput = document.getElementById('fitMin');
const fitMaxInput = document.getElementById('fitMax');
const tcVal = document.getElementById('tcVal');
const tpVal = document.getElementById('tpVal');
const thetaVal = document.getElementById('thetaVal');
const fitEquation = document.getElementById('fitEquation');
const r2Val = document.getElementById('r2Val');
const cVal = document.getElementById('cVal');
const muEffVal = document.getElementById('muEffVal');
const fitRangeDisplay = document.getElementById('fitRangeDisplay');
// ECharts 实例
let chart1, chart2;
let parsedData = [];
// 初始化图表
function initCharts() {
chart1 = echarts.init(document.getElementById('chart1'));
chart2 = echarts.init(document.getElementById('chart2'));
// 监听窗口大小变化
window.addEventListener('resize', () => {
chart1.resize();
chart2.resize();
});
}
// 解析输入文本
function parseData(text) {
const lines = text.trim().split('\n');
let dataObj = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// 忽略空行和表头
if (!line || !/^[0-9.-]/.test(line)) continue;
// 支持空格、制表符、逗号分割
const parts = line.split(/[\t\s,]+/).filter(Boolean);
if (parts.length >= 2) {
const t = parseFloat(parts[0]);
const m = parseFloat(parts[1]);
// 如果存在第三列则使用第三列作为1/M,否则自动计算
const invM = parts.length >= 3 ? parseFloat(parts[2]) : (1 / m);
if (!isNaN(t) && !isNaN(m)) {
dataObj.push({ t, m, invM });
}
}
}
// 按温度升序排序
dataObj.sort((a, b) => a.t - b.t);
// 中心差分法计算 dM/dT
for (let i = 0; i < dataObj.length; i++) {
if (i === 0 || i === dataObj.length - 1) {
dataObj[i].dmdt = 0;
} else {
const dt = dataObj[i + 1].t - dataObj[i - 1].t;
const dm = dataObj[i + 1].m - dataObj[i - 1].m;
dataObj[i].dmdt = dt !== 0 ? dm / dt : 0;
}
}
parsedData = dataObj;
updateDashboard();
}
// 核心计算与UI更新
function updateDashboard() {
if (parsedData.length === 0) {
clearDashboard();
return;
}
// 1. 寻找 T_peak 和 T_c
let maxM = -Infinity;
let peakT = null;
let minDmdt = Infinity;
let tc = null;
parsedData.forEach(d => {
if (d.m > maxM) {
maxM = d.m;
peakT = d.t;
}
// 限定区间寻找 dM/dT 的极小值以避免边缘噪声
if (d.t > 10 && d.t < 150 && d.dmdt < minDmdt) {
minDmdt = d.dmdt;
tc = d.t;
}
});
// 2. 居里-外斯线性拟合
const minT = parseFloat(fitMinInput.value) || 0;
const maxT = parseFloat(fitMaxInput.value) || 300;
const fitData = parsedData.filter(d => d.t >= minT && d.t <= maxT);
let theta = null, c = null, slope = 0, intercept = 0, r2 = null, mu_eff = null;
if (fitData.length > 3) {
const n = fitData.length;
let sum_t = 0, sum_invM = 0, sum_t_invM = 0, sum_t_t = 0;
fitData.forEach(d => {
sum_t += d.t;
sum_invM += d.invM;
sum_t_invM += d.t * d.invM;
sum_t_t += d.t * d.t;
});
// 最小二乘法
slope = (n * sum_t_invM - sum_t * sum_invM) / (n * sum_t_t - sum_t * sum_t);
intercept = (sum_invM - slope * sum_t) / n;
c = 1 / slope;
theta = -intercept / slope;
mu_eff = c > 0 ? 2.827 * Math.sqrt(c) : null;
// 计算 R²
const mean_invM = sum_invM / n;
let ssTot = 0, ssRes = 0;
fitData.forEach(d => {
const predicted = slope * d.t + intercept;
ssTot += Math.pow(d.invM - mean_invM, 2);
ssRes += Math.pow(d.invM - predicted, 2);
});
r2 = ssTot !== 0 ? 1 - (ssRes / ssTot) : 0;
}
// 更新文本显示
tcVal.innerText = tc ? tc.toFixed(1) : '--';
tpVal.innerText = peakT ? peakT.toFixed(1) : '--';
thetaVal.innerText = theta ? theta.toFixed(1) : '--';
fitRangeDisplay.innerText = `区间: ${minT} K - ${maxT} K`;
if (slope) {
const sign = intercept > 0 ? '+' : '-';
fitEquation.innerText = `1/M = ${slope.toFixed(5)}T ${sign} ${Math.abs(intercept).toFixed(5)}`;
r2Val.innerText = r2 ? r2.toFixed(5) : '--';
cVal.innerText = c ? c.toFixed(4) : '--';
muEffVal.innerText = mu_eff ? mu_eff.toFixed(2) : '--';
} else {
fitEquation.innerText = '--';
r2Val.innerText = '--';
cVal.innerText = '--';
muEffVal.innerText = '--';
}
// 更新 ECharts
renderCharts(tc, theta, slope, intercept);
}
function clearDashboard() {
tcVal.innerText = '--';
tpVal.innerText = '--';
thetaVal.innerText = '--';
fitEquation.innerText = '--';
r2Val.innerText = '--';
cVal.innerText = '--';
muEffVal.innerText = '--';
chart1.clear();
chart2.clear();
}
// 渲染图表
function renderCharts(tc, theta, slope, intercept) {
// 构造 Chart 1 数据系列
const mSeries = parsedData.map(d => [d.t, d.m]);
const dmdtSeries = parsedData.map(d => [d.t, d.dmdt]);
const option1 = {
tooltip: { trigger: 'axis', textStyle: { fontSize: 12 } },
legend: { data: ['M (磁化强度)', 'dM/dT (微商)'], top: 0 },
grid: { left: '8%', right: '8%', bottom: '10%', top: '15%' },
xAxis: {
type: 'value', name: 'T (K)', nameLocation: 'middle', nameGap: 25,
scale: true, splitLine: { lineStyle: { type: 'dashed', color: '#e2e8f0' } }
},
yAxis: [
{ type: 'value', name: 'M', position: 'left', splitLine: { show: false } },
{ type: 'value', name: 'dM/dT', position: 'right', splitLine: { show: false } }
],
series: [
{
name: 'M (磁化强度)', type: 'line', data: mSeries,
showSymbol: false, itemStyle: { color: '#1e40af' }, lineStyle: { width: 2 },
markLine: tc ? {
symbol: ['none', 'none'],
data: [{ xAxis: tc, name: 'Tc', lineStyle: { color: '#3b82f6', type: 'dashed' }, label: { formatter: 'Tc = '+tc.toFixed(1)+'K' } }]
} : null
},
{
name: 'dM/dT (微商)', type: 'line', yAxisIndex: 1, data: dmdtSeries,
showSymbol: false, itemStyle: { color: '#ef4444' }, lineStyle: { width: 1.5, opacity: 0.8 }
}
]
};
chart1.setOption(option1);
// 构造 Chart 2 数据系列 (仅在 T > 外斯温度 时画拟合线)
const invMSeries = parsedData.map(d => [d.t, d.invM]);
let fitLineData = [];
if (slope && intercept) {
const maxT = parsedData[parsedData.length - 1].t;
// 让拟合线画到横坐标相交点附近
const startT = theta ? Math.max(0, theta - 20) : 0;
fitLineData = [
[startT, slope * startT + intercept],
[maxT, slope * maxT + intercept]
];
}
const option2 = {
tooltip: { trigger: 'axis', textStyle: { fontSize: 12 } },
legend: { data: ['1/M (实验数据)', 'Curie-Weiss 拟合'], top: 0 },
grid: { left: '8%', right: '8%', bottom: '10%', top: '15%' },
xAxis: {
type: 'value', name: 'T (K)', nameLocation: 'middle', nameGap: 25,
scale: true, splitLine: { lineStyle: { type: 'dashed', color: '#e2e8f0' } }
},
yAxis: {
type: 'value', name: '1/M', splitLine: { show: false }
},
series: [
{
name: '1/M (实验数据)', type: 'scatter', symbolSize: 4, data: invMSeries,
itemStyle: { color: '#64748b' },
markLine: theta ? {
symbol: ['none', 'none'],
data: [
{ xAxis: theta, name: 'θ', lineStyle: { color: '#0d9488', type: 'dashed' }, label: { formatter: 'θ = '+theta.toFixed(1)+'K' } },
{ yAxis: 0, lineStyle: { color: '#cbd5e1', type: 'solid' }, label: {show:false} }
]
} : null
},
{
name: 'Curie-Weiss 拟合', type: 'line', data: fitLineData,
showSymbol: false, itemStyle: { color: '#0d9488' }, lineStyle: { width: 2, type: 'dashed' }
}
]
};
chart2.setOption(option2);
}
// 绑定事件监听
dataInput.addEventListener('input', (e) => parseData(e.target.value));
fitMinInput.addEventListener('change', updateDashboard);
fitMaxInput.addEventListener('change', updateDashboard);
// 初始化
window.onload = () => {
initCharts();
// 如果输入框有默认值,执行一次解析
if(dataInput.value.trim() !== '') {
parseData(dataInput.value);
}
};
</script>
</body>
</html>