react-native-chart-kit
Version:
Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.
111 lines (110 loc) • 4.28 kB
JavaScript
import { useCallback, useMemo, useRef } from "react";
import { resolveChartViewportWindowFromPanDelta } from "../../core/index";
import { isChartViewportEventInBounds } from "./bounds";
import { getChartViewportPanDeltaPoints, resolveChartViewportInteractionConfig } from "./config";
const isSameViewportWindow = (first, second) => first.startIndex === second.startIndex && first.endIndex === second.endIndex;
export const useChartViewportPan = ({ dataLength, enabled, onViewportChange, plotBounds, preventBrowserSelection, viewportInteraction, viewportWindow }) => {
const config = useMemo(() => resolveChartViewportInteractionConfig(viewportInteraction), [viewportInteraction]);
const panStateRef = useRef(undefined);
const canPan = enabled &&
config.pan &&
onViewportChange !== undefined &&
dataLength > viewportWindow.visibleCount &&
viewportWindow.visibleCount > 0 &&
plotBounds.width > 0;
const emitViewportChange = useCallback((nextWindow) => {
onViewportChange?.({
viewport: {
startIndex: nextWindow.startIndex,
endIndex: nextWindow.endIndex
},
startIndex: nextWindow.startIndex,
endIndex: nextWindow.endIndex,
visibleCount: nextWindow.visibleCount,
itemCount: nextWindow.itemCount,
isWindowed: nextWindow.isWindowed,
source: "mainPlot",
interaction: "pan"
});
}, [onViewportChange]);
const shouldSetResponder = useCallback((event) => canPan && isChartViewportEventInBounds({ bounds: plotBounds, event }), [canPan, plotBounds]);
const handleGrant = useCallback((event) => {
if (!canPan) {
return false;
}
preventBrowserSelection(event);
panStateRef.current = {
lastWindow: viewportWindow,
startLocationX: event.nativeEvent.locationX,
startWindow: viewportWindow
};
config.onGestureStart?.({ interaction: "pan" });
return true;
}, [canPan, config, preventBrowserSelection, viewportWindow]);
const handleMove = useCallback((event) => {
const panState = panStateRef.current;
if (!canPan || !panState) {
return false;
}
preventBrowserSelection(event);
const deltaX = Math.abs(event.nativeEvent.locationX - panState.startLocationX);
if (deltaX < config.minPanDistance) {
return true;
}
const deltaPoints = getChartViewportPanDeltaPoints({
currentLocationX: event.nativeEvent.locationX,
plotWidth: plotBounds.width,
startLocationX: panState.startLocationX,
visibleCount: panState.startWindow.visibleCount
});
if (deltaPoints === 0) {
return true;
}
const nextWindow = resolveChartViewportWindowFromPanDelta({
currentWindow: panState.startWindow,
deltaPoints,
itemCount: dataLength
});
if (isSameViewportWindow(nextWindow, panState.lastWindow)) {
return true;
}
panStateRef.current = {
...panState,
lastWindow: nextWindow
};
emitViewportChange(nextWindow);
return true;
}, [
canPan,
config.minPanDistance,
dataLength,
emitViewportChange,
plotBounds.width,
preventBrowserSelection
]);
const handleEnd = useCallback(() => {
if (!panStateRef.current) {
return false;
}
panStateRef.current = undefined;
config.onGestureEnd?.({ interaction: "pan" });
return true;
}, [config]);
const isActive = useCallback(() => panStateRef.current !== undefined, []);
const hasChanged = useCallback(() => {
const panState = panStateRef.current;
return panState
? !isSameViewportWindow(panState.startWindow, panState.lastWindow)
: false;
}, []);
return {
hasChanged,
handleEnd,
handleGrant,
handleMove,
isActive,
isEnabled: canPan,
shouldBlockTermination: canPan && config.lockParentScroll,
shouldSetResponder
};
};