diff --git a/src/App.jsx b/src/App.jsx index 6e9e216..fce2a15 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import { ComparisonControls } from './components/ComparisonControls'; import { Header } from './components/Header'; import { FileConfigModal } from './components/FileConfigModal'; import { PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { mergeFilesWithReplacement } from './utils/mergeFiles.js'; function App() { const [uploadedFiles, setUploadedFiles] = useState([]); @@ -54,7 +55,7 @@ function App() { } } })); - setUploadedFiles(prev => [...prev, ...filesWithDefaults]); + setUploadedFiles(prev => mergeFilesWithReplacement(prev, filesWithDefaults)); }, [globalParsingConfig]); // 全局文件处理函数 diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx index aa316e3..d2f9365 100644 --- a/src/components/ChartContainer.jsx +++ b/src/components/ChartContainer.jsx @@ -13,6 +13,7 @@ import { Legend, } from 'chart.js'; import zoomPlugin from 'chartjs-plugin-zoom'; +import { getMinSteps } from "../utils/getMinSteps.js"; ChartJS.register( CategoryScale, @@ -164,6 +165,17 @@ export default function ChartContainer({ onMaxStepChange(maxStep); }, [parsedData, onMaxStepChange]); + useEffect(() => { + const minSteps = getMinSteps(parsedData); + if (minSteps > 0) { + onXRangeChange(prev => { + const next = { min: 0, max: minSteps - 1 }; + if (prev.min === next.min && prev.max === next.max) return prev; + return next; + }); + } + }, [parsedData, onXRangeChange]); + const colors = ['#ef4444', '#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#f97316']; const createChartData = dataArray => ({ datasets: dataArray.map((item, index) => { diff --git a/src/utils/__tests__/getMinSteps.test.js b/src/utils/__tests__/getMinSteps.test.js new file mode 100644 index 0000000..79c4ab8 --- /dev/null +++ b/src/utils/__tests__/getMinSteps.test.js @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { getMinSteps } from '../getMinSteps.js'; + +describe('getMinSteps', () => { + it('returns minimum length among enabled files', () => { + const parsed = [ + { enabled: true, metricsData: { a: [{},{},{}], b: [{},{}] } }, + { enabled: true, metricsData: { a: [{},{},{} ,{}], b: [{},{} ,{}] } }, + ]; + expect(getMinSteps(parsed)).toBe(3); + }); + + it('ignores disabled files', () => { + const parsed = [ + { enabled: false, metricsData: { a: [{},{},{}] } }, + { enabled: true, metricsData: { a: [{},{}] } } + ]; + expect(getMinSteps(parsed)).toBe(2); + }); +}); diff --git a/src/utils/__tests__/mergeFiles.test.js b/src/utils/__tests__/mergeFiles.test.js new file mode 100644 index 0000000..840440d --- /dev/null +++ b/src/utils/__tests__/mergeFiles.test.js @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { mergeFilesWithReplacement } from '../mergeFiles.js'; + +describe('mergeFilesWithReplacement', () => { + it('replaces file with same name and keeps config', () => { + const prev = [{ name: 'log1.txt', id: '1', enabled: true, config: { a: 1 }, content: 'old' }]; + const newFile = { name: 'log1.txt', id: '2', enabled: true, config: {}, content: 'new' }; + const result = mergeFilesWithReplacement(prev, [newFile]); + expect(result).toHaveLength(1); + expect(result[0].content).toBe('new'); + expect(result[0].config).toEqual({ a: 1 }); + }); + + it('adds new file when name does not exist', () => { + const prev = [{ name: 'log1.txt', id: '1', enabled: true, config: {}, content: 'old' }]; + const newFile = { name: 'log2.txt', id: '2', enabled: true, config: {}, content: 'new' }; + const result = mergeFilesWithReplacement(prev, [newFile]); + expect(result).toHaveLength(2); + }); +}); diff --git a/src/utils/getMinSteps.js b/src/utils/getMinSteps.js new file mode 100644 index 0000000..9ae22f2 --- /dev/null +++ b/src/utils/getMinSteps.js @@ -0,0 +1,10 @@ +export function getMinSteps(parsedFiles = []) { + const enabled = parsedFiles.filter(f => f.enabled !== false); + if (enabled.length === 0) return 0; + const lengths = enabled.map(file => { + const datasets = Object.values(file.metricsData || {}); + if (datasets.length === 0) return 0; + return datasets.reduce((m, d) => Math.max(m, d.length), 0); + }); + return Math.min(...lengths); +} diff --git a/src/utils/mergeFiles.js b/src/utils/mergeFiles.js new file mode 100644 index 0000000..b48f6b3 --- /dev/null +++ b/src/utils/mergeFiles.js @@ -0,0 +1,13 @@ +export function mergeFilesWithReplacement(prevFiles, newFiles) { + const updated = [...prevFiles]; + newFiles.forEach(file => { + const idx = updated.findIndex(f => f.name === file.name); + if (idx !== -1) { + const existing = updated[idx]; + updated[idx] = { ...file, enabled: existing.enabled, config: existing.config }; + } else { + updated.push(file); + } + }); + return updated; +}