4 Commits

View File

@@ -86,6 +86,39 @@ function isSameZoomRange(left: HistoryZoomRange, right: HistoryZoomRange): boole
return left.from === right.from && left.to === right.to && left.granulate === right.granulate; return left.from === right.from && left.to === right.to && left.granulate === right.granulate;
} }
/** Вычисляет, какую часть верхнеуровневого периода занимает текущий пользовательский zoom. */
function createDataZoomState(baseRange: HistoryZoomRange, zoomRange: HistoryZoomRange): DataZoomState {
// Верхнеуровневый период берём из инпутов "С/По" и считаем его полными 0..100%.
const baseFromMs = new Date(baseRange.from).getTime();
const baseToMs = new Date(baseRange.to).getTime();
// Пользовательский zoom живёт внутри верхнеуровневого периода и должен быть выделен на нижней шкале.
const zoomFromMs = new Date(zoomRange.from).getTime();
const zoomToMs = new Date(zoomRange.to).getTime();
const baseSpanMs = baseToMs - baseFromMs;
// Если даты некорректны, показываем весь период, чтобы не ломать управление графиком.
if (
!Number.isFinite(baseFromMs) ||
!Number.isFinite(baseToMs) ||
!Number.isFinite(zoomFromMs) ||
!Number.isFinite(zoomToMs) ||
baseSpanMs <= 0
) {
return { start: 0, end: 100 };
}
// Переводим абсолютные даты zoom-а в проценты относительно верхнеуровневого периода.
const start = ((zoomFromMs - baseFromMs) / baseSpanMs) * 100;
const end = ((zoomToMs - baseFromMs) / baseSpanMs) * 100;
// Ограничиваем значения диапазоном 0..100, чтобы ECharts не получил выход за шкалу.
return {
start: Math.max(0, Math.min(100, start)),
end: Math.max(0, Math.min(100, end)),
};
}
export function HistoryChart({ export function HistoryChart({
edge, edge,
from, from,
@@ -96,9 +129,14 @@ export function HistoryChart({
to, to,
tagLabels = {}, tagLabels = {},
}: HistoryChartProps) { }: HistoryChartProps) {
const baseRange = useMemo(
() => createInitialZoomRange({ from, granulate, labelFormat, tickIntervalMs, to }),
[from, granulate, labelFormat, tickIntervalMs, to],
);
const [zoomRange, setZoomRange] = useState(() => const [zoomRange, setZoomRange] = useState(() =>
createInitialZoomRange({ from, granulate, labelFormat, tickIntervalMs, to }), createInitialZoomRange({ from, granulate, labelFormat, tickIntervalMs, to }),
); );
const baseRangeRef = useRef(baseRange);
const zoomRangeRef = useRef(zoomRange); const zoomRangeRef = useRef(zoomRange);
const zoomTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const zoomTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lines = useHistoryChartQueries({ const lines = useHistoryChartQueries({
@@ -114,10 +152,10 @@ export function HistoryChart({
const loading = lines.some((line) => line.loading); const loading = lines.some((line) => line.loading);
useEffect(() => { useEffect(() => {
const nextRange = createInitialZoomRange({ from, granulate, labelFormat, tickIntervalMs, to }); baseRangeRef.current = baseRange;
zoomRangeRef.current = nextRange; zoomRangeRef.current = baseRange;
setZoomRange(nextRange); setZoomRange(baseRange);
}, [from, granulate, labelFormat, tickIntervalMs, to]); }, [baseRange]);
useEffect(() => { useEffect(() => {
zoomRangeRef.current = zoomRange; zoomRangeRef.current = zoomRange;
@@ -135,15 +173,15 @@ export function HistoryChart({
const option = useMemo( const option = useMemo(
() => () =>
createHistoryChartOptions({ createHistoryChartOptions({
dataZoomState: { start: 0, end: 100 }, dataZoomState: createDataZoomState(baseRange, zoomRange),
from: zoomRange.from, from: baseRange.from,
to: zoomRange.to, to: baseRange.to,
labelFormat: zoomRange.labelFormat, labelFormat: zoomRange.labelFormat,
legendData, legendData,
series, series,
tickIntervalMs: zoomRange.tickIntervalMs, tickIntervalMs: zoomRange.tickIntervalMs,
}), }),
[legendData, series, zoomRange], [baseRange, legendData, series, zoomRange],
); );
const handleDataZoom = useCallback((event: DataZoomEventBatch) => { const handleDataZoom = useCallback((event: DataZoomEventBatch) => {
@@ -153,8 +191,9 @@ export function HistoryChart({
return; return;
} }
const baseRange = baseRangeRef.current;
const currentRange = zoomRangeRef.current; const currentRange = zoomRangeRef.current;
const nextRange = getZoomRangeFromEvent(state, currentRange); const nextRange = getZoomRangeFromEvent(state, baseRange);
if (!nextRange || isSameZoomRange(nextRange, currentRange)) { if (!nextRange || isSameZoomRange(nextRange, currentRange)) {
return; return;