react-native-chart-kit
Version:
Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.
200 lines (199 loc) • 9.69 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
import { ScrollView, StyleSheet, View } from "react-native";
import { useChartKitTheme } from "../../theme";
import { useScopedChartSelection } from "../../selection/ChartSelectionProvider";
import { getBarChartAccessibilitySummary } from "./accessibility";
import { BarChartSurface, BarChartTooltipOverlay, StickyBarChartYAxis } from "./BarChartSurface";
import { buildBarChartSelectEvent, getBarChartBarAtPoint, getBarChartBarKey, getBarChartInteractionConfig, isBarChartInteractionEnabled } from "./interaction";
import { buildBarChartModel } from "./model";
import { getBarChartTooltipConfig } from "./options";
import { getSafeBarChartContentWidth, getSafeBarChartRenderer } from "./rendererSafety";
import { getBarChartTooltipModel } from "./tooltipModel";
import { resolveBarChartViewport, resolveBarChartViewportInitialOffset } from "./viewport";
export { getBarChartAccessibilitySummary, getBarChartDataTable } from "./accessibility";
export const BarChart = (props) => {
const generatedChartId = useId().replace(/:/g, "");
const scopedChartId = props.id ?? generatedChartId;
const chartKitTheme = useChartKitTheme();
const renderer = props.renderer ?? chartKitTheme.renderer;
const scrollViewRef = useRef(null);
const interactionConfig = useMemo(() => getBarChartInteractionConfig(props.interaction), [props.interaction]);
const [gestureSelectedBarKey, setGestureSelectedBarKey] = useState(() => getBarChartBarKey(props.defaultSelectedBar));
const [gestureSelectionPointer, setGestureSelectionPointer] = useState();
const [scrollOffsetX, setScrollOffsetX] = useState(0);
const viewport = useMemo(() => resolveBarChartViewport({
itemCount: props.data.length,
scrollable: props.scrollable,
viewportWidth: props.width,
visiblePoints: props.visiblePoints
}), [props.data.length, props.scrollable, props.visiblePoints, props.width]);
const safeContentWidth = getSafeBarChartContentWidth({
contentWidth: viewport.contentWidth,
renderer,
scrollable: viewport.scrollable
});
const safeViewport = useMemo(() => ({
...viewport,
contentWidth: safeContentWidth,
maxOffset: Math.max(0, safeContentWidth - viewport.viewportWidth),
scrollable: viewport.scrollable && safeContentWidth > viewport.viewportWidth
}), [safeContentWidth, viewport]);
const initialScrollOffset = useMemo(() => resolveBarChartViewportInitialOffset({
initialIndex: props.initialIndex,
viewport: safeViewport
}), [props.initialIndex, safeViewport]);
const model = useMemo(() => buildBarChartModel({
...props,
chartKitTheme,
width: safeViewport.contentWidth
}), [chartKitTheme, props, safeViewport.contentWidth]);
const { bars, boxes, resolvedTheme } = model;
const safeRenderer = getSafeBarChartRenderer({
contentWidth: viewport.contentWidth,
renderer,
scrollable: viewport.scrollable
});
const scrollInitialOffset = safeViewport.scrollable && props.initialIndex === "end"
? Math.max(0, initialScrollOffset - boxes.plot.x * 0.66)
: initialScrollOffset;
const barRadius = Math.max(0, props.barRadius ?? 5);
const controlledSelectedBarKey = getBarChartBarKey(props.selectedBar);
const selectedBarKey = controlledSelectedBarKey ?? gestureSelectedBarKey;
const clearGestureSelection = useCallback((reason) => {
setGestureSelectionPointer(undefined);
if (props.selectedBar === undefined) {
setGestureSelectedBarKey(undefined);
}
interactionConfig.onDeselect?.({ reason });
}, [interactionConfig, props.selectedBar]);
const clearSelectionFromScope = useCallback((reason) => {
if (reason === "scopeChange") {
if (props.selectedBar === undefined) {
setGestureSelectedBarKey(undefined);
}
return;
}
clearGestureSelection(reason);
}, [clearGestureSelection, props.selectedBar]);
const scopedSelection = useScopedChartSelection({
chartId: scopedChartId,
controlled: props.selectedBar !== undefined,
hasSelection: selectedBarKey !== undefined,
onClear: clearSelectionFromScope
});
const clearScopedGestureSelection = useCallback((reason) => {
clearGestureSelection(reason);
if (reason !== "programmatic") {
scopedSelection.dismissSelection?.(reason);
}
}, [clearGestureSelection, scopedSelection]);
const selectedBar = bars.find((bar) => bar.key === selectedBarKey);
const tooltipConfig = useMemo(() => getBarChartTooltipConfig({
themeTooltip: resolvedTheme.tooltip,
tooltip: props.tooltip
}), [props.tooltip, resolvedTheme.tooltip]);
const tooltipModel = useMemo(() => getBarChartTooltipModel({
bar: selectedBar,
boxes,
config: tooltipConfig,
pointer: gestureSelectionPointer?.key === selectedBarKey
? gestureSelectionPointer
: undefined
}), [boxes, gestureSelectionPointer, selectedBar, selectedBarKey, tooltipConfig]);
const isInteractionEnabled = isBarChartInteractionEnabled(interactionConfig);
const handleResponderRelease = useCallback((event) => {
event.preventDefault();
const { locationX, locationY } = event.nativeEvent;
const tappedBar = getBarChartBarAtPoint({
bars,
locationX,
locationY
});
if (!tappedBar) {
if (interactionConfig.deselectOnOutsidePress) {
clearScopedGestureSelection("outsidePress");
}
return;
}
if (props.selectedBar === undefined) {
setGestureSelectedBarKey(tappedBar.key);
}
setGestureSelectionPointer({
key: tappedBar.key,
x: locationX,
y: locationY
});
scopedSelection.selectChart();
const selectEvent = buildBarChartSelectEvent(tappedBar);
if (selectEvent) {
interactionConfig.onSelect?.(selectEvent);
}
}, [
bars,
clearScopedGestureSelection,
interactionConfig,
props.selectedBar,
scopedSelection
]);
const responderProps = isInteractionEnabled
? {
onStartShouldSetResponderCapture: () => true,
onStartShouldSetResponder: () => true,
onResponderGrant: (event) => {
event.preventDefault();
},
onResponderRelease: handleResponderRelease,
onResponderTerminationRequest: () => true
}
: {};
const accessibilityLabel = props.accessibilityLabel ??
getBarChartAccessibilitySummary({
data: props.data,
formatXLabel: props.formatXLabel,
formatYLabel: props.formatYLabel,
series: props.series,
xKey: props.xKey,
yKey: props.yKey,
yKeys: props.yKeys
});
const chartSurface = (_jsx(BarChartSurface, { barRadius: barRadius, height: props.height, model: model, responderProps: responderProps, renderBar: props.renderBar, renderer: safeRenderer, selectedBarKey: selectedBarKey, selectionAnimation: props.selectionAnimation, showYAxis: !safeViewport.scrollable, width: safeViewport.contentWidth }));
const tooltipOverlay = (_jsx(BarChartTooltipOverlay, { height: props.height, model: model, tooltipConfig: tooltipConfig, tooltipModel: tooltipModel, viewportOffsetX: safeViewport.scrollable ? scrollOffsetX : 0, width: props.width, renderer: safeRenderer }));
useEffect(() => {
if (!safeViewport.scrollable) {
const frame = requestAnimationFrame(() => {
setScrollOffsetX(0);
});
return () => {
cancelAnimationFrame(frame);
};
}
const frame = requestAnimationFrame(() => {
setScrollOffsetX(scrollInitialOffset);
scrollViewRef.current?.scrollTo({
animated: false,
x: scrollInitialOffset
});
});
return () => {
cancelAnimationFrame(frame);
};
}, [safeViewport.scrollable, scrollInitialOffset]);
return (_jsx(View, { accessible: true, accessibilityLabel: accessibilityLabel, accessibilityRole: "image", style: { width: props.width, height: props.height }, testID: props.testID, children: safeViewport.scrollable ? (_jsxs(_Fragment, { children: [_jsx(ScrollView, { ref: scrollViewRef, horizontal: true, bounces: false, showsHorizontalScrollIndicator: true, style: [
styles.scroller,
{ width: props.width, height: props.height }
], contentContainerStyle: [
styles.scrollerContent,
{ width: safeViewport.contentWidth, height: props.height }
], onScroll: (event) => {
setScrollOffsetX(event.nativeEvent.contentOffset.x);
}, scrollEventThrottle: 16, children: chartSurface }), _jsx(StickyBarChartYAxis, { height: props.height, model: model, renderer: safeRenderer, width: props.width }), tooltipOverlay] })) : (_jsxs(_Fragment, { children: [chartSurface, tooltipOverlay] })) }));
};
const styles = StyleSheet.create({
scroller: {
overflow: "hidden"
},
scrollerContent: {
flexGrow: 0
}
});