From cb15a04c1d6a9d34550123147bf886a0835d77b8 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:41:40 +0800 Subject: [PATCH] feat: support manual y-axis range with auto reset --- public/locales/en/translation.json | 5 ++++ public/locales/zh/translation.json | 5 ++++ src/App.jsx | 4 +++ src/components/ChartContainer.jsx | 31 ++++++++++++++++---- src/components/RegexControls.jsx | 46 ++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 3c81e22..6b75a8d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -94,8 +94,13 @@ "regex.metricConfig": "{{title}} parsing config", "regex.addMetric": "+ Add Metric", "regex.xRange": "X-axis range", + "regex.yRange": "Y-axis range", + "regex.min": "Min", + "regex.max": "Max", + "regex.auto": "Auto", "regex.reset": "Reset", "regex.xRangeHint": "Hold <0>Shift and drag on the chart to select range, or input values directly.", + "regex.yRangeHint": "Leave blank for auto-scale, or input one/both bounds to manually lock Y-axis range.", "regex.matchPreview": "Match Preview", "regex.matchCount": "{{count}} matches", "regex.lineNumber": "(line {{line}})", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 8c4e462..305cbcd 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -94,8 +94,13 @@ "regex.metricConfig": "{{title}} 解析配置", "regex.addMetric": "+ 添加指标", "regex.xRange": "X轴范围", + "regex.yRange": "Y轴范围", + "regex.min": "最小值", + "regex.max": "最大值", + "regex.auto": "自动", "regex.reset": "复位", "regex.xRangeHint": "在图表上按住 <0>Shift 键并拖动鼠标可选择范围,或直接输入数值。", + "regex.yRangeHint": "留空即自动缩放,也可以输入一个或两个边界手动固定 Y 轴范围。", "regex.matchPreview": "匹配预览", "regex.matchCount": "({{count}} 个匹配)", "regex.lineNumber": "(第{{line}}行)", diff --git a/src/App.jsx b/src/App.jsx index e7fcefa..f1e9593 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -72,6 +72,7 @@ function App() { const [globalDragOver, setGlobalDragOver] = useState(false); const [, setDragCounter] = useState(0); const [xRange, setXRange] = useState({ min: undefined, max: undefined }); + const [yRange, setYRange] = useState({ min: undefined, max: undefined }); const [maxStep, setMaxStep] = useState(0); const [sidebarVisible, setSidebarVisible] = useState(true); const savingDisabledRef = useRef(false); @@ -535,6 +536,8 @@ function App() { uploadedFiles={uploadedFiles} xRange={xRange} onXRangeChange={setXRange} + yRange={yRange} + onYRangeChange={setYRange} maxStep={maxStep} /> @@ -644,6 +647,7 @@ function App() { absoluteBaseline={absoluteBaseline} xRange={xRange} onXRangeChange={setXRange} + yRange={yRange} onMaxStepChange={setMaxStep} /> diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx index e40b638..e97e88b 100644 --- a/src/components/ChartContainer.jsx +++ b/src/components/ChartContainer.jsx @@ -98,6 +98,7 @@ export default function ChartContainer({ relativeBaseline = 0.002, absoluteBaseline = 0.005, xRange = { min: undefined, max: undefined }, + yRange = { min: undefined, max: undefined }, onXRangeChange, onMaxStepChange }) { @@ -393,6 +394,24 @@ export default function ChartContainer({ return { min: niceMin, max: niceMax, step }; }, []); + const getFinalYScale = useCallback((autoScale) => { + const hasManualMin = Number.isFinite(yRange?.min); + const hasManualMax = Number.isFinite(yRange?.max); + + const min = hasManualMin ? yRange.min : autoScale.min; + const max = hasManualMax ? yRange.max : autoScale.max; + + if (!Number.isFinite(min) || !Number.isFinite(max) || min >= max) { + return autoScale; + } + + return { + ...autoScale, + min, + max + }; + }, [yRange]); + const chartOptions = useMemo(() => ({ responsive: true, maintainAspectRatio: false, @@ -668,7 +687,8 @@ export default function ChartContainer({ }); }); - const yRange = calculateNiceScale(min, max); + const autoYRange = calculateNiceScale(min, max); + const finalYRange = getFinalYScale(autoYRange); const options = { ...chartOptions, @@ -690,10 +710,10 @@ export default function ChartContainer({ ...chartOptions.scales, y: { ...chartOptions.scales.y, - min: yRange.min, - max: yRange.max, + min: finalYRange.min, + max: finalYRange.max, ticks: { - stepSize: yRange.step, + stepSize: finalYRange.step, callback: (value) => Number(value.toFixed(yDecimals)) } } @@ -721,7 +741,8 @@ export default function ChartContainer({ }); }); - const compRange = calculateNiceScale(cMin, cMax); + const autoCompRange = calculateNiceScale(cMin, cMax); + const compRange = getFinalYScale(autoCompRange); const compDecimals = Math.max(4, getMaxDecimals(compResult.datasets)); // Ensure at least 4 for diffs const compOptions = { diff --git a/src/components/RegexControls.jsx b/src/components/RegexControls.jsx index 3515352..d7a1bfe 100644 --- a/src/components/RegexControls.jsx +++ b/src/components/RegexControls.jsx @@ -45,6 +45,8 @@ export function RegexControls({ uploadedFiles = [], xRange, onXRangeChange, + yRange, + onYRangeChange, maxStep }) { const [showPreview, setShowPreview] = useState(false); @@ -182,6 +184,11 @@ export function RegexControls({ onXRangeChange(newRange); }; + const handleYRangeChange = (field, value) => { + const newRange = { ...yRange, [field]: value === '' ? undefined : Number(value) }; + onYRangeChange(newRange); + }; + // Function to render config panel const renderConfigPanel = (type, config, onConfigChange, index) => { const ModeIcon = MODE_CONFIG[config.mode].icon; @@ -353,6 +360,45 @@ export function RegexControls({ +
+
+
+
+ handleYRangeChange('min', e.target.value)} + className="input-field" + /> + - + handleYRangeChange('max', e.target.value)} + className="input-field" + /> + +
+

+ {t('regex.yRangeHint')} +

+
+