Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
"fileList.disabled": "Disabled",
"fileList.config": "Configure file {{name}}",
"fileList.delete": "Remove file {{name}}",
"comparison.title": "⚖️ Compare Mode",
"comparison.title": "Compare Mode",
"comparison.select": "Select comparison mode",
"comparison.normal": "📊 Mean Error (normal)",
"comparison.multiFileMode": "Multi-file comparison mode",
"comparison.modeBaseline": "Baseline vs others",
"comparison.modePairwise": "Pairwise comparisons",
"comparison.baselineFile": "Baseline file",
"comparison.normal": "Mean Error (normal)",
"comparison.normalDesc": "Mean error without absolute value",
"comparison.absolute": "📈 Mean Error (absolute)",
"comparison.absolute": "Mean Error (absolute)",
"comparison.absoluteDesc": "Mean of absolute differences",
"comparison.relativeNormal": "📉 Relative Error (normal)",
"comparison.relativeNormal": "Relative Error (normal)",
"comparison.relativeNormalDesc": "Relative error without absolute value",
"comparison.relative": "📊 Mean Relative Error (absolute)",
"comparison.relative": "Mean Relative Error (absolute)",
"comparison.relativeDesc": "Mean of absolute relative error",
"themeToggle.aria": "Toggle theme",
"chart.noData": "📊 No data",
Expand Down Expand Up @@ -61,11 +65,18 @@
"chart.area": "Chart display area",
"chart.actions": "Chart action buttons",
"chart.diffLabel": "{{title}} difference",
"comparison.panelTitle": "⚖️ {{key}} comparison ({{mode}})",
"comparison.panelTitle": "{{key}} comparison ({{mode}})",
"comparison.meanNormal": "Mean error (normal): {{value}}",
"comparison.meanAbsolute": "Mean error (absolute): {{value}}",
"comparison.relativeError": "Relative error (normal): {{value}}",
"comparison.meanRelative": "Mean relative error (absolute): {{value}}",
"comparison.pair": "Pair",
"comparison.meanNormalLabel": "Mean error (normal)",
"comparison.meanAbsoluteLabel": "Mean error (absolute)",
"comparison.relativeErrorLabel": "Relative error (normal)",
"comparison.meanRelativeLabel": "Mean relative error (absolute)",
"comparison.maxAbsoluteLabel": "Max absolute error",
"comparison.maxRelativeLabel": "Max relative error",
"regex.preset": "Preset",
"regex.selectPreset": "Select preset",
"regex.mode": "Match Mode",
Expand Down
23 changes: 17 additions & 6 deletions public/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
"fileList.disabled": "已禁用",
"fileList.config": "配置文件 {{name}}",
"fileList.delete": "删除文件 {{name}}",
"comparison.title": "⚖️ 对比模式",
"comparison.title": "对比模式",
"comparison.select": "选择数据对比模式",
"comparison.normal": "📊 平均误差 (normal)",
"comparison.multiFileMode": "多文件对比模式",
"comparison.modeBaseline": "基准文件对比",
"comparison.modePairwise": "成对比较",
"comparison.baselineFile": "基准文件",
"comparison.normal": "平均误差 (normal)",
"comparison.normalDesc": "未取绝对值的平均误差",
"comparison.absolute": "📈 平均误差 (absolute)",
"comparison.absolute": "平均误差 (absolute)",
"comparison.absoluteDesc": "绝对值差值的平均",
"comparison.relativeNormal": "📉 相对误差 (normal)",
"comparison.relativeNormal": "相对误差 (normal)",
"comparison.relativeNormalDesc": "不取绝对值的相对误差",
"comparison.relative": "📊 平均相对误差 (absolute)",
"comparison.relative": "平均相对误差 (absolute)",
"comparison.relativeDesc": "绝对相对误差的平均",
"themeToggle.aria": "切换主题",
"chart.noData": "📊 暂无数据",
Expand Down Expand Up @@ -61,11 +65,18 @@
"chart.area": "图表显示区域",
"chart.actions": "图表操作按钮",
"chart.diffLabel": "{{title}} 差值",
"comparison.panelTitle": "⚖️ {{key}} 对比分析 ({{mode}})",
"comparison.panelTitle": "{{key}} 对比分析 ({{mode}})",
"comparison.meanNormal": "平均误差 (normal): {{value}}",
"comparison.meanAbsolute": "平均误差 (absolute): {{value}}",
"comparison.relativeError": "相对误差 (normal): {{value}}",
"comparison.meanRelative": "平均相对误差 (absolute): {{value}}",
"comparison.pair": "文件对",
"comparison.meanNormalLabel": "平均误差 (normal)",
"comparison.meanAbsoluteLabel": "平均误差 (absolute)",
"comparison.relativeErrorLabel": "相对误差 (normal)",
"comparison.meanRelativeLabel": "平均相对误差 (absolute)",
"comparison.maxAbsoluteLabel": "最大绝对误差",
"comparison.maxRelativeLabel": "最大相对误差",
"regex.preset": "预设",
"regex.selectPreset": "选择预设",
"regex.mode": "匹配模式",
Expand Down
22 changes: 21 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ function App() {
});

