UNPKG

react-native-gifted-charts

Version:

The most complete library for Bar, Line, Area, Pie, Donut, Stacked Bar, Population Pyramid, Radar and Bubble charts in React Native. Allows 2D, 3D, gradient, animations and live data updates.

449 lines (448 loc) 29.2 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { BubbleDefaults, useBubbleChart, withinMinMaxRange, } from 'gifted-charts-core'; import BarAndLineChartsWrapper from '../Components/BarAndLineChartsWrapper'; import { Fragment, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { Animated, Easing, I18nManager, Text, View, } from 'react-native'; import { Circle, ForeignObject, Rect, Svg, Line, RadialGradient, Stop, Defs, } from 'react-native-svg'; import { isAndroid, isWebApp, screenWidth } from '../utils'; var AnimatedCircle = Animated.createAnimatedComponent(Circle); var AnimatedRect = Animated.createAnimatedComponent(Rect); var AnimatedLine = Animated.createAnimatedComponent(Line); export var BubbleChart = function (props) { var _a, _b, _c, _d; var opacityValue = useMemo(function () { return new Animated.Value(0); }, []); var pointsOpacityValue = useMemo(function () { return new Animated.Value(0); }, []); var secondaryXAxis = props.secondaryXAxis, xAxisLabelTextStyle = props.xAxisLabelTextStyle, formatBubbleLabel = props.formatBubbleLabel; var _e = useBubbleChart(__assign(__assign({}, props), { parentWidth: (_a = props.parentWidth) !== null && _a !== void 0 ? _a : screenWidth })), data = _e.data, barAndLineChartsWrapperProps = _e.barAndLineChartsWrapperProps, totalWidth = _e.totalWidth, animationDuration = _e.animationDuration, _f = _e.containerHeightIncludingBelowXAxis, containerHeightIncludingBelowXAxis = _f === void 0 ? 0 : _f, getY = _e.getY, getX = _e.getX, maxValue = _e.maxValue, selectedIndex = _e.selectedIndex, setSelectedIndex = _e.setSelectedIndex, showTextOnFocus = _e.showTextOnFocus, focusEnabled = _e.focusEnabled, focusTogether = _e.focusTogether, selectedLineNumber = _e.selectedLineNumber, lastLineNumber = _e.lastLineNumber, initialSpacing = _e.initialSpacing, spacing = _e.spacing, containerHeight = _e.containerHeight, handleFocus = _e.handleFocus, handleUnFocus = _e.handleUnFocus, isAnimated = _e.isAnimated, showBubbleOnFocus = _e.showBubbleOnFocus, showBubbleLabelOnFocus = _e.showBubbleLabelOnFocus, bubblesShape = _e.bubblesShape, bubblesWidth = _e.bubblesWidth, bubblesHeight = _e.bubblesHeight, bubblesColor = _e.bubblesColor, bubblesRadius = _e.bubblesRadius, labelFontSize = _e.labelFontSize, labelTextStyle = _e.labelTextStyle, startIndex = _e.startIndex, endIndex = _e.endIndex, showValuesAsBubbleLabels = _e.showValuesAsBubbleLabels, hideBubbles = _e.hideBubbles, xAxisLabelsVerticalShift = _e.xAxisLabelsVerticalShift, labelsExtraHeight = _e.labelsExtraHeight, xAxisThickness = _e.xAxisThickness, xAxisTextNumberOfLines = _e.xAxisTextNumberOfLines, rotateLabel = _e.rotateLabel, allowFontScaling = _e.allowFontScaling, borderColor = _e.borderColor, borderWidth = _e.borderWidth, opacity = _e.opacity, borderOpacity = _e.borderOpacity, xAxisLabelTexts = _e.xAxisLabelTexts, showRegressionLine = _e.showRegressionLine, regressionLineX1 = _e.regressionLineX1, regressionLineY1 = _e.regressionLineY1, regressionLineX2 = _e.regressionLineX2, regressionLineY2 = _e.regressionLineY2, regressionLineConfig = _e.regressionLineConfig, scatterChart = _e.scatterChart, maxRadius = _e.maxRadius, minRadius = _e.minRadius, extraWidthDueToBubble = _e.extraWidthDueToBubble, showGradient = _e.showGradient, centerColorForGradient = _e.centerColorForGradient; var progress = useRef(new Animated.Value(0)).current; var AnimatedRegressionLineX = useRef(new Animated.Value(0)).current; var AnimatedRegressionLineY = useRef(new Animated.Value(0)).current; var scrollRef = (_b = props.scrollRef) !== null && _b !== void 0 ? _b : useRef(null); var widthValue = useMemo(function () { return new Animated.Value(0); }, []); var appearingOpacity = opacityValue.interpolate({ inputRange: [0, 1], outputRange: [0, 1], }); // const appearRegressionLine = RegressionLine.interpolate({ // inputRange:[0,1], // outputRange:[regressionLineX1,regressionLineX2] // }) var growingRadii = useMemo(function () { return (data !== null && data !== void 0 ? data : []).map(function (item) { var _a; return progress.interpolate({ inputRange: [0, 1], outputRange: [ 0, withinMinMaxRange((_a = item.r) !== null && _a !== void 0 ? _a : bubblesRadius, maxRadius, minRadius), ], }); }); }, [data, progress]); var growingHeight = (data !== null && data !== void 0 ? data : []).map(function (_, i) { var _a; return progress.interpolate({ inputRange: [0, 1], outputRange: [0, (_a = data === null || data === void 0 ? void 0 : data[i].bubbleHeight) !== null && _a !== void 0 ? _a : bubblesHeight], }); }); var growingWidth = (data !== null && data !== void 0 ? data : []).map(function (_, i) { var _a; return progress.interpolate({ inputRange: [0, 1], outputRange: [0, (_a = data === null || data === void 0 ? void 0 : data[i].bubbleWidth) !== null && _a !== void 0 ? _a : bubblesWidth], }); }); var appearingDataPoints = pointsOpacityValue.interpolate({ inputRange: [0, 1], outputRange: [0, opacity], }); var appearingDataPointsAndroid = pointsOpacityValue.interpolate({ inputRange: [0, 1], outputRange: [0, opacity / 2], // hack to show slightly transparent bubbles while animating, as gradient on Android can't be animated }); var appearingBorder = pointsOpacityValue.interpolate({ inputRange: [0, 1], outputRange: [0, borderOpacity], }); var drawRegressionLine = useCallback(function () { if (!regressionLineConfig.isAnimated) return; AnimatedRegressionLineX.setValue(regressionLineX1); AnimatedRegressionLineY.setValue(regressionLineY1); Animated.parallel([ Animated.timing(AnimatedRegressionLineX, { toValue: regressionLineX2, duration: regressionLineConfig.animationDuration, easing: Easing.linear, useNativeDriver: false, // SVG props }), Animated.timing(AnimatedRegressionLineY, { toValue: regressionLineY2, duration: regressionLineConfig.animationDuration, easing: Easing.linear, useNativeDriver: false, // SVG props }), ]).start(); }, [regressionLineConfig]); var decreaseWidth = useCallback(function () { widthValue.setValue(0); Animated.timing(widthValue, { toValue: totalWidth, duration: animationDuration, easing: Easing.linear, useNativeDriver: false, }).start(); }, [animationDuration, widthValue]); var labelsAppear = useCallback(function () { opacityValue.setValue(0); Animated.timing(opacityValue, { toValue: 1, duration: 500, easing: Easing.ease, useNativeDriver: false, }).start(); }, [opacityValue]); var radiiGrow = useCallback(function () { if (bubblesShape === 'rectangular') return; progress.setValue(0); Animated.timing(progress, { toValue: 1, duration: animationDuration, easing: Easing.out(Easing.cubic), useNativeDriver: false, }).start(); }, [progress, animationDuration]); var bubblesHeightsWidthsGrow = useCallback(function () { if (bubblesShape !== 'rectangular') return; progress.setValue(0); Animated.timing(progress, { toValue: 1, duration: animationDuration, easing: Easing.out(Easing.cubic), useNativeDriver: false, }).start(); }, [progress, animationDuration]); var dataPointsAppear = useCallback(function () { pointsOpacityValue.setValue(0); Animated.timing(pointsOpacityValue, { toValue: 1, duration: animationDuration, easing: Easing.ease, useNativeDriver: false, }).start(); }, [pointsOpacityValue]); var _g = __read(useState(false), 2), isAnimationOver = _g[0], setIsAnimationOver = _g[1]; useEffect(function () { if (isAnimated) { decreaseWidth(); labelsAppear(); dataPointsAppear(); if (bubblesShape === 'rectangular') { bubblesHeightsWidthsGrow(); } else { radiiGrow(); } if (isAndroid) { setTimeout(function () { setIsAnimationOver(true); }, animationDuration + 20); } } if (regressionLineConfig.isAnimated) { drawRegressionLine(); } }, [isAnimated, bubblesShape]); var svgHeight = containerHeightIncludingBelowXAxis + ((_c = props.overflowBottom) !== null && _c !== void 0 ? _c : 0); // const onStripPress = (item: any, index: number) => { // if (props.focusedBubbleIndex === undefined || !props.onFocus) { // setSelectedIndex(index); // } // if (props.onFocus) { // props.onFocus(item, index); // } // }; var renderLabel = function (top, index, label) { var _a, _b; var left = index ? initialSpacing + spacing * index - spacing / 2 : initialSpacing / 2; return (_jsx(View, { style: [ { position: 'absolute', bottom: top ? containerHeight + 60 + ((_a = secondaryXAxis === null || secondaryXAxis === void 0 ? void 0 : secondaryXAxis.labelsDistanceFromXaxis) !== null && _a !== void 0 ? _a : 15) : -xAxisTextNumberOfLines * 18 - (containerHeight - 200) / 20, zIndex: 10, width: spacing + labelsExtraHeight, left: left, height: (_b = props.xAxisLabelsHeight) !== null && _b !== void 0 ? _b : xAxisTextNumberOfLines * 18, }, rotateLabel && { transform: [{ rotate: '60deg' }] }, ], children: _jsx(Text, { style: [{ textAlign: index ? 'center' : 'left' }, xAxisLabelTextStyle], allowFontScaling: allowFontScaling, numberOfLines: xAxisTextNumberOfLines, children: label }) })); }; var renderAnimatedLabel = function (top, index, label) { var _a, _b; var left = index ? initialSpacing + spacing * index - spacing / 2 : initialSpacing / 2; return (_jsx(Animated.View, { style: [ { height: rotateLabel ? 40 : ((_a = props.xAxisLabelsHeight) !== null && _a !== void 0 ? _a : xAxisTextNumberOfLines * 18), position: 'absolute', bottom: top ? containerHeight + 60 + ((_b = secondaryXAxis === null || secondaryXAxis === void 0 ? void 0 : secondaryXAxis.labelsDistanceFromXaxis) !== null && _b !== void 0 ? _b : 15) : rotateLabel ? 10 : -xAxisTextNumberOfLines * 18, zIndex: 10, width: spacing + labelsExtraHeight, left: left, opacity: appearingOpacity, }, rotateLabel && { transform: [{ rotate: '60deg' }] }, ], children: _jsx(Text, { allowFontScaling: allowFontScaling, style: [{ textAlign: index ? 'center' : 'left' }, xAxisLabelTextStyle], numberOfLines: xAxisTextNumberOfLines, children: label }) })); }; var renderBubbles = function (hideBubbles, dataForRender, originalDataFromProps, bubsShape, bubsWidth, bubsHeight, bubsColor, bubsRadius, labelFontSize, startIndex, endIndex, isSecondary, showValuesAsDataPointsText, key) { var getYOrSecondaryY = getY; //isSecondary ? getSecondaryY : getY; return dataForRender.map(function (item, index) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s; if (index < startIndex || index > endIndex) return null; if (item.hideBubble) { return null; } var bubblesShape, bubblesWidth, bubblesHeight, dataPointsColor, bubblesRadius, text, customBubble, labelComponent; if (index === selectedIndex && (focusTogether || key === selectedLineNumber)) { bubblesShape = item.focusedBubbleShape || props.focusedBubbleShape || item.bubbleShape || bubsShape; bubblesWidth = item.focusedBubbleWidth || props.focusedBubbleWidth || item.bubbleWidth || bubsWidth; bubblesHeight = item.focusedBubbleHeight || props.focusedBubbleHeight || item.bubbleHeight || bubsHeight; dataPointsColor = item.focusedBubbleColor || props.focusedBubbleColor || BubbleDefaults.focusedBubbleColor; bubblesRadius = withinMinMaxRange((_c = (_b = (_a = item.focusedBubbleRadius) !== null && _a !== void 0 ? _a : props.focusedBubbleRadius) !== null && _b !== void 0 ? _b : item.r) !== null && _c !== void 0 ? _c : bubsRadius, maxRadius, minRadius); if (showTextOnFocus) { text = item.label; } customBubble = item.focusedCustomBubble || props.focusedCustomBubble || item.customBubble || props.customBubble; labelComponent = item.focusedLabelComponent || item.labelComponent || props.focusedLabelComponent || props.labelComponent; } else { bubblesShape = item.bubbleShape || bubsShape; bubblesWidth = item.bubbleWidth || bubsWidth; bubblesHeight = item.bubbleHeight || bubsHeight; dataPointsColor = item.bubbleColor || bubsColor; bubblesRadius = withinMinMaxRange((_d = item.r) !== null && _d !== void 0 ? _d : bubsRadius, maxRadius, minRadius); if (showTextOnFocus) { text = ''; } customBubble = item.customBubble || props.customBubble; labelComponent = item.labelComponent || props.labelComponent; } if (showValuesAsDataPointsText) { text = originalDataFromProps[index].y; } var labelWidth = item.labelWidth ? item.labelWidth : props.labelWidth ? props.labelWidth : 30; var textLabel = !showTextOnFocus && !showValuesAsDataPointsText ? item.label : text.toString(); var formattedTextLabel = textLabel ? ((_e = formatBubbleLabel === null || formatBubbleLabel === void 0 ? void 0 : formatBubbleLabel(textLabel)) !== null && _e !== void 0 ? _e : textLabel) : ''; var textStyle = ((_g = (_f = item.labelTextStyle) !== null && _f !== void 0 ? _f : labelTextStyle) !== null && _g !== void 0 ? _g : {}); var fontSize = textStyle.fontSize || item.labelFontSize || labelFontSize; var defaultFontSize = 14; var fillColorForAnimatedGradientOnAndroid = isAnimationOver ? "url(#radial".concat(index, ")") : dataPointsColor; return (_jsxs(Fragment, { children: [focusEnabled ? (_jsx(_Fragment, { children: key === lastLineNumber - 1 ? (_jsx(Rect, { x: initialSpacing + (spacing * index - spacing / 2), y: 8, width: spacing, height: containerHeight - 0, fill: 'none', onPressIn: function (evt) { var locationY = evt.nativeEvent.locationY; // Note that we have another property named pageY which can be useful handleFocus(index); }, onPressOut: handleUnFocus })) : null })) : null, hideBubbles ? null : (_jsxs(_Fragment, { children: [customBubble ? (isWebApp ? (_jsx(ForeignObject, { height: svgHeight, width: totalWidth, x: getX(index), y: getYOrSecondaryY(item.y) - bubblesHeight / 2, children: customBubble(item, index) })) : (_jsx(Animated.View, { style: { position: 'absolute', // height: svgHeight, // width: totalWidth, left: getX(index) - bubblesWidth / 2, top: getYOrSecondaryY(item.y) - bubblesHeight / 2, opacity: isAnimated ? appearingOpacity : 1, }, children: customBubble(item, index) }))) : null, bubblesShape === 'rectangular' ? (_jsx(Fragment, { children: customBubble ? null : (_jsx(AnimatedRect, { x: getX(index) - bubblesWidth / 2, y: getYOrSecondaryY(item.y) - bubblesHeight / 2, width: isAnimated ? growingWidth[index] : bubblesWidth, height: isAnimated ? growingHeight[index] : bubblesHeight, opacity: isAnimated ? appearingDataPoints : opacity, fill: showBubbleOnFocus ? index === selectedIndex ? dataPointsColor : 'none' : dataPointsColor, fillOpacity: isAnimated ? appearingDataPoints : opacity, stroke: (_h = item.borderColor) !== null && _h !== void 0 ? _h : borderColor, strokeWidth: (_j = item.borderWidth) !== null && _j !== void 0 ? _j : borderWidth, strokeOpacity: (_l = (_k = item.borderOpacity) !== null && _k !== void 0 ? _k : props.borderOpacity) !== null && _l !== void 0 ? _l : (isAnimated ? appearingBorder : borderOpacity), onPress: function () { item.onPress ? item.onPress(item, index) : props.onPress ? props.onPress(item, index) : focusEnabled ? handleFocus(index) : null; }, onPressOut: function () { if (!item.onPress && !props.onPress && focusEnabled) { handleUnFocus(); } } })) }, index)) : (_jsx(Fragment, { children: customBubble ? null : (_jsx(AnimatedCircle, { cx: getX(index), cy: getYOrSecondaryY(item.y), r: isAnimated ? growingRadii[index] : bubblesRadius, fill: ((_m = item.showGradient) !== null && _m !== void 0 ? _m : showGradient) ? isAndroid && isAnimated ? fillColorForAnimatedGradientOnAndroid : "url(#radial".concat(index, ")") : dataPointsColor, fillOpacity: isAnimated ? isAndroid && showGradient ? isAnimationOver ? opacity : appearingDataPointsAndroid : appearingDataPoints : opacity, stroke: (_o = item.borderColor) !== null && _o !== void 0 ? _o : borderColor, strokeWidth: (_p = item.borderWidth) !== null && _p !== void 0 ? _p : borderWidth, strokeOpacity: (_r = (_q = item.borderOpacity) !== null && _q !== void 0 ? _q : props.borderOpacity) !== null && _r !== void 0 ? _r : (isAnimated ? appearingBorder : borderOpacity), onPress: function () { item.onPress ? item.onPress(item, index) : props.onPress ? props.onPress(item, index) : focusEnabled ? handleFocus(index) : null; }, onPressOut: function () { if (!item.onPress && !props.onPress && focusEnabled) { handleUnFocus(); } } })) }, index)), labelComponent ? (!showTextOnFocus || index === selectedIndex ? (isWebApp ? (_jsx(ForeignObject, { height: svgHeight, width: totalWidth, x: getX(index) - labelWidth / 2 + 6 + (item.labelShiftX || props.labelShiftX || 0), y: getYOrSecondaryY(item.y) - 10 + (item.labelShiftY || props.labelShiftY || 0) - (scatterChart ? bubblesRadius + 10 : 0), children: showBubbleLabelOnFocus ? index === selectedIndex && (focusTogether || key == selectedLineNumber) ? labelComponent(item, index) // not pushed in latest release : null : labelComponent(item, index) })) : (_jsx(Animated.View, { style: { position: 'absolute', height: svgHeight, width: labelWidth, left: getX(index) - defaultFontSize / 2 + (item.labelShiftX || props.labelShiftX || 0), top: getYOrSecondaryY(item.y) - defaultFontSize / 1.5 + (item.labelShiftY || props.labelShiftY || 0) - (scatterChart ? bubblesRadius + 10 : 0), opacity: isAnimated ? appearingDataPoints : 1, }, children: showBubbleLabelOnFocus ? index === selectedIndex && (focusTogether || key == selectedLineNumber) ? labelComponent(item, index) // not pushed in latest release : null : labelComponent(item, index) }))) : null) : formattedTextLabel ? (!showTextOnFocus || index === selectedIndex ? (isWebApp ? (_jsx(ForeignObject, { height: svgHeight, width: totalWidth, x: getX(index) - Math.min(bubblesRadius, (formattedTextLabel.length * fontSize) / 3) + (item.labelShiftX || props.labelShiftX || 0), y: getYOrSecondaryY(item.y) - Math.max(10, fontSize / 1.5) + (item.labelShiftY || props.labelShiftY || 0) - (scatterChart ? bubblesRadius + 10 : 0), children: _jsx(Text, { style: __assign(__assign({}, textStyle), { fontSize: fontSize }), children: formattedTextLabel }) })) : (_jsx(Animated.Text, { style: __assign(__assign({}, textStyle), { position: 'absolute', left: getX(index) - Math.min(bubblesRadius, (formattedTextLabel.length * fontSize) / 3) + (item.labelShiftX || props.labelShiftX || 0), top: getYOrSecondaryY(item.y) - fontSize / 1.5 + (item.labelShiftY || props.labelShiftY || 0) - (scatterChart ? bubblesRadius + 10 : 0), fontSize: fontSize, opacity: isAnimated ? appearingDataPoints : ((_s = textStyle.opacity) !== null && _s !== void 0 ? _s : 1) }), children: formattedTextLabel }))) : null) : null, index === selectedIndex ? _jsx(Text, { children: '' }) : null] }))] }, index)); }); }; var svgWrapperViewStyle = { position: 'absolute', top: 0, // 281 + xAxisLabelsVerticalShift + labelsExtraHeight - xAxisThickness, left: 0, zIndex: 1, transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }], }; var renderChartContent = function () { return (_jsxs(View, { style: [ svgWrapperViewStyle, { width: totalWidth + extraWidthDueToBubble, height: containerHeightIncludingBelowXAxis, // zIndex, }, ], children: [_jsxs(Svg, { height: svgHeight, width: totalWidth + extraWidthDueToBubble, onPress: props.onBackgroundPress, children: [showGradient && (_jsx(Defs, { children: data === null || data === void 0 ? void 0 : data.map(function (item, index) { var _a; return (_jsxs(RadialGradient, { id: "radial".concat(index), cx: "50%", cy: "50%", r: "50%", children: [_jsx(Stop, { offset: "0%", stopColor: (_a = item.centerColorForGradient) !== null && _a !== void 0 ? _a : centerColorForGradient }), _jsx(Stop, { offset: "100%", stopColor: item.bubbleColor || bubblesColor })] }, index)); }) })), renderBubbles(hideBubbles, data, data, bubblesShape, bubblesWidth, bubblesHeight, bubblesColor, bubblesRadius, labelFontSize, startIndex, endIndex, false, showValuesAsBubbleLabels, 0), showRegressionLine && (_jsx(AnimatedLine, { x1: regressionLineX1, y1: regressionLineY1, x2: regressionLineConfig.isAnimated ? AnimatedRegressionLineX : regressionLineX2, y2: regressionLineConfig.isAnimated ? AnimatedRegressionLineY : regressionLineY2, stroke: regressionLineConfig.color, strokeOpacity: regressionLineConfig.opacity, strokeWidth: regressionLineConfig.thickness, strokeDasharray: regressionLineConfig.strokeDashArray }))] }), xAxisLabelTexts === null || xAxisLabelTexts === void 0 ? void 0 : xAxisLabelTexts.map(function (label, index) { return (_jsx(View, { children: isAnimated ? renderAnimatedLabel(false, index, label) : renderLabel(false, index, label) }, index)); })] })); }; var remainingScrollViewProps = { onScroll: function (ev) { var _a; (_a = props.onScroll) === null || _a === void 0 ? void 0 : _a.call(props, ev); }, bounces: props.bounces, overScrollMode: (_d = props.overScrollMode) !== null && _d !== void 0 ? _d : 'auto', }; return (_jsx(BarAndLineChartsWrapper, __assign({}, barAndLineChartsWrapperProps, { scrollRef: scrollRef, animatedWidth: widthValue, renderChartContent: renderChartContent, remainingScrollViewProps: remainingScrollViewProps, nestedScrollEnabled: props.nestedScrollEnabled }))); };