const [compareMode, setCompareMode] = useState('normal');
const [multiFileMode, setMultiFileMode] = useState('baseline');
const [baselineFile, setBaselineFile] = useState('');
const [relativeBaseline, setRelativeBaseline] = useState(0.002);
const [absoluteBaseline, setAbsoluteBaseline] = useState(0.005);
const [configModalOpen, setConfigModalOpen] = useState(false);
Expand All @@ -55,6 +57,17 @@ function App() {
const [maxStep, setMaxStep] = useState(0);
const [sidebarVisible, setSidebarVisible] = useState(true);
const savingDisabledRef = useRef(false);
const enabledFiles = uploadedFiles.filter(file => file.enabled);

useEffect(() => {
if (enabledFiles.length > 0) {
if (!enabledFiles.find(f => f.name === baselineFile)) {
setBaselineFile(enabledFiles[0].name);
}
} else {
setBaselineFile('');
}
}, [enabledFiles, baselineFile]);

// Persist configuration to localStorage
useEffect(() => {
Expand Down Expand Up @@ -387,10 +400,15 @@ function App() {
onFileConfig={handleFileConfig}
/>

{uploadedFiles.filter(file => file.enabled).length === 2 && (
{enabledFiles.length >= 2 && (
<ComparisonControls
compareMode={compareMode}
onCompareModeChange={setCompareMode}
files={enabledFiles}
baseline={baselineFile}
onBaselineChange={setBaselineFile}
multiFileMode={multiFileMode}
onMultiFileModeChange={setMultiFileMode}
/>
)}

Expand Down Expand Up @@ -475,6 +493,8 @@ function App() {
files={uploadedFiles}
metrics={globalParsingConfig.metrics}
compareMode={compareMode}
multiFileMode={multiFileMode}
baselineFile={baselineFile}
relativeBaseline={relativeBaseline}
absoluteBaseline={absoluteBaseline}
xRange={xRange}
Expand Down
136 changes: 87 additions & 49 deletions src/components/ChartContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export default function ChartContainer({
files,
metrics = [],
compareMode,
multiFileMode = 'baseline',
baselineFile,
relativeBaseline = 0.002,
absoluteBaseline = 0.005,
xRange = { min: undefined, max: undefined },
Expand Down Expand Up @@ -531,40 +533,74 @@ export default function ChartContainer({
elements: { point: { radius: 0 } }
}), [xRange, onXRangeChange]);

const createComparisonChartData = (item1, item2, title) => {
const comparisonData = getComparisonData(item1.data, item2.data, compareMode);
const baseline =
const buildComparisonChartData = (dataArray) => {
const baselineVal =
compareMode === 'relative' || compareMode === 'relative-normal'
? relativeBaseline
: compareMode === 'absolute'
? absoluteBaseline
: 0;
const datasets = [
{
label: t('chart.diffLabel', { title }),
data: comparisonData,
borderColor: '#dc2626',
backgroundColor: '#dc2626',
const datasets = [];
const stats = [];
const addPair = (base, target, colorIdx) => {
const diffData = getComparisonData(base.data, target.data, compareMode);
const color = colors[colorIdx % colors.length];
datasets.push({
label: `${target.name} vs ${base.name}`,
data: diffData,
borderColor: color,
backgroundColor: color,
borderWidth: 2,
fill: false,
tension: 0,
pointRadius: 0,
pointHoverRadius: 4,
pointBackgroundColor: '#dc2626',
pointBorderColor: '#dc2626',
pointBackgroundColor: color,
pointBorderColor: color,
pointBorderWidth: 1,
pointHoverBackgroundColor: '#dc2626',
pointHoverBorderColor: '#dc2626',
pointHoverBackgroundColor: color,
pointHoverBorderColor: color,
pointHoverBorderWidth: 1,
animation: false,
animations: { colors: false, x: false, y: false },
},
];
if (baseline > 0 && (compareMode === 'relative' || compareMode === 'relative-normal' || compareMode === 'absolute')) {
const baselineData = comparisonData.map(p => ({ x: p.x, y: baseline }));
});
const normalDiff = getComparisonData(base.data, target.data, 'normal');
const absDiff = getComparisonData(base.data, target.data, 'absolute');
const relNormalDiff = getComparisonData(base.data, target.data, 'relative-normal');
const relDiff = getComparisonData(base.data, target.data, 'relative');
const mean = arr => (arr.reduce((s, p) => s + p.y, 0) / arr.length) || 0;
const max = arr => arr.reduce((m, p) => (p.y > m ? p.y : m), 0);
stats.push({
label: `${target.name} vs ${base.name}`,
meanNormal: mean(normalDiff),
meanAbsolute: mean(absDiff),
relativeError: mean(relNormalDiff),
meanRelative: mean(relDiff),
maxAbsolute: max(absDiff),
maxRelative: max(relDiff)
});
};

let colorIdx = 0;
if (multiFileMode === 'baseline') {
const base = dataArray.find(d => d.name === baselineFile) || dataArray[0];
dataArray.forEach(item => {
if (item.name === base.name) return;
addPair(base, item, colorIdx++);
});
} else {
for (let i = 0; i < dataArray.length; i++) {
for (let j = i + 1; j < dataArray.length; j++) {
addPair(dataArray[i], dataArray[j], colorIdx++);
}
}
}

if (datasets.length > 0 && baselineVal > 0 && (compareMode === 'relative' || compareMode === 'relative-normal' || compareMode === 'absolute')) {
const baseData = datasets[0].data.map(p => ({ x: p.x, y: baselineVal }));
datasets.push({
label: 'Baseline',
data: baselineData,
data: baseData,
borderColor: '#10b981',
backgroundColor: '#10b981',
borderWidth: 2,
Expand All @@ -583,7 +619,8 @@ export default function ChartContainer({
animations: { colors: false, x: false, y: false },
});
}
return { datasets };

return { datasets, stats };
};

if (parsedData.length === 0) {
Expand Down Expand Up @@ -626,7 +663,7 @@ export default function ChartContainer({
const metricElements = metrics.map((metric, idx) => {
const key = metric.name || metric.keyword || `metric${idx + 1}`;
const dataArray = metricDataArrays[key] || [];
const showComparison = dataArray.length === 2;
const showComparison = dataArray.length >= 2;

const yRange = calculateYRange(dataArray);
const options = {
Expand All @@ -638,28 +675,11 @@ export default function ChartContainer({
};

let stats = null;
if (showComparison) {
const normalDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'normal');
const absDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'absolute');
const relNormalDiff = getComparisonData(
dataArray[0].data,
dataArray[1].data,
'relative-normal'
);
const relDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'relative');
const mean = arr => (arr.reduce((s, p) => s + p.y, 0) / arr.length) || 0;
stats = {
meanNormal: mean(normalDiff),
meanAbsolute: mean(absDiff),
relativeError: mean(relNormalDiff),
meanRelative: mean(relDiff)
};
}

let comparisonChart = null;
if (showComparison) {
const compData = createComparisonChartData(dataArray[0], dataArray[1], key);
const compRange = calculateYRange(compData.datasets);
const compResult = buildComparisonChartData(dataArray);
stats = compResult.stats.length > 0 ? compResult.stats : null;
const compRange = calculateYRange(compResult.datasets);
const compOptions = {
...chartOptions,
scales: {
Expand Down Expand Up @@ -705,7 +725,7 @@ export default function ChartContainer({
onRegisterChart={registerChart}
onSyncHover={syncHoverToAllCharts}
syncRef={syncLockRef}
data={compData}
data={{ datasets: compResult.datasets }}
options={compOptions}
/>
</ResizablePanel>
Expand Down Expand Up @@ -760,14 +780,32 @@ export default function ChartContainer({
</ResizablePanel>
{comparisonChart}
{stats && (
<div className="card">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{key} {t('chart.diffStats')}</h4>
<div className="space-y-1 text-xs">
<p>{t('comparison.meanNormal', { value: stats.meanNormal.toFixed(6) })}</p>
<p>{t('comparison.meanAbsolute', { value: stats.meanAbsolute.toFixed(6) })}</p>
<p>{t('comparison.relativeError', { value: stats.relativeError.toFixed(6) })}</p>
<p>{t('comparison.meanRelative', { value: stats.meanRelative.toFixed(6) })}</p>
</div>
<div className="card overflow-x-auto">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{key} {t('chart.diffStats')}</h4>
<table className="min-w-full text-xs">
<thead>
<tr className="text-left">
<th className="pr-2">{t('comparison.pair')}</th>
<th className="text-right">{t('comparison.meanNormalLabel')}</th>
<th className="text-right">{t('comparison.meanAbsoluteLabel')}</th>
<th className="text-right">{t('comparison.maxAbsoluteLabel')}</th>
<th className="text-right">{t('comparison.meanRelativeLabel')}</th>
<th className="text-right">{t('comparison.maxRelativeLabel')}</th>
</tr>
</thead>
<tbody>
{stats.map(s => (
<tr key={s.label} className="border-t border-gray-200 dark:border-gray-700">
<td className="pr-2 py-1">{s.label}</td>
<td className="text-right py-1">{s.meanNormal.toFixed(6)}</td>
<td className="text-right py-1">{s.meanAbsolute.toFixed(6)}</td>
<td className="text-right py-1">{s.maxAbsolute.toFixed(6)}</td>
<td className="text-right py-1">{s.meanRelative.toFixed(6)}</td>
<td className="text-right py-1">{s.maxRelative.toFixed(6)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
Expand Down
Loading