From 48bc7a30df6a8d980f94f49e28575564b7202a51 Mon Sep 17 00:00:00 2001 From: Valentin Magrez Date: Thu, 7 Sep 2023 18:33:35 +0200 Subject: [PATCH] :lipstick: Add chart by forking lib that was not TS compliant --- example/package-lock.json | 16 + example/package.json | 1 + example/src/App.tsx | 3 + example/src/Chart/ChartPage.tsx | 81 + example/src/HomeScreen.tsx | 8 + package.json | 3 +- .../BarChart/Animated2DWithGradient.tsx | 332 ++ src/components/charts/BarChart/RenderBars.tsx | 576 ++++ .../charts/BarChart/RenderStackBars.tsx | 437 +++ src/components/charts/BarChart/index.tsx | 570 ++++ src/components/charts/BarChart/styles.tsx | 47 + src/components/charts/BarChart/types.ts | 264 ++ .../charts/Components/AnimatedBar/index.tsx | 262 ++ .../charts/Components/AnimatedBar/styles.tsx | 14 + .../BarAndLineChartsWrapper/index.tsx | 387 +++ .../renderHorizSections.tsx | 756 +++++ .../renderLineInBarChart.tsx | 404 +++ .../renderVerticalLines.tsx | 122 + .../barBackgroundPattern.tsx | 34 + .../Components/BarSpecificComponents/cap.tsx | 34 + .../charts/Components/ThreeDBar/index.tsx | 215 ++ .../charts/Components/ThreeDBar/styles.tsx | 14 + src/components/charts/Components/lineSvg.tsx | 42 + .../charts/LineChart/LineChartBicolor.tsx | 1362 ++++++++ src/components/charts/LineChart/index.tsx | 2921 +++++++++++++++++ src/components/charts/LineChart/styles.tsx | 47 + src/components/charts/LineChart/types.ts | 381 +++ src/components/charts/PieChart/index.tsx | 154 + src/components/charts/PieChart/main.tsx | 507 +++ src/components/charts/utils/constants.ts | 237 ++ src/components/charts/utils/index.tsx | 453 +++ src/components/charts/utils/types.ts | 212 ++ src/components/index.tsx | 2 + src/index.tsx | 2 + tsconfig.json | 4 +- 35 files changed, 10901 insertions(+), 3 deletions(-) create mode 100644 example/src/Chart/ChartPage.tsx create mode 100644 src/components/charts/BarChart/Animated2DWithGradient.tsx create mode 100644 src/components/charts/BarChart/RenderBars.tsx create mode 100644 src/components/charts/BarChart/RenderStackBars.tsx create mode 100644 src/components/charts/BarChart/index.tsx create mode 100644 src/components/charts/BarChart/styles.tsx create mode 100644 src/components/charts/BarChart/types.ts create mode 100644 src/components/charts/Components/AnimatedBar/index.tsx create mode 100644 src/components/charts/Components/AnimatedBar/styles.tsx create mode 100644 src/components/charts/Components/BarAndLineChartsWrapper/index.tsx create mode 100644 src/components/charts/Components/BarAndLineChartsWrapper/renderHorizSections.tsx create mode 100644 src/components/charts/Components/BarAndLineChartsWrapper/renderLineInBarChart.tsx create mode 100644 src/components/charts/Components/BarAndLineChartsWrapper/renderVerticalLines.tsx create mode 100644 src/components/charts/Components/BarSpecificComponents/barBackgroundPattern.tsx create mode 100644 src/components/charts/Components/BarSpecificComponents/cap.tsx create mode 100644 src/components/charts/Components/ThreeDBar/index.tsx create mode 100644 src/components/charts/Components/ThreeDBar/styles.tsx create mode 100644 src/components/charts/Components/lineSvg.tsx create mode 100644 src/components/charts/LineChart/LineChartBicolor.tsx create mode 100644 src/components/charts/LineChart/index.tsx create mode 100644 src/components/charts/LineChart/styles.tsx create mode 100644 src/components/charts/LineChart/types.ts create mode 100644 src/components/charts/PieChart/index.tsx create mode 100644 src/components/charts/PieChart/main.tsx create mode 100644 src/components/charts/utils/constants.ts create mode 100644 src/components/charts/utils/index.tsx create mode 100644 src/components/charts/utils/types.ts diff --git a/example/package-lock.json b/example/package-lock.json index 0c852fb3..85e4dc89 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -17,6 +17,7 @@ "react-native-drop-shadow": "^0.0.6", "react-native-gesture-handler": "^2.9.0", "react-native-icomoon": "^0.1.1", + "react-native-linear-gradient": "^2.8.3", "react-native-paper": "^5.1.4", "react-native-reanimated": "^3.0.2", "react-native-safe-area-context": "^4.5.0", @@ -7687,6 +7688,15 @@ "react-native": "*" } }, + "node_modules/react-native-linear-gradient": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz", + "integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-paper": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.1.4.tgz", @@ -15339,6 +15349,12 @@ "integrity": "sha512-/WHF2OXHGPZcjqEHutOtMIPFBMf9wfRXiA/uA5kSAr3WLhyZZ/wNMlz7C+XFjoqAE7D+M2ycDB37z6yZmnCTQg==", "requires": {} }, + "react-native-linear-gradient": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz", + "integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==", + "requires": {} + }, "react-native-paper": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.1.4.tgz", diff --git a/example/package.json b/example/package.json index 4527d75f..f4705c3a 100644 --- a/example/package.json +++ b/example/package.json @@ -19,6 +19,7 @@ "react-native-drop-shadow": "^0.0.6", "react-native-gesture-handler": "^2.9.0", "react-native-icomoon": "^0.1.1", + "react-native-linear-gradient": "^2.8.3", "react-native-paper": "^5.1.4", "react-native-reanimated": "^3.0.2", "react-native-safe-area-context": "^4.5.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index a7aff652..c182a0ea 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -26,6 +26,7 @@ import { LabelPage } from './Label/LabelPage'; import { ProductPage } from './Product/ProductPage'; import { DividerPage } from './DividerPage/DividerPage'; import { BodyPage } from './Body/BodyPage'; +import { ChartPage } from './Chart/ChartPage'; export type RootStackParamList = { Home: undefined; @@ -52,6 +53,7 @@ export type RootStackParamList = { Label: undefined; ProductPage: undefined; DividerPage: undefined; + ChartPage: undefined; }; const Stack = createNativeStackNavigator(); @@ -102,6 +104,7 @@ const App = () => { + diff --git a/example/src/Chart/ChartPage.tsx b/example/src/Chart/ChartPage.tsx new file mode 100644 index 00000000..2fb39be3 --- /dev/null +++ b/example/src/Chart/ChartPage.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { StyleSheet } from 'react-native'; +import { LineChart, Body, Card, useTheme, Screen } from 'smartway-react-native-ui'; + +export const ChartPage = () => { + const theme = useTheme(); + + const styles = StyleSheet.create({ + wrapper: { + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + alignSelf: 'stretch', + gap: theme.sw.spacing.l, + padding: theme.sw.spacing.l, + }, + header: { + color: theme.sw.colors.neutral[900], + textAlign: 'left', + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch', + }, + chartLabels: { + color: theme.sw.colors.neutral['500'], + fontFamily: theme.fonts.default.fontFamily, + fontSize: 14, + }, + }); + + const data = [0, 10, 8, 58, 56, 78, 58, 56, 78, 74, 98, 74]; + + const labels = [ + 'Aout', + 'Sep.', + 'Oct.', + 'Nov.', + 'Déc.', + 'Jan.', + 'Fév.', + 'Mars', + 'Avr.', + 'Mai.', + 'Juin', + 'Juill.', + ]; + return ( + + + + Vente annuelle + + { + return { value: _ }; + })} + height={160} + width={650} + initialSpacing={20} + spacing={55} + endSpacing={15} + color1={theme.sw.colors.primary.main} + curved={true} + noOfSections={4} + xAxisLabelTextStyle={styles.chartLabels} + yAxisTextStyle={styles.chartLabels} + hideDataPoints={true} + xAxisThickness={0} + yAxisThickness={0} + xAxisLabelTexts={labels} + /> + + + ); +}; diff --git a/example/src/HomeScreen.tsx b/example/src/HomeScreen.tsx index 19a2eb9f..67e66853 100644 --- a/example/src/HomeScreen.tsx +++ b/example/src/HomeScreen.tsx @@ -216,6 +216,14 @@ export const HomeScreen = ({ navigation }: Props) => { > Divider + diff --git a/package.json b/package.json index 36b4d757..ed0e042b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,8 @@ "react-native-safe-area-context": "*", "react-native-svg": "*", "react-native-vector-icons": "*", - "react-native-drop-shadow": "*" + "react-native-drop-shadow": "*", + "react-native-linear-gradient": "*" }, "dependencies": {} } diff --git a/src/components/charts/BarChart/Animated2DWithGradient.tsx b/src/components/charts/BarChart/Animated2DWithGradient.tsx new file mode 100644 index 00000000..249bdeb9 --- /dev/null +++ b/src/components/charts/BarChart/Animated2DWithGradient.tsx @@ -0,0 +1,332 @@ +import React, {useEffect, useState} from 'react'; +import { + View, + ColorValue, + LayoutAnimation, + Platform, + UIManager, +} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Svg, {Defs, Rect} from 'react-native-svg'; +import Cap from '../Components/BarSpecificComponents/cap'; +import {itemType} from './types'; + +if (Platform.OS === 'android') { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +type propTypes = { + item: itemType; + height: number; + minHeight: number; + opacity?: number; + animationDuration: number; + roundedTop: boolean; + roundedBottom: boolean; + barWidth: number; + gradientColor: ColorValue; + frontColor: ColorValue; + noGradient?: boolean; + noAnimation?: boolean; + cappedBars?: boolean; + capThickness?: number; + capColor?: ColorValue; + capRadius?: number; + horizontal: boolean; + intactTopLabel: boolean; + barBorderRadius?: number; + barBorderTopLeftRadius?: number; + barBorderTopRightRadius?: number; + barBorderBottomLeftRadius?: number; + barBorderBottomRightRadius?: number; + containerHeight?: number; + maxValue?: number; + barBackgroundPattern?: Function; + patternId?: String; + barMarginBottom?: number; + barStyle?: object; +}; + +const Animated2DWithGradient = (props: propTypes) => { + const { + barBackgroundPattern, + patternId, + barWidth, + barStyle, + item, + opacity, + animationDuration, + noGradient, + noAnimation, + containerHeight, + maxValue, + barMarginBottom, + barBorderRadius, + barBorderTopLeftRadius, + barBorderTopRightRadius, + barBorderBottomLeftRadius, + barBorderBottomRightRadius, + } = props; + const [height, setHeight] = useState(noAnimation ? props.height : 0.2); + const [initialRender, setInitialRender] = useState( + noAnimation ? false : true, + ); + + useEffect(() => { + if (!noAnimation) { + if (initialRender) { + setTimeout(() => layoutAppear(), 20); + } else { + elevate(); + } + } + }, [props.height]); + + const elevate = () => { + LayoutAnimation.configureNext({ + duration: animationDuration, + update: {type: 'linear', property: 'scaleXY'}, + }); + setHeight(props.height); + }; + + const layoutAppear = () => { + LayoutAnimation.configureNext({ + duration: Platform.OS == 'ios' ? animationDuration : 20, + create: {type: 'linear', property: 'opacity'}, + update: {type: 'linear', property: 'scaleXY'}, + }); + setInitialRender(false); + setTimeout(() => elevate(), Platform.OS == 'ios' ? 10 : 100); + }; + + return ( + <> + {!initialRender && ( + + + {noGradient ? ( + + {props.cappedBars && item.value ? ( + + ) : null} + + ) : ( + + {props.cappedBars && ( + + )} + + )} + {(item.barBackgroundPattern || barBackgroundPattern) && ( + + + {item.barBackgroundPattern + ? item.barBackgroundPattern() + : barBackgroundPattern?.()} + + + + )} + + {item.topLabelComponent && ( + + {item.topLabelComponent()} + + )} + + )} + + ); +}; + +export default Animated2DWithGradient; diff --git a/src/components/charts/BarChart/RenderBars.tsx b/src/components/charts/BarChart/RenderBars.tsx new file mode 100644 index 00000000..0e857697 --- /dev/null +++ b/src/components/charts/BarChart/RenderBars.tsx @@ -0,0 +1,576 @@ +import React, {Component} from 'react'; +import {View, TouchableOpacity, Animated, Text, ColorValue} from 'react-native'; +import ThreeDBar from '../Components/ThreeDBar'; +import AnimatedBar from '../Components/AnimatedBar'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated2DWithGradient from './Animated2DWithGradient'; +import {Style} from 'util'; +import Cap from '../Components/BarSpecificComponents/cap'; +import BarBackgroundPattern from '../Components/BarSpecificComponents/barBackgroundPattern'; +import {itemType} from './types'; + +type Props = { + style?: any; + width?: number; + height?: number; + minHeight: number; + color?: ColorValue; + showGradient?: boolean; + gradientColor?: any; + frontColor?: ColorValue; + sideColor?: ColorValue; + topColor?: ColorValue; + topLabelComponent?: Component; + topLabelContainerStyle?: Style; + opacity?: number; + side?: String; + labelTextStyle?: any; + + item: itemType; + index: number; + label: String; + containerHeight?: number; + maxValue: number; + spacing: number; + propSpacing?: number; + data?: any; + barWidth?: number; + sideWidth?: number; + labelWidth?: number; + + isThreeD?: boolean; + isAnimated?: boolean; + rotateLabel?: boolean; + animatedHeight?: any; + appearingOpacity?: any; + animationDuration?: number; + roundedTop?: boolean; + roundedBottom?: boolean; + disablePress?: boolean; + activeOpacity?: number; + cappedBars?: boolean; + capThickness?: number; + capColor?: ColorValue; + capRadius?: number; + showXAxisIndices: boolean; + xAxisIndicesHeight: number; + xAxisIndicesWidth: number; + xAxisIndicesColor: ColorValue; + horizontal: boolean; + rtl: boolean; + intactTopLabel: boolean; + barBorderRadius?: number; + barBorderTopLeftRadius?: number; + barBorderTopRightRadius?: number; + barBorderBottomLeftRadius?: number; + barBorderBottomRightRadius?: number; + autoShiftLabels?: boolean; + barBackgroundPattern?: Function; + patternId?: String; + barMarginBottom?: number; + onPress?: Function; + xAxisTextNumberOfLines: number; + renderTooltip: Function | undefined; + leftShiftForTooltip?: number; + leftShiftForLastIndexTooltip: number; + initialSpacing: number; + selectedIndex: number; + setSelectedIndex: Function; + barStyle?: object; + xAxisThickness?: number; +}; +const RenderBars = (props: Props) => { + const { + item, + index, + containerHeight, + maxValue, + minHeight, + spacing, + propSpacing, + side, + data, + barStyle, + barBorderRadius, + barBorderTopLeftRadius, + barBorderTopRightRadius, + barBorderBottomLeftRadius, + barBorderBottomRightRadius, + // oldValue, + + isThreeD, + isAnimated, + rotateLabel, + appearingOpacity, + opacity, + animationDuration, + autoShiftLabels, + label, + labelTextStyle, + xAxisTextNumberOfLines, + renderTooltip, + leftShiftForTooltip, + leftShiftForLastIndexTooltip, + initialSpacing, + selectedIndex, + setSelectedIndex, + xAxisThickness, + horizontal, + rtl, + } = props; + + const barMarginBottom = + item.barMarginBottom === 0 + ? 0 + : item.barMarginBottom || props.barMarginBottom || 0; + + const renderLabel = (label: String, labelTextStyle: any, value: number) => { + return ( + + {item.labelComponent ? ( + item.labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const renderAnimatedLabel = ( + label: String, + labelTextStyle: any, + value: number, + ) => { + return ( + + {item.labelComponent ? ( + item.labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const static2DWithGradient = (item: itemType) => { + return ( + <> + + {props.cappedBars && item.value ? ( + + ) : null} + + {(item.barBackgroundPattern || props.barBackgroundPattern) && ( + + )} + {item.topLabelComponent && ( + + {item.topLabelComponent()} + + )} + + ); + }; + + const barHeight = Math.max( + minHeight, + (Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200) - + (xAxisThickness ?? 0), + ); + + let leftSpacing = initialSpacing; + for (let i = 0; i < index; i++) { + leftSpacing += + (data[i].spacing ?? propSpacing) + + (data[i].barWidth || props.barWidth || 30); + } + + return ( + <> + { + if (renderTooltip) { + setSelectedIndex(index); + } + item.onPress + ? item.onPress() + : props.onPress + ? props.onPress(item, index) + : null; + }} + style={[ + { + // overflow: 'visible', + marginBottom: 60 + barMarginBottom, + width: item.barWidth || props.barWidth || 30, + height: barHeight, + marginRight: spacing, + }, + item.value < 0 && { + transform: [ + { + translateY: + (Math.abs(item.value) * (containerHeight || 200)) / + (maxValue || 200), + }, + {rotateZ: '180deg'}, + ], + }, + // !isThreeD && !item.showGradient && !props.showGradient && + // { backgroundColor: item.frontColor || props.frontColor || 'black' }, + side !== 'right' && {zIndex: data.length - index}, + ]}> + {(props.showXAxisIndices || item.showXAxisIndex) && ( + + )} + {isThreeD ? ( + isAnimated ? ( + + ) : ( + + ) + ) : item.showGradient || props.showGradient ? ( + isAnimated ? ( + + ) : ( + static2DWithGradient(item) + ) + ) : isAnimated ? ( + + ) : ( + + )} + {isAnimated + ? renderAnimatedLabel(label, labelTextStyle, item.value) + : renderLabel(label, labelTextStyle, item.value)} + + {renderTooltip && selectedIndex === index && ( + + {renderTooltip(item, index)} + + )} + + ); +}; + +export default RenderBars; diff --git a/src/components/charts/BarChart/RenderStackBars.tsx b/src/components/charts/BarChart/RenderStackBars.tsx new file mode 100644 index 00000000..e0ac50fe --- /dev/null +++ b/src/components/charts/BarChart/RenderStackBars.tsx @@ -0,0 +1,437 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { Component, useEffect, useState } from 'react'; +import { + View, + TouchableOpacity, + Text, + ColorValue, + GestureResponderEvent, + LayoutAnimation, + Platform, + UIManager, +} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Svg, { Defs, Rect } from 'react-native-svg'; +import { Style } from 'util'; +import { BarDefaults } from '../utils/constants'; + +if (Platform.OS === 'android') { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +type Props = { + style?: any; + width?: number; + height?: number; + color?: ColorValue; + topLabelComponent?: Component; + topLabelContainerStyle?: Style; + opacity?: number; + label: String; + labelTextStyle?: any; + disablePress?: boolean; + + item: stackItemType; + index: number; + containerHeight?: number; + maxValue: number; + spacing: number; + propSpacing?: number; + data?: any; + barWidth?: number; + onPress?: Function; + + rotateLabel?: boolean; + showXAxisIndices: boolean; + xAxisIndicesHeight: number; + xAxisIndicesWidth: number; + xAxisIndicesColor: ColorValue; + horizontal: boolean; + intactTopLabel: boolean; + barBorderRadius?: number; + xAxisThickness: number; + barBackgroundPattern?: Function; + patternId?: String; + xAxisTextNumberOfLines: number; + renderTooltip: Function | undefined; + leftShiftForTooltip?: number; + leftShiftForLastIndexTooltip: number; + initialSpacing: number; + selectedIndex: number; + setSelectedIndex: Function; + activeOpacity: number; + showGradient?: boolean; + gradientColor?: any; + stackData: Array; + isAnimated?: boolean; + animationDuration?: number; +}; +export type stackItemType = { + value?: number; + onPress?: any; + label?: String; + barWidth?: number; + spacing?: number; + labelTextStyle?: any; + topLabelComponent?: Function; + topLabelContainerStyle?: any; + disablePress?: any; + color?: ColorValue; + showGradient?: boolean; + gradientColor?: any; + capThickness?: number; + capColor?: ColorValue; + capRadius?: number; + labelComponent?: Function; + borderRadius?: number; + stacks: Array<{ + value: number; + color?: ColorValue; + onPress?: (event: GestureResponderEvent) => void; + marginBottom?: number; + barBorderRadius?: number; + borderTopLeftRadius?: number; + borderTopRightRadius?: number; + borderBottomLeftRadius?: number; + borderBottomRightRadius?: number; + showGradient?: boolean; + gradientColor?: ColorValue; + barWidth?: number; + innerBarComponent?: Function; + }>; + barBackgroundPattern?: Function; + barBorderRadius?: number; + patternId?: String; + leftShiftForTooltip?: number; + showXAxisIndex?: boolean; +}; +const RenderStackBars = (props: Props) => { + const { + barBackgroundPattern, + patternId, + item, + index, + containerHeight, + maxValue, + spacing, + propSpacing, + rotateLabel, + xAxisThickness, + label, + labelTextStyle, + xAxisTextNumberOfLines, + renderTooltip, + leftShiftForTooltip, + leftShiftForLastIndexTooltip, + initialSpacing, + selectedIndex, + setSelectedIndex, + activeOpacity, + stackData, + isAnimated, + animationDuration = BarDefaults.animationDuration, + } = props; + const cotainsNegative = item.stacks.some((item) => item.value < 0); + const noAnimation = cotainsNegative || !isAnimated; + + let leftSpacing = initialSpacing; + for (let i = 0; i < index; i++) { + leftSpacing += + (stackData[i].spacing ?? propSpacing ?? 0) + + (stackData[i].stacks[0].barWidth ?? props.barWidth ?? 30); + } + const disablePress = props.disablePress || false; + const renderLabel = (label: String, labelTextStyle: any) => { + return ( + + {item.labelComponent ? ( + item.labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const getPosition = (index: number) => { + let position = 0; + for (let i = 0; i < index; i++) { + position += + (Math.abs(props.item.stacks[i].value) * (containerHeight || 200)) / + (maxValue || 200); + } + return position; + }; + + const totalHeight = props.item.stacks.reduce( + (acc, stack) => + acc + (Math.abs(stack.value) * (containerHeight || 200)) / (maxValue || 200), + 0, + ); + + const [height, setHeight] = useState(noAnimation ? totalHeight : 1); + + useEffect(() => { + if (!noAnimation) { + layoutAppear(); + } + }, [totalHeight]); + + const elevate = () => { + LayoutAnimation.configureNext({ + duration: animationDuration, + update: { type: 'linear', property: 'scaleXY' }, + }); + setHeight(totalHeight); + }; + + const layoutAppear = () => { + LayoutAnimation.configureNext({ + duration: Platform.OS == 'ios' ? animationDuration : 20, + create: { type: 'linear', property: 'opacity' }, + update: { type: 'linear', property: 'scaleXY' }, + }); + setTimeout(() => elevate(), Platform.OS == 'ios' ? 10 : 100); + }; + + const barWrapper = () => { + return noAnimation ? ( + static2DSimple() + ) : ( + + {static2DSimple()} + + ); + }; + + const static2DSimple = () => { + return ( + <> + { + setSelectedIndex(index); + if (item.onPress) { + item.onPress(); + } else if (props.onPress) { + props.onPress(item, index); + } + }} + style={[ + { + position: 'absolute', + width: item.stacks[0].barWidth || props.barWidth || 30, + height: '100%', + }, + cotainsNegative && { + transform: [ + { translateY: totalHeight + xAxisThickness / 2 }, + { rotate: '180deg' }, + ], + }, + ]} + > + {item.stacks.map((stackItem, index) => { + return ( + + {stackItem.showGradient || + item.showGradient || + props.showGradient ? ( + + ) : null} + {stackItem.innerBarComponent && stackItem.innerBarComponent()} + + ); + })} + {(item.barBackgroundPattern || barBackgroundPattern) && ( + + + {item.barBackgroundPattern + ? item.barBackgroundPattern() + : barBackgroundPattern?.()} + + + + )} + + {item.topLabelComponent && ( + + {item.topLabelComponent()} + + )} + + ); + }; + + return ( + <> + + {/* {props.showVerticalLines && ( + + )} */} + {(props.showXAxisIndices || item.showXAxisIndex) && ( + + )} + {barWrapper()} + {renderLabel(label || '', labelTextStyle)} + + {renderTooltip && selectedIndex === index && ( + + {renderTooltip(item, index)} + + )} + + ); +}; + +export default RenderStackBars; diff --git a/src/components/charts/BarChart/index.tsx b/src/components/charts/BarChart/index.tsx new file mode 100644 index 00000000..9fe8c968 --- /dev/null +++ b/src/components/charts/BarChart/index.tsx @@ -0,0 +1,570 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { Animated, Easing } from 'react-native'; +import RenderBars from './RenderBars'; +import RenderStackBars from './RenderStackBars'; +import { + getArrowPoints, + getAxesAndRulesProps, + getSecondaryDataWithOffsetIncluded, + getXForLineInBar, + getYForLineInBar, + maxAndMinUtil, + svgPath, +} from '../utils'; +import { + AxesAndRulesDefaults, + BarDefaults, + chartTypes, + defaultLineConfig, +} from '../utils/constants'; +import BarAndLineChartsWrapper from '../Components/BarAndLineChartsWrapper'; +import { BarChartPropsType, itemType } from './types'; +import { BarAndLineChartsWrapperTypes, HorizSectionsType } from '../utils/types'; + +export const BarChart = (props: BarChartPropsType) => { + const scrollRef = props.scrollRef ?? useRef(null); + const [points, setPoints] = useState(''); + const [arrowPoints, setArrowPoints] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(-1); + const showLine = props.showLine || BarDefaults.showLine; + const spacing = props.spacing ?? BarDefaults.spacing; + const initialSpacing = props.initialSpacing ?? spacing; + const endSpacing = props.endSpacing ?? spacing; + const showFractionalValues = + props.showFractionalValues || AxesAndRulesDefaults.showFractionalValues; + + const horizontal = props.horizontal ?? BarDefaults.horizontal; + const rtl = props.rtl ?? BarDefaults.rtl; + const yAxisAtTop = props.yAxisAtTop ?? BarDefaults.yAxisAtTop; + const intactTopLabel = props.intactTopLabel ?? BarDefaults.intactTopLabel; + + const heightFromProps = horizontal ? props.width : props.height; + const widthFromProps = horizontal ? props.height : props.width; + + const isAnimated = props.isAnimated ?? BarDefaults.isAnimated; + const animationDuration = props.animationDuration ?? BarDefaults.animationDuration; + + const data = useMemo(() => { + if (!props.data) { + return []; + } + if (props.yAxisOffset) { + return props.data.map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data; + }, [props.yAxisOffset, props.data]); + + const secondaryData = getSecondaryDataWithOffsetIncluded( + props.secondaryData, + props.secondaryYAxis, + ); + + const lineData = useMemo(() => { + if (!props.lineData) { + return data; + } + if (props.yAxisOffset) { + return props.lineData.map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.lineData; + }, [props.yAxisOffset, props.lineData, data]); + + const lineBehindBars = props.lineBehindBars || BarDefaults.lineBehindBars; + + defaultLineConfig.initialSpacing = initialSpacing; + defaultLineConfig.endIndex = lineData.length - 1; + defaultLineConfig.animationDuration = animationDuration; + + const lineConfig = props.lineConfig + ? { + initialSpacing: props.lineConfig.initialSpacing ?? defaultLineConfig.initialSpacing, + curved: props.lineConfig.curved || defaultLineConfig.curved, + curvature: props.lineConfig.curvature ?? defaultLineConfig.curvature, + curveType: props.lineConfig.curveType ?? defaultLineConfig.curveType, + isAnimated: props.lineConfig.isAnimated || defaultLineConfig.isAnimated, + animationDuration: + props.lineConfig.animationDuration || defaultLineConfig.animationDuration, + thickness: props.lineConfig.thickness || defaultLineConfig.thickness, + color: props.lineConfig.color || defaultLineConfig.color, + hideDataPoints: props.lineConfig.hideDataPoints || defaultLineConfig.hideDataPoints, + dataPointsShape: + props.lineConfig.dataPointsShape || defaultLineConfig.dataPointsShape, + dataPointsHeight: + props.lineConfig.dataPointsHeight || defaultLineConfig.dataPointsHeight, + dataPointsWidth: + props.lineConfig.dataPointsWidth || defaultLineConfig.dataPointsWidth, + dataPointsColor: + props.lineConfig.dataPointsColor || defaultLineConfig.dataPointsColor, + dataPointsRadius: + props.lineConfig.dataPointsRadius || defaultLineConfig.dataPointsRadius, + textColor: props.lineConfig.textColor || defaultLineConfig.textColor, + textFontSize: props.lineConfig.textFontSize || defaultLineConfig.textFontSize, + textShiftX: props.lineConfig.textShiftX || defaultLineConfig.textShiftX, + textShiftY: props.lineConfig.textShiftY || defaultLineConfig.textShiftY, + shiftX: props.lineConfig.shiftX || defaultLineConfig.shiftX, + shiftY: props.lineConfig.shiftY || defaultLineConfig.shiftY, + delay: props.lineConfig.delay || defaultLineConfig.delay, + startIndex: props.lineConfig.startIndex || defaultLineConfig.startIndex, + endIndex: + props.lineConfig.endIndex === 0 + ? 0 + : props.lineConfig.endIndex || defaultLineConfig.endIndex, + + showArrow: props.lineConfig.showArrow ?? defaultLineConfig.showArrow, + arrowConfig: { + length: + props.lineConfig.arrowConfig?.length ?? defaultLineConfig.arrowConfig?.length, + width: + props.lineConfig.arrowConfig?.width ?? defaultLineConfig.arrowConfig?.width, + + strokeWidth: + props.lineConfig.arrowConfig?.strokeWidth ?? + defaultLineConfig.arrowConfig?.strokeWidth, + + strokeColor: + props.lineConfig.arrowConfig?.strokeColor ?? + defaultLineConfig.arrowConfig?.strokeColor, + + fillColor: + props.lineConfig.arrowConfig?.fillColor ?? + defaultLineConfig.arrowConfig?.fillColor, + + showArrowBase: + props.lineConfig.arrowConfig?.showArrowBase ?? + defaultLineConfig.arrowConfig?.showArrowBase, + }, + customDataPoint: props.lineConfig.customDataPoint, + } + : defaultLineConfig; + const noOfSections = props.noOfSections ?? AxesAndRulesDefaults.noOfSections; + const containerHeight = + heightFromProps ?? + ((props.stepHeight ?? 0) * noOfSections || AxesAndRulesDefaults.containerHeight); + const horizSections = [{ value: '0' }]; + const horizSectionsBelow: HorizSectionsType = []; + const stepHeight = props.stepHeight ?? containerHeight / noOfSections; + const labelWidth = props.labelWidth ?? AxesAndRulesDefaults.labelWidth; + const scrollToEnd = props.scrollToEnd ?? BarDefaults.scrollToEnd; + const scrollAnimation = props.scrollAnimation ?? BarDefaults.scrollAnimation; + const labelsExtraHeight = props.labelsExtraHeight ?? AxesAndRulesDefaults.labelsExtraHeight; + + let totalWidth = initialSpacing; + let maxItem = 0, + minItem = 0; + if (props.stackData) { + props.stackData.forEach((stackItem) => { + let stackSum = stackItem.stacks.reduce((acc, stack) => acc + (stack.value ?? 0), 0); + if (stackSum > maxItem) { + maxItem = stackSum; + } + if (stackSum < minItem) { + minItem = stackSum; + } + totalWidth += + (stackItem.stacks[0].barWidth ?? props.barWidth ?? BarDefaults.barWidth) + spacing; + }); + } else { + data.forEach((item: itemType) => { + if (item.value > maxItem) { + maxItem = item.value; + } + if (item.value < minItem) { + minItem = item.value; + } + totalWidth += + (item.barWidth ?? props.barWidth ?? BarDefaults.barWidth) + + (item.spacing ?? spacing); + }); + } + + const maxAndMin = maxAndMinUtil( + maxItem, + minItem, + props.roundToDigits, + props.showFractionalValues, + ); + + const maxValue = props.maxValue ?? maxAndMin.maxItem; + const minValue = props.minValue ?? maxAndMin.minItem; + + const stepValue = props.stepValue ?? maxValue / noOfSections; + const noOfSectionsBelowXAxis = props.noOfSectionsBelowXAxis ?? -minValue / stepValue; + const disableScroll = props.disableScroll ?? BarDefaults.disableScroll; + const showScrollIndicator = props.showScrollIndicator ?? BarDefaults.showScrollIndicator; + const side = props.side ?? BarDefaults.side; + const rotateLabel = props.rotateLabel ?? AxesAndRulesDefaults.rotateLabel; + const opacity = props.opacity ?? BarDefaults.opacity; + const isThreeD = props.isThreeD ?? BarDefaults.isThreeD; + + const showXAxisIndices = props.showXAxisIndices ?? AxesAndRulesDefaults.showXAxisIndices; + const xAxisIndicesHeight = props.xAxisIndicesHeight ?? AxesAndRulesDefaults.xAxisIndicesHeight; + const xAxisIndicesWidth = props.xAxisIndicesWidth ?? AxesAndRulesDefaults.xAxisIndicesWidth; + const xAxisIndicesColor = props.xAxisIndicesColor ?? AxesAndRulesDefaults.xAxisIndicesColor; + + const xAxisThickness = props.xAxisThickness ?? AxesAndRulesDefaults.xAxisThickness; + + const xAxisTextNumberOfLines = + props.xAxisTextNumberOfLines ?? AxesAndRulesDefaults.xAxisTextNumberOfLines; + const horizontalRulesStyle = props.horizontalRulesStyle; + const yAxisLabelWidth = + props.yAxisLabelWidth ?? + (props.hideYAxisText + ? AxesAndRulesDefaults.yAxisEmptyLabelWidth + : AxesAndRulesDefaults.yAxisLabelWidth); + + const heightValue = useMemo(() => new Animated.Value(0), []); + const opacValue = useMemo(() => new Animated.Value(0), []); + const widthValue = useMemo(() => new Animated.Value(0), []); + const autoShiftLabels = props.autoShiftLabels ?? false; + + const labelsAppear = useCallback(() => { + opacValue.setValue(0); + Animated.timing(opacValue, { + toValue: 1, + duration: 500, + easing: Easing.ease, + useNativeDriver: false, + }).start(); + }, [opacValue]); + + const decreaseWidth = useCallback(() => { + widthValue.setValue(0); + Animated.timing(widthValue, { + toValue: 1, + duration: lineConfig.animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [lineConfig.animationDuration, widthValue]); + + useEffect(() => { + if (showLine) { + let pp = ''; + const firstBarWidth = data[0].barWidth ?? props.barWidth ?? 30; + if (!lineConfig.curved) { + for (let i = 0; i < lineData.length; i++) { + if (i < lineConfig.startIndex || i > lineConfig.endIndex) continue; + const currentBarWidth = + data?.[i]?.barWidth ?? props.barWidth ?? BarDefaults.barWidth; + pp += + 'L' + + getXForLineInBar( + i, + firstBarWidth, + currentBarWidth, + yAxisLabelWidth, + lineConfig, + spacing, + ) + + ' ' + + getYForLineInBar( + lineData[i].value, + lineConfig.shiftY, + containerHeight, + maxValue, + ) + + ' '; + } + setPoints(pp.replace('L', 'M')); + if (lineData.length > 1 && lineConfig.showArrow) { + let ppArray = pp.trim().split(' '); + let arrowTipY = parseInt(ppArray[ppArray.length - 1]); + let arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + let y1 = parseInt(ppArray[ppArray.length - 3]); + let x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + let arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + lineConfig.arrowConfig.length, + lineConfig.arrowConfig.width, + lineConfig.arrowConfig.showArrowBase, + ); + + setArrowPoints(arrowPoints); + } + } else { + let p1Array: Array> = []; + for (let i = 0; i < lineData.length; i++) { + if (i < lineConfig.startIndex || i > lineConfig.endIndex) continue; + const currentBarWidth = + data?.[i]?.barWidth ?? props.barWidth ?? BarDefaults.barWidth; + p1Array.push([ + getXForLineInBar( + i, + firstBarWidth, + currentBarWidth, + yAxisLabelWidth, + lineConfig, + spacing, + ), + getYForLineInBar( + lineData[i].value, + lineConfig.shiftY, + containerHeight, + maxValue, + ), + ]); + let xx = svgPath(p1Array, lineConfig.curveType, lineConfig.curvature); + setPoints(xx); + } + } + if (lineConfig.isAnimated) { + setTimeout(() => decreaseWidth(), lineConfig.delay || 0); + } + } + setTimeout(() => labelsAppear(), animationDuration); + }, [ + animationDuration, + containerHeight, + data, + lineData, + decreaseWidth, + initialSpacing, + labelsAppear, + lineConfig.initialSpacing, + lineConfig.curved, + lineConfig.dataPointsWidth, + lineConfig.shiftY, + lineConfig.isAnimated, + lineConfig.delay, + lineConfig.startIndex, + lineConfig.endIndex, + maxValue, + props.barWidth, + showLine, + spacing, + yAxisLabelWidth, + lineConfig.showArrow, + lineConfig.arrowConfig.length, + lineConfig.arrowConfig.width, + lineConfig.arrowConfig.showArrowBase, + ]); + + // horizSections.pop(); + // for (let i = 0; i <= noOfSections; i++) { + // let value = maxValue - stepValue * i; + // if (showFractionalValues || props.roundToDigits) { + // value = parseFloat( + // value.toFixed( + // props.roundToDigits ?? AxesAndRulesDefaults.roundToDigits, + // ), + // ); + // } + // horizSections.push({ + // value: props.yAxisLabelTexts + // ? props.yAxisLabelTexts[noOfSections - i] ?? value.toString() + // : value.toString(), + // }); + // } + if (noOfSectionsBelowXAxis) { + for (let i = 1; i <= noOfSectionsBelowXAxis; i++) { + let value = stepValue * -i; + if (showFractionalValues || props.roundToDigits) { + value = parseFloat( + value.toFixed(props.roundToDigits ?? AxesAndRulesDefaults.roundToDigits), + ); + } + horizSectionsBelow.push({ + value: props.yAxisLabelTexts + ? props.yAxisLabelTexts[noOfSectionsBelowXAxis - i] ?? value.toString() + : value.toString(), + }); + } + } + + const animatedHeight = heightValue.interpolate({ + inputRange: [0, 1], + outputRange: ['0%', '100%'], + }); + const appearingOpacity = opacValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + }); + + const animatedWidth = widthValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const renderChartContent = () => { + const getPropsCommonForBarAndStack = (item, index) => { + return { + key: index, + item: item, + index: index, + containerHeight: containerHeight, + maxValue: maxValue, + spacing: item.spacing ?? spacing, + propSpacing: spacing, + xAxisThickness: xAxisThickness, + barWidth: props.barWidth, + opacity: opacity, + disablePress: item.disablePress || props.disablePress, + rotateLabel: rotateLabel, + showXAxisIndices: showXAxisIndices, + xAxisIndicesHeight: xAxisIndicesHeight, + xAxisIndicesWidth: xAxisIndicesWidth, + xAxisIndicesColor: xAxisIndicesColor, + horizontal: horizontal, + rtl: rtl, + intactTopLabel: intactTopLabel, + barBorderRadius: props.barBorderRadius, + barBorderTopLeftRadius: props.barBorderTopLeftRadius, + barBorderTopRightRadius: props.barBorderTopRightRadius, + barBorderBottomLeftRadius: props.barBorderBottomLeftRadius, + barBorderBottomRightRadius: props.barBorderBottomRightRadius, + color: props.color, + showGradient: props.showGradient, + gradientColor: props.gradientColor, + barBackgroundPattern: props.barBackgroundPattern, + patternId: props.patternId, + onPress: props.onPress, + xAxisTextNumberOfLines: xAxisTextNumberOfLines, + renderTooltip: props.renderTooltip, + leftShiftForTooltip: props.leftShiftForTooltip || 0, + initialSpacing: initialSpacing, + selectedIndex: selectedIndex, + setSelectedIndex: setSelectedIndex, + activeOpacity: props.activeOpacity || 0.2, + + leftShiftForLastIndexTooltip: props.leftShiftForLastIndexTooltip || 0, + label: + item.label || + (props.xAxisLabelTexts && props.xAxisLabelTexts[index] + ? props.xAxisLabelTexts[index] + : ''), + labelTextStyle: item.labelTextStyle || props.xAxisLabelTextStyle, + }; + }; + if (props.stackData) { + return props.stackData.map((item, index) => { + return ( + + ); + }); + } else { + return data.map((item, index) => ( + + )); + } + }; + + const remainingScrollViewProps = { + onTouchStart: (evt) => { + if (props.renderTooltip) { + setSelectedIndex(-1); + } + }, + }; + + const barAndLineChartsWrapperProps: BarAndLineChartsWrapperTypes = { + chartType: chartTypes.BAR, + containerHeight, + horizSectionsBelow, + stepHeight, + labelsExtraHeight, + yAxisLabelWidth, + horizontal, + rtl, + shiftX: props.shiftX ?? 0, + shiftY: props.shiftY ?? 0, + scrollRef, + yAxisAtTop, + initialSpacing, + data, + stackData: props.stackData, + secondaryData: secondaryData, + barWidth: props.barWidth || BarDefaults.barWidth, + xAxisThickness, + totalWidth, + disableScroll, + showScrollIndicator, + scrollToEnd, + scrollToIndex: props.scrollToIndex, + scrollAnimation, + indicatorColor: props.indicatorColor, + setSelectedIndex, + spacing, + showLine, + lineConfig, + maxValue, + lineData, + animatedWidth, + lineBehindBars, + points, + arrowPoints, + renderChartContent, + remainingScrollViewProps, + + //horizSectionProps- + width: widthFromProps, + horizSections, + endSpacing, + horizontalRulesStyle, + noOfSections, + showFractionalValues, + + axesAndRulesProps: getAxesAndRulesProps(props, stepValue), + + yAxisLabelTexts: props.yAxisLabelTexts, + yAxisOffset: props.yAxisOffset, + rotateYAxisTexts: props.rotateYAxisTexts, + hideAxesAndRules: props.hideAxesAndRules, + + showXAxisIndices, + xAxisIndicesHeight, + xAxisIndicesWidth, + xAxisIndicesColor, + + // These are Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + pointerConfig: null, + getPointerProps: null, + pointerIndex: 0, + pointerX: 0, + pointerY: 0, + }; + + return ; +}; diff --git a/src/components/charts/BarChart/styles.tsx b/src/components/charts/BarChart/styles.tsx new file mode 100644 index 00000000..f6af90e2 --- /dev/null +++ b/src/components/charts/BarChart/styles.tsx @@ -0,0 +1,47 @@ +import {StyleSheet} from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + width: '100%', + marginBottom: 40, + marginRight: 40, + }, + horizBar: { + flexDirection: 'row', + }, + leftLabel: { + justifyContent: 'center', + alignItems: 'center', + }, + lastLeftLabel: { + justifyContent: 'center', + alignItems: 'center', + }, + leftPart: { + justifyContent: 'center', + width: '100%', + }, + lastLeftPart: { + justifyContent: 'flex-end', + width: '100%', + }, + line: { + width: '100%', + height: 1, + backgroundColor: 'gray', + opacity: 0.5, + }, + lastLine: { + width: '100%', + height: 1, + backgroundColor: 'black', + }, + bottomLabel: { + width: '100%', + }, + customDataPointContainer: { + position: 'absolute', + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/src/components/charts/BarChart/types.ts b/src/components/charts/BarChart/types.ts new file mode 100644 index 00000000..e6f47744 --- /dev/null +++ b/src/components/charts/BarChart/types.ts @@ -0,0 +1,264 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import { ColorValue, View } from 'react-native'; +import { stackItemType } from '../BarChart/RenderStackBars'; +import { yAxisSides } from '../utils/constants'; +import { CurveType, RuleType, secondaryYAxisType } from '../utils/types'; + +export type BarChartPropsType = { + width?: number; + height?: number; + minHeight?: number; + noOfSections?: number; + noOfSectionsBelowXAxis?: number; + maxValue?: number; + minValue?: number; + stepHeight?: number; + stepValue?: number; + spacing?: number; + data?: Array; + stackData?: Array; + side?: String; + rotateLabel?: boolean; + isAnimated?: boolean; + animationDuration?: number; + // animationEasing?: any; + opacity?: number; + isThreeD?: boolean; + xAxisLength?: number; + xAxisThickness?: number; + xAxisColor?: ColorValue; + yAxisThickness?: number; + yAxisColor?: ColorValue; + xAxisType?: RuleType; + yAxisLabelContainerStyle?: any; + horizontalRulesStyle?: any; + yAxisTextStyle?: any; + yAxisTextNumberOfLines?: number; + xAxisTextNumberOfLines?: number; + yAxisLabelWidth?: number; + hideYAxisText?: boolean; + rotateYAxisTexts?: number; + yAxisSide?: yAxisSides; + yAxisOffset?: number; + initialSpacing?: number; + endSpacing?: number; + barWidth?: number; + sideWidth?: number; + showLine?: boolean; + lineData?: any; + lineConfig?: lineConfigType; + lineBehindBars?: boolean; + + cappedBars?: boolean; + capThickness?: number; + capColor?: ColorValue; + capRadius?: number; + + hideAxesAndRules?: boolean; + hideRules?: boolean; + rulesLength?: number; + rulesColor?: ColorValue; + rulesThickness?: number; + rulesType?: RuleType; + dashWidth?: number; + dashGap?: number; + showReferenceLine1?: boolean; + referenceLine1Config?: referenceConfigType; + referenceLine1Position?: number; + showReferenceLine2?: boolean; + referenceLine2Config?: referenceConfigType; + referenceLine2Position?: number; + showReferenceLine3?: boolean; + referenceLine3Config?: referenceConfigType; + referenceLine3Position?: number; + showVerticalLines?: boolean; + verticalLinesThickness?: number; + verticalLinesHeight?: number; + verticalLinesColor?: ColorValue; + verticalLinesType?: RuleType; + verticalLinesShift?: number; + verticalLinesZIndex?: number; + noOfVerticalLines?: number; + verticalLinesSpacing?: number; + + showYAxisIndices?: boolean; + showXAxisIndices?: boolean; + yAxisIndicesHeight?: number; + xAxisIndicesHeight?: number; + yAxisIndicesWidth?: number; + xAxisIndicesWidth?: number; + xAxisIndicesColor?: ColorValue; + yAxisIndicesColor?: ColorValue; + + showFractionalValues?: boolean; + roundToDigits?: number; + backgroundColor?: ColorValue; + + disableScroll?: boolean; + showScrollIndicator?: boolean; + indicatorColor?: 'black' | 'default' | 'white'; + roundedTop?: boolean; + roundedBottom?: boolean; + disablePress?: boolean; + + frontColor?: ColorValue; + color?: ColorValue; + sideColor?: ColorValue; + topColor?: ColorValue; + gradientColor?: ColorValue; + showGradient?: boolean; + activeOpacity?: number; + + horizontal?: boolean; + rtl?: boolean; + shiftX?: number; + shiftY?: number; + yAxisAtTop?: boolean; + + intactTopLabel?: boolean; + + horizSections?: Array; + barBorderRadius?: number; + barBorderTopLeftRadius?: number; + barBorderTopRightRadius?: number; + barBorderBottomLeftRadius?: number; + barBorderBottomRightRadius?: number; + hideOrigin?: boolean; + labelWidth?: number; + yAxisLabelTexts?: Array; + xAxisLabelTexts?: Array; + xAxisLabelTextStyle?: any; + yAxisLabelPrefix?: String; + yAxisLabelSuffix?: String; + autoShiftLabels?: boolean; + scrollRef?: any; + scrollToEnd?: boolean; + scrollToIndex?: number; + scrollAnimation?: boolean; + labelsExtraHeight?: number; + barBackgroundPattern?: Function; + patternId?: String; + barMarginBottom?: number; + onPress?: Function; + renderTooltip?: Function; + leftShiftForTooltip?: number; + leftShiftForLastIndexTooltip?: number; + barStyle?: object; + + secondaryData?: Array; + secondaryYAxis?: secondaryYAxisType | boolean; +}; +type lineConfigType = { + initialSpacing?: number; + curved?: boolean; + curvature?: number; + curveType?: CurveType; + isAnimated?: boolean; + animationDuration?: number; + delay?: number; + thickness?: number; + color?: ColorValue | String | any; + hideDataPoints?: boolean; + dataPointsShape?: String; + dataPointsWidth?: number; + dataPointsHeight?: number; + dataPointsColor?: ColorValue | String | any; + dataPointsRadius?: number; + textColor?: ColorValue | String | any; + textFontSize?: number; + textShiftX?: number; + textShiftY?: number; + shiftX?: number; + shiftY?: number; + startIndex?: number; + endIndex?: number; + showArrow?: boolean; + arrowConfig?: arrowType; + customDataPoint?: Function; +}; +export type defaultLineConfigType = { + initialSpacing: number; + curved: boolean; + curvature: number; + curveType: CurveType; + isAnimated: boolean; + animationDuration: number; + delay: number; + thickness: number; + color: ColorValue | String | any; + hideDataPoints: boolean; + dataPointsShape: String; + dataPointsWidth: number; + dataPointsHeight: number; + dataPointsColor: ColorValue | String | any; + dataPointsRadius: number; + textColor: ColorValue | String | any; + textFontSize: number; + textShiftX: number; + textShiftY: number; + shiftX: number; + shiftY: number; + startIndex: number; + endIndex: number; + showArrow: boolean; + arrowConfig: arrowType; + customDataPoint?: Function; +}; +type arrowType = { + length?: number; + width?: number; + strokeWidth?: number; + strokeColor?: string; + fillColor?: string; + showArrowBase?: boolean; +}; +type referenceConfigType = { + thickness: number; + width: number; + color: ColorValue | String | any; + type: String; + dashWidth: number; + dashGap: number; + labelText: String; + labelTextStyle: any; +}; +type sectionType = { + value: string; +}; +export type itemType = { + value: number; + onPress?: any; + frontColor?: ColorValue; + sideColor?: ColorValue; + topColor?: ColorValue; + showGradient?: boolean; + gradientColor?: any; + label?: String; + barWidth?: number; + sideWidth?: number; + labelTextStyle?: any; + topLabelComponent?: Function; + topLabelContainerStyle?: any; + disablePress?: any; + capThickness?: number; + capColor?: ColorValue; + capRadius?: number; + labelComponent?: Function; + barBorderRadius?: number; + barBorderTopLeftRadius?: number; + barBorderTopRightRadius?: number; + barBorderBottomLeftRadius?: number; + barBorderBottomRightRadius?: number; + topLabelComponentHeight?: number; + spacing?: number; + labelWidth?: number; + barBackgroundPattern?: Function; + patternId?: String; + barMarginBottom?: number; + leftShiftForTooltip?: number; + barStyle?: object; + showXAxisIndex?: boolean; +}; diff --git a/src/components/charts/Components/AnimatedBar/index.tsx b/src/components/charts/Components/AnimatedBar/index.tsx new file mode 100644 index 00000000..61e5b5a0 --- /dev/null +++ b/src/components/charts/Components/AnimatedBar/index.tsx @@ -0,0 +1,262 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { useEffect, useState } from 'react'; +import { View, StyleSheet, ColorValue, LayoutAnimation, Platform, UIManager } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Svg, { Defs, Rect } from 'react-native-svg'; +import { styles } from './styles'; + +if (Platform.OS === 'android') { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +type trianglePropTypes = { + style: any; + width: number; + color: ColorValue; +}; + +type animatedBarPropTypes = { + animationDuration: number; + width: number; + sideWidth?: number; + height: number; + showGradient: boolean; + gradientColor: any; + frontColor: ColorValue; + sideColor: ColorValue; + topColor: ColorValue; + topLabelComponent: any; + topLabelContainerStyle: any; + opacity: number; + side: String; + horizontal: boolean; + intactTopLabel: boolean; + barBackgroundPattern?: Function; + patternId?: String; + barStyle?: object; + item: any; +}; + +const TriangleCorner = (props: trianglePropTypes) => { + return ( + + ); +}; + +const triangleStyles = StyleSheet.create({ + triangleCorner: { + width: 0, + height: 0, + backgroundColor: 'transparent', + borderStyle: 'solid', + borderRightColor: 'transparent', + transform: [{ rotate: '90deg' }], + }, +}); + +const AnimatedBar = (props: animatedBarPropTypes) => { + const [initialRender, setInitialRender] = useState(true); + const [height, setHeight] = useState(Platform.OS === 'ios' ? 0 : 20); + + const animationDuration = props.animationDuration || 800; + + useEffect(() => { + const elevate = () => { + LayoutAnimation.configureNext({ + duration: animationDuration, + update: { type: 'linear', property: 'scaleY' }, + }); + setHeight(props.height); + }; + + const layoutAppear = () => { + LayoutAnimation.configureNext({ + duration: Platform.OS == 'ios' ? animationDuration : 20, + create: { type: 'linear', property: 'scaleY' }, + // update: { type: 'linear' } + }); + setInitialRender(false); + setTimeout(() => elevate(), Platform.OS == 'ios' ? 10 : 100); + }; + if (initialRender) { + // labelsAppear(); + // increaseOpacity(); + setTimeout(() => { + layoutAppear(); + }, 20); + } else { + elevate(); + } + }, [animationDuration, initialRender, props.height]); + + const { item, width, sideWidth, barStyle } = props; + + const { barBackgroundPattern, patternId } = props; + + const showGradient = props.showGradient || false; + const gradientColor = props.gradientColor || 'white'; + + const frontColor = props.frontColor || '#fe2233'; + const sideColor = props.sideColor || '#cc2233'; + const topColor = props.topColor || '#ff4433'; + + const topLabelComponent = props.topLabelComponent || null; + const topLabelContainerStyle = props.topLabelContainerStyle || {}; + + const opacity = props.opacity || 1; + + return ( + + {!initialRender && ( + + {/******************* Top View *****************/} + {props.height ? ( + <> + + + + + + + + + + + ) : null} + + {/*******************************************************************/} + + {props.height ? ( + + + + + + ) : null} + + + {showGradient && ( + + )} + {barBackgroundPattern && ( + + {barBackgroundPattern()} + + + )} + + + {/******************* Top Label *****************/} + + {topLabelComponent && ( + + {topLabelComponent()} + + )} + + {/*******************************************************************/} + + )} + + ); +}; + +export default AnimatedBar; diff --git a/src/components/charts/Components/AnimatedBar/styles.tsx b/src/components/charts/Components/AnimatedBar/styles.tsx new file mode 100644 index 00000000..956751ff --- /dev/null +++ b/src/components/charts/Components/AnimatedBar/styles.tsx @@ -0,0 +1,14 @@ +import {StyleSheet} from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1, + // width: '100%', + // height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + row: { + flexDirection: 'row', + }, +}); diff --git a/src/components/charts/Components/BarAndLineChartsWrapper/index.tsx b/src/components/charts/Components/BarAndLineChartsWrapper/index.tsx new file mode 100644 index 00000000..707e22fe --- /dev/null +++ b/src/components/charts/Components/BarAndLineChartsWrapper/index.tsx @@ -0,0 +1,387 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { Fragment } from 'react'; +import { View, ScrollView, ViewStyle } from 'react-native'; +import { renderHorizSections } from './renderHorizSections'; +import RenderLineInBarChart from './renderLineInBarChart'; +import RenderVerticalLines from './renderVerticalLines'; +import { AxesAndRulesDefaults, BarDefaults, chartTypes, yAxisSides } from '../../utils/constants'; +import { BarAndLineChartsWrapperTypes, horizSectionPropTypes } from '../../utils/types'; + +const BarAndLineChartsWrapper = (props: BarAndLineChartsWrapperTypes) => { + const { + chartType, + containerHeight, + horizSectionsBelow, + stepHeight, + labelsExtraHeight, + yAxisLabelWidth, + horizontal, + rtl, + shiftX, + shiftY, + scrollRef, + initialSpacing, + data, + stackData, + secondaryData, + barWidth, + xAxisThickness, + totalWidth, + disableScroll, + showScrollIndicator, + scrollToEnd, + scrollToIndex, + scrollAnimation, + indicatorColor, + setSelectedIndex, + spacing, + showLine, + lineConfig, + maxValue, + lineData, + animatedWidth, + lineBehindBars, + points, + arrowPoints, + renderChartContent, + remainingScrollViewProps, + + width, + horizSections, + endSpacing, + horizontalRulesStyle, + noOfSections, + showFractionalValues, + axesAndRulesProps, + + yAxisLabelTexts, + yAxisOffset, + rotateYAxisTexts, + hideAxesAndRules, + + showXAxisIndices, + xAxisIndicesHeight, + xAxisIndicesWidth, + xAxisIndicesColor, + + pointerConfig, + getPointerProps, + pointerIndex, + pointerX, + pointerY, + } = props; + + let yAxisAtTop = rtl ? !props.yAxisAtTop : props.yAxisAtTop; + + const hideOrigin = axesAndRulesProps.hideOrigin ?? AxesAndRulesDefaults.hideOrigin; + + const yAxisSide = axesAndRulesProps.yAxisSide ?? AxesAndRulesDefaults.yAxisSide; + const yAxisLabelContainerStyle = axesAndRulesProps.yAxisLabelContainerStyle; + const yAxisColor = axesAndRulesProps.yAxisColor ?? AxesAndRulesDefaults.yAxisColor; + const yAxisThickness = axesAndRulesProps.yAxisThickness ?? AxesAndRulesDefaults.yAxisThickness; + const xAxisColor = axesAndRulesProps.xAxisColor ?? AxesAndRulesDefaults.xAxisColor; + const xAxisLength = axesAndRulesProps.xAxisLength; + const xAxisType = axesAndRulesProps.xAxisType ?? AxesAndRulesDefaults.xAxisType; + const dashWidth = axesAndRulesProps.dashWidth ?? AxesAndRulesDefaults.dashWidth; + const dashGap = axesAndRulesProps.dashGap ?? AxesAndRulesDefaults.dashGap; + const backgroundColor = + axesAndRulesProps.backgroundColor ?? AxesAndRulesDefaults.backgroundColor; + const hideRules = axesAndRulesProps.hideRules ?? AxesAndRulesDefaults.hideRules; + const rulesLength = axesAndRulesProps.rulesLength; + const rulesType = axesAndRulesProps.rulesType ?? AxesAndRulesDefaults.rulesType; + const rulesThickness = axesAndRulesProps.rulesThickness ?? AxesAndRulesDefaults.rulesThickness; + const rulesColor = axesAndRulesProps.rulesColor ?? AxesAndRulesDefaults.rulesColor; + const showYAxisIndices = axesAndRulesProps.showYAxisIndices ?? false; + const yAxisIndicesHeight = + axesAndRulesProps.yAxisIndicesHeight ?? AxesAndRulesDefaults.yAxisIndicesHeight; + const yAxisIndicesWidth = + axesAndRulesProps.yAxisIndicesWidth ?? AxesAndRulesDefaults.yAxisIndicesWidth; + const yAxisIndicesColor = + axesAndRulesProps.yAxisIndicesColor ?? AxesAndRulesDefaults.yAxisIndicesColor; + const hideYAxisText = axesAndRulesProps.hideYAxisText ?? AxesAndRulesDefaults.hideYAxisText; + const yAxisTextNumberOfLines = + axesAndRulesProps.yAxisTextNumberOfLines ?? AxesAndRulesDefaults.yAxisTextNumberOfLines; + const yAxisLabelPrefix = axesAndRulesProps.yAxisLabelPrefix ?? ''; + const yAxisLabelSuffix = axesAndRulesProps.yAxisLabelSuffix ?? ''; + const yAxisTextStyle = axesAndRulesProps.yAxisTextStyle; + const secondaryYAxis = axesAndRulesProps.secondaryYAxis; + const stepValue = axesAndRulesProps.stepValue; + const roundToDigits = axesAndRulesProps.roundToDigits; + + const referenceLinesConfig = axesAndRulesProps.referenceLinesConfig; + + const showVerticalLines = + axesAndRulesProps.showVerticalLines ?? AxesAndRulesDefaults.showVerticalLines; + const verticalLinesThickness = + axesAndRulesProps.verticalLinesThickness ?? AxesAndRulesDefaults.verticalLinesThickness; + const verticalLinesHeight = axesAndRulesProps.verticalLinesHeight; + const verticalLinesColor = + axesAndRulesProps.verticalLinesColor ?? AxesAndRulesDefaults.verticalLinesColor; + const verticalLinesType = + axesAndRulesProps.verticalLinesType ?? AxesAndRulesDefaults.verticalLinesType; + const verticalLinesShift = + axesAndRulesProps.verticalLinesShift ?? AxesAndRulesDefaults.verticalLinesShift; + const verticalLinesZIndex = + axesAndRulesProps.verticalLinesZIndex ?? AxesAndRulesDefaults.verticalLinesZIndex; + const verticalLinesSpacing = + axesAndRulesProps.verticalLinesSpacing ?? AxesAndRulesDefaults.verticalLinesSpacing; + const verticalLinesUptoDataPoint = + axesAndRulesProps.verticalLinesUptoDataPoint ?? + AxesAndRulesDefaults.verticalLinesUptoDataPoint; + const noOfVerticalLines = axesAndRulesProps.noOfVerticalLines; + + const verticalLinesAr = noOfVerticalLines + ? [...Array(noOfVerticalLines).keys()] + : [...Array(stackData ? stackData.length : data.length).keys()]; + + // const + + const horizSectionProps: horizSectionPropTypes = { + chartType, + width, + horizSections, + horizSectionsBelow, + totalWidth, + endSpacing, + yAxisSide, + horizontalRulesStyle, + noOfSections, + stepHeight, + yAxisLabelWidth, + yAxisLabelContainerStyle, + yAxisThickness, + yAxisColor, + xAxisThickness, + xAxisColor, + xAxisLength, + xAxisType, + dashWidth, + dashGap, + backgroundColor, + hideRules, + rulesLength, + rulesType, + rulesThickness, + rulesColor, + spacing, + showYAxisIndices, + yAxisIndicesHeight, + yAxisIndicesWidth, + yAxisIndicesColor, + + hideOrigin, + hideYAxisText, + showFractionalValues, + yAxisTextNumberOfLines, + yAxisLabelPrefix, + yAxisLabelSuffix, + yAxisTextStyle, + rotateYAxisTexts, + rtl, + + containerHeight, + maxValue, + + referenceLinesConfig, + + yAxisLabelTexts, + yAxisOffset, + + horizontal, + yAxisAtTop, + + stepValue, + roundToDigits, + + secondaryData, + secondaryYAxis, + }; + + const lineInBarChartProps = { + yAxisLabelWidth, + initialSpacing, + spacing, + containerHeight, + lineConfig, + maxValue, + animatedWidth, + lineBehindBars, + points, + arrowPoints, + data: lineData ?? data, + totalWidth, + barWidth, + labelsExtraHeight, + }; + const extendedContainerHeight = containerHeight + 10; + const containerHeightIncludingBelowXAxis = + extendedContainerHeight + horizSectionsBelow.length * stepHeight; + const verticalLinesProps = { + verticalLinesAr, + verticalLinesSpacing, + spacing, + initialSpacing, + verticalLinesZIndex, + verticalLinesHeight, + verticalLinesThickness, + verticalLinesColor, + verticalLinesType, + verticalLinesShift, + verticalLinesUptoDataPoint, + xAxisThickness, + labelsExtraHeight, + containerHeight, + data, + stackData, + barWidth, + maxValue, + chartType, + containerHeightIncludingBelowXAxis, + }; + + const actualContainerHeight = containerHeightIncludingBelowXAxis + labelsExtraHeight - 10; + const actualContainerWidth = (width ?? totalWidth) + yAxisLabelWidth; + + /*******************************************************************************************************************************************/ + /*************** horizontal chart related calculations *******************/ + /*******************************************************************************************************************************************/ + + const containerHeightIncludingXaxisLabels = + actualContainerHeight + BarDefaults.labelsWidthForHorizontal; + + const difBwWidthHeight = actualContainerWidth - containerHeightIncludingXaxisLabels; + + const transformForHorizontal = [ + { rotate: rtl ? '-90deg' : '90deg' }, + { + translateY: -shiftX + (rtl ? -difBwWidthHeight + 14 : difBwWidthHeight) / 2 - 20, + }, + { + translateX: + shiftY + + (rtl + ? (props.width ? -98 - endSpacing : -75 - endSpacing) - difBwWidthHeight + : props.width + ? difBwWidthHeight + : difBwWidthHeight - 40) / + 2 + + (yAxisAtTop ? (rtl ? (props.width ? 12 : 40) : 12) : 52), + }, + ]; + + /*******************************************************************************************************************************************/ + /*******************************************************************************************************************************************/ + + const container = { + width: horizontal ? actualContainerWidth : '100%', + height: actualContainerHeight, + marginBottom: 40, //This is to not let the Things that should be rendered below the chart overlap with it + } as ViewStyle; + + return ( + + {hideAxesAndRules !== true && renderHorizSections(horizSectionProps)} + { + if (scrollRef.current && scrollToEnd) { + scrollRef.current.scrollToEnd({ animated: scrollAnimation }); + } else if (scrollRef.current && scrollToIndex) { + scrollRef.current.scrollTo({ + x: + initialSpacing + + ((barWidth ?? 0) + spacing) * scrollToIndex - + spacing, + }); + } + }} + {...remainingScrollViewProps} + > + + {showVerticalLines && } + { + // Only For Bar Charts- + showLine ? : null + } + { + // Only For Line Charts- + chartType === chartTypes.LINE && + data.map((item: any, index: number) => { + return showXAxisIndices || item.showXAxisIndex ? ( + + ) : null; + }) + } + {renderChartContent()} + + + { + // Only For Line Charts- + pointerConfig && + getPointerProps && + getPointerProps({ pointerIndex, pointerX, pointerY }) + } + + ); +}; + +export default BarAndLineChartsWrapper; diff --git a/src/components/charts/Components/BarAndLineChartsWrapper/renderHorizSections.tsx b/src/components/charts/Components/BarAndLineChartsWrapper/renderHorizSections.tsx new file mode 100644 index 00000000..62757407 --- /dev/null +++ b/src/components/charts/Components/BarAndLineChartsWrapper/renderHorizSections.tsx @@ -0,0 +1,756 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React from 'react'; +import { Text, View } from 'react-native'; +import Rule from '../lineSvg'; +import { styles } from '../../LineChart/styles'; +import { AxesAndRulesDefaults, chartTypes, yAxisSides } from '../../utils/constants'; +import { HorizSectionsType, horizSectionPropTypes, secondaryYAxisType } from '../../utils/types'; +import { computeMaxAndMinItems, getLabelTextUtil } from '../../utils'; + +export const renderHorizSections = (props: horizSectionPropTypes) => { + const { + chartType, + width, + horizSections: h, + horizSectionsBelow, + totalWidth, + endSpacing, + yAxisSide, + horizontalRulesStyle, + noOfSections, + stepHeight, + yAxisLabelWidth, + yAxisLabelContainerStyle, + yAxisThickness, + yAxisColor, + xAxisThickness, + xAxisColor, + xAxisLength, + xAxisType, + dashWidth, + dashGap, + backgroundColor, + hideRules, + rulesLength, + rulesType, + rulesThickness, + rulesColor, + spacing, + showYAxisIndices, + yAxisIndicesHeight, + yAxisIndicesWidth, + yAxisIndicesColor, + + hideOrigin, + hideYAxisText, + showFractionalValues, + yAxisTextNumberOfLines, + yAxisLabelPrefix, + yAxisLabelSuffix, + yAxisTextStyle, + rotateYAxisTexts, + rtl, + + containerHeight, + maxValue, + + referenceLinesConfig, + + yAxisLabelTexts, + yAxisOffset, + + horizontal, + yAxisAtTop, + + stepValue, + roundToDigits, + + secondaryData, + secondaryYAxis, + } = props; + + /*********************************************************************************************************************************** + * * + ***************************** secondary Y Axis related props computations ****************************** + * * + ***********************************************************************************************************************************/ + + const secondaryYAxisConfig: secondaryYAxisType = { + noOfSections: secondaryYAxis?.noOfSections ?? noOfSections, + maxValue: secondaryYAxis?.maxValue, + minValue: secondaryYAxis?.minValue, + stepValue: secondaryYAxis?.stepValue, + stepHeight: secondaryYAxis?.stepHeight, + + showFractionalValues: secondaryYAxis?.showFractionalValues ?? showFractionalValues, + roundToDigits: secondaryYAxis?.roundToDigits ?? roundToDigits, + + showYAxisIndices: secondaryYAxis?.showYAxisIndices ?? showYAxisIndices, + yAxisIndicesHeight: secondaryYAxis?.yAxisIndicesHeight ?? yAxisIndicesHeight, + yAxisIndicesWidth: secondaryYAxis?.yAxisIndicesWidth ?? yAxisIndicesWidth, + yAxisIndicesColor: secondaryYAxis?.yAxisIndicesColor ?? yAxisIndicesColor, + + yAxisSide: secondaryYAxis?.yAxisSide ?? yAxisSide, + yAxisOffset: secondaryYAxis?.yAxisOffset, + yAxisThickness: secondaryYAxis?.yAxisThickness ?? yAxisThickness, + yAxisColor: secondaryYAxis?.yAxisColor ?? yAxisColor, + yAxisLabelContainerStyle: + secondaryYAxis?.yAxisLabelContainerStyle ?? yAxisLabelContainerStyle, + yAxisLabelTexts: secondaryYAxis?.yAxisLabelTexts ?? yAxisLabelTexts, + yAxisTextStyle: secondaryYAxis?.yAxisTextStyle ?? yAxisTextStyle, + yAxisTextNumberOfLines: secondaryYAxis?.yAxisTextNumberOfLines ?? yAxisTextNumberOfLines, + yAxisLabelWidth: secondaryYAxis?.yAxisLabelWidth ?? yAxisLabelWidth, + hideYAxisText: secondaryYAxis?.hideYAxisText ?? hideYAxisText, + yAxisLabelPrefix: secondaryYAxis?.yAxisLabelPrefix ?? yAxisLabelPrefix, + yAxisLabelSuffix: secondaryYAxis?.yAxisLabelSuffix ?? yAxisLabelSuffix, + hideOrigin: secondaryYAxis?.hideOrigin ?? hideOrigin, + }; + + const { maxItem, minItem } = computeMaxAndMinItems( + secondaryData, + secondaryYAxisConfig.roundToDigits, + secondaryYAxisConfig.showFractionalValues, + ); + secondaryYAxisConfig.maxValue = secondaryYAxisConfig.maxValue ?? (maxItem || maxValue); + secondaryYAxisConfig.minValue = secondaryYAxisConfig.maxValue ?? minItem; + secondaryYAxisConfig.stepValue = + secondaryYAxisConfig.stepValue ?? + secondaryYAxisConfig.maxValue / (secondaryYAxisConfig.noOfSections ?? noOfSections); + secondaryYAxisConfig.stepHeight = + secondaryYAxisConfig.stepHeight || + containerHeight / (secondaryYAxisConfig.noOfSections ?? noOfSections); + + const horizSections: HorizSectionsType = []; + for (let i = 0; i <= noOfSections; i++) { + let value = maxValue - stepValue * i; + if (showFractionalValues || roundToDigits) { + value = parseFloat(value.toFixed(roundToDigits ?? AxesAndRulesDefaults.roundToDigits)); + } + horizSections.push({ + value: yAxisLabelTexts?.length + ? yAxisLabelTexts[noOfSections - i] ?? value.toString() + : value.toString(), + }); + } + + const secondaryHorizSections: HorizSectionsType = []; + if (secondaryYAxis) { + for (let i = 0; i <= (secondaryYAxisConfig.noOfSections ?? noOfSections); i++) { + let value = secondaryYAxisConfig.stepValue * i; + if (secondaryYAxisConfig.showFractionalValues || secondaryYAxisConfig.roundToDigits) { + value = parseFloat( + value.toFixed( + secondaryYAxisConfig.roundToDigits ?? AxesAndRulesDefaults.roundToDigits, + ), + ); + } + secondaryHorizSections.push({ + value: secondaryYAxisConfig.yAxisLabelTexts?.length + ? secondaryYAxisConfig.yAxisLabelTexts[i] ?? value.toString() + : value.toString(), + }); + } + } + + /*********************************************************************************************************************************** + ***********************************************************************************************************************************/ + + let { + showReferenceLine1, + referenceLine1Position, + referenceLine1Config, + + showReferenceLine2, + referenceLine2Position, + referenceLine2Config, + + showReferenceLine3, + referenceLine3Position, + referenceLine3Config, + } = referenceLinesConfig; + + const defaultReferenceConfig = { + thickness: rulesThickness, + width: (width || totalWidth - spacing) + endSpacing, + color: 'black', + type: rulesType, + dashWidth: dashWidth, + dashGap: dashGap, + labelText: '', + labelTextStyle: null, + }; + + showReferenceLine1 = referenceLinesConfig.showReferenceLine1 || false; + referenceLine1Position = + referenceLinesConfig.referenceLine1Position ?? + (referenceLinesConfig.referenceLine1Position || containerHeight / 2); + referenceLine1Config = referenceLinesConfig.referenceLine1Config + ? { + thickness: + referenceLinesConfig.referenceLine1Config.thickness || + defaultReferenceConfig.thickness, + width: + referenceLinesConfig.referenceLine1Config.width ?? defaultReferenceConfig.width, + color: + referenceLinesConfig.referenceLine1Config.color || defaultReferenceConfig.color, + type: referenceLinesConfig.referenceLine1Config.type || defaultReferenceConfig.type, + dashWidth: + referenceLinesConfig.referenceLine1Config.dashWidth || + defaultReferenceConfig.dashWidth, + dashGap: + referenceLinesConfig.referenceLine1Config.dashGap || + defaultReferenceConfig.dashGap, + labelText: + referenceLinesConfig.referenceLine1Config.labelText || + defaultReferenceConfig.labelText, + labelTextStyle: + referenceLinesConfig.referenceLine1Config.labelTextStyle || + defaultReferenceConfig.labelTextStyle, + } + : defaultReferenceConfig; + + showReferenceLine2 = referenceLinesConfig.showReferenceLine2 || false; + referenceLine2Position = + referenceLinesConfig.referenceLine2Position ?? + (referenceLinesConfig.referenceLine2Position || (3 * containerHeight) / 2); + referenceLine2Config = referenceLinesConfig.referenceLine2Config + ? { + thickness: + referenceLinesConfig.referenceLine2Config.thickness || + defaultReferenceConfig.thickness, + width: + referenceLinesConfig.referenceLine2Config.width ?? defaultReferenceConfig.width, + color: + referenceLinesConfig.referenceLine2Config.color || defaultReferenceConfig.color, + type: referenceLinesConfig.referenceLine2Config.type || defaultReferenceConfig.type, + dashWidth: + referenceLinesConfig.referenceLine2Config.dashWidth || + defaultReferenceConfig.dashWidth, + dashGap: + referenceLinesConfig.referenceLine2Config.dashGap || + defaultReferenceConfig.dashGap, + labelText: + referenceLinesConfig.referenceLine2Config.labelText || + defaultReferenceConfig.labelText, + labelTextStyle: + referenceLinesConfig.referenceLine2Config.labelTextStyle || + defaultReferenceConfig.labelTextStyle, + } + : defaultReferenceConfig; + + showReferenceLine3 = referenceLinesConfig.showReferenceLine3 || false; + referenceLine3Position = + referenceLinesConfig.referenceLine3Position ?? + (referenceLinesConfig.referenceLine3Position || containerHeight / 3); + referenceLine3Config = referenceLinesConfig.referenceLine3Config + ? { + thickness: + referenceLinesConfig.referenceLine3Config.thickness || + defaultReferenceConfig.thickness, + width: + referenceLinesConfig.referenceLine3Config.width ?? defaultReferenceConfig.width, + color: + referenceLinesConfig.referenceLine3Config.color || defaultReferenceConfig.color, + type: referenceLinesConfig.referenceLine3Config.type || defaultReferenceConfig.type, + dashWidth: + referenceLinesConfig.referenceLine3Config.dashWidth || + defaultReferenceConfig.dashWidth, + dashGap: + referenceLinesConfig.referenceLine3Config.dashGap || + defaultReferenceConfig.dashGap, + labelText: + referenceLinesConfig.referenceLine3Config.labelText || + defaultReferenceConfig.labelText, + labelTextStyle: + referenceLinesConfig.referenceLine3Config.labelTextStyle || + defaultReferenceConfig.labelTextStyle, + } + : defaultReferenceConfig; + + const getLabelTexts = (val, index) => { + return getLabelTextUtil( + val, + index, + showFractionalValues, + yAxisLabelTexts, + yAxisOffset, + yAxisLabelPrefix, + yAxisLabelSuffix, + ); + }; + + const getLabelTextsForSecondaryYAxis = (val, index) => { + const { + showFractionalValues, + yAxisLabelTexts, + yAxisOffset, + yAxisLabelPrefix, + yAxisLabelSuffix, + } = secondaryYAxisConfig; + return getLabelTextUtil( + val, + index, + showFractionalValues, + yAxisLabelTexts, + yAxisOffset, + yAxisLabelPrefix, + yAxisLabelSuffix, + ); + }; + + const renderAxesAndRules = (index: number) => ( + + {index === noOfSections ? ( + + ) : hideRules ? null : ( + + )} + {showYAxisIndices && index !== noOfSections ? ( + + ) : null} + + ); + + return ( + + + {horizSections.map((sectionItems, index) => { + return ( + + + {renderAxesAndRules(index)} + + ); + })} + + { + /***********************************************************************************************/ + /************************** Render the y axis labels separately **********************/ + /***********************************************************************************************/ + + !hideYAxisText && + horizSections.map((sectionItems, index) => { + let label = getLabelTexts(sectionItems.value, index); + if (hideOrigin && index === horizSections.length - 1) { + label = ''; + } + return ( + + + {label} + + + ); + }) + /***********************************************************************************************/ + /***********************************************************************************************/ + } + + {horizSectionsBelow.map((sectionItems, index) => { + return ( + + + + {hideRules ? null : ( + + )} + + + ); + })} + + { + /***********************************************************************************************/ + /************************* Render the y axis labels below origin *********************/ + /***********************************************************************************************/ + + !hideYAxisText && + horizSectionsBelow.map((sectionItems, index) => { + let label = getLabelTexts( + horizSectionsBelow[horizSectionsBelow.length - 1 - index].value, + index, + ); + return ( + + + {label} + + + ); + }) + /***********************************************************************************************/ + /***********************************************************************************************/ + } + + { + /***********************************************************************************************/ + /************************* Render the reference lines separately *********************/ + /***********************************************************************************************/ + + !hideYAxisText && + horizSections.map((sectionItems, index) => { + // let label = getLabelTexts(sectionItems.value, index); + // if (hideOrigin && index === horizSections.length - 1) { + // label = ''; + // } + return ( + + {index === noOfSections && showReferenceLine1 ? ( + + + {referenceLine1Config.labelText ? ( + + {referenceLine1Config.labelText} + + ) : null} + + ) : null} + {index === noOfSections && showReferenceLine2 ? ( + + + {referenceLine2Config.labelText ? ( + + {referenceLine2Config.labelText} + + ) : null} + + ) : null} + {index === noOfSections && showReferenceLine3 ? ( + + + {referenceLine3Config.labelText ? ( + + {referenceLine3Config.labelText} + + ) : null} + + ) : null} + + ); + }) + /***********************************************************************************************/ + /***********************************************************************************************/ + } + + { + /***********************************************************************************************/ + /************************* Render the secondary Y Axis *********************/ + /***********************************************************************************************/ + secondaryYAxis && ( + + {!secondaryYAxisConfig.hideYAxisText && + secondaryHorizSections.map((sectionItems, index) => { + let label = getLabelTextsForSecondaryYAxis( + sectionItems.value, + index, + ); + if ( + secondaryYAxisConfig.hideOrigin && + index === secondaryHorizSections.length - 1 + ) { + label = ''; + } + return ( + + {secondaryYAxisConfig.showYAxisIndices && index !== 0 ? ( + + ) : null} + + {label} + + + ); + })} + + ) + } + + ); +}; diff --git a/src/components/charts/Components/BarAndLineChartsWrapper/renderLineInBarChart.tsx b/src/components/charts/Components/BarAndLineChartsWrapper/renderLineInBarChart.tsx new file mode 100644 index 00000000..3094d93a --- /dev/null +++ b/src/components/charts/Components/BarAndLineChartsWrapper/renderLineInBarChart.tsx @@ -0,0 +1,404 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { Fragment } from 'react'; +import { View, Animated } from 'react-native'; +import Svg, { Circle, Path, Rect, Text as CanvasText } from 'react-native-svg'; +import { styles } from '../../BarChart/styles'; +import { getXForLineInBar, getYForLineInBar } from '../../utils'; + +const RenderLineInBarChart = (props) => { + const { + yAxisLabelWidth, + initialSpacing, + spacing, + containerHeight, + lineConfig, + maxValue, + animatedWidth, + lineBehindBars, + points, + arrowPoints, + data, + totalWidth, + barWidth, + labelsExtraHeight, + } = props; + + const firstBarWidth = data[0].barWidth ?? barWidth; + + const renderSpecificVerticalLines = (dataForRender: any) => { + return dataForRender.map((item: any, index: number) => { + if (item.showVerticalLine) { + const currentBarWidth = item.barWidth || barWidth || 30; + return ( + + ); + } + return null; + }); + }; + + const renderDataPoints = () => { + return data.map((item: any, index: number) => { + if (index < lineConfig.startIndex || index > lineConfig.endIndex) { + return null; + } + const currentBarWidth = item.barWidth || barWidth || 30; + const customDataPoint = item.customDataPoint || lineConfig.customDataPoint; + if (customDataPoint) { + return ( + + {customDataPoint()} + + ); + } + if (lineConfig.dataPointsShape === 'rectangular') { + return ( + + + {item.dataPointText && ( + + {item.dataPointText} + + )} + + ); + } + return ( + + + {item.dataPointText && ( + + {item.dataPointText} + + )} + + ); + }); + }; + const renderSpecificDataPoints = (dataForRender) => { + return dataForRender.map((item: any, index: number) => { + const currentBarWidth = item.barWidth || barWidth || 30; + if (item.showDataPoint) { + if (item.dataPointShape === 'rectangular') { + return ( + + + {item.dataPointText && ( + + {item.dataPointText} + + )} + + ); + } else { + return ( + + + {item.dataPointText && ( + + {item.dataPointText} + + )} + + ); + } + } + return null; + }); + }; + + const renderAnimatedLine = () => { + // console.log('animatedWidth is-------->', animatedWidth); + return ( + + + + + {renderSpecificVerticalLines(data)} + + {!lineConfig.hideDataPoints + ? renderDataPoints() + : renderSpecificDataPoints(data)} + {lineConfig.showArrow && ( + + )} + + + ); + }; + + const renderLine = () => { + return ( + + + + {renderSpecificVerticalLines(data)} + + {!lineConfig.hideDataPoints + ? renderDataPoints() + : renderSpecificDataPoints(data)} + {lineConfig.showArrow && ( + + )} + + + ); + }; + + if (lineConfig.isAnimated) { + return renderAnimatedLine(); + } + + return renderLine(); +}; + +export default RenderLineInBarChart; diff --git a/src/components/charts/Components/BarAndLineChartsWrapper/renderVerticalLines.tsx b/src/components/charts/Components/BarAndLineChartsWrapper/renderVerticalLines.tsx new file mode 100644 index 00000000..b9c56c24 --- /dev/null +++ b/src/components/charts/Components/BarAndLineChartsWrapper/renderVerticalLines.tsx @@ -0,0 +1,122 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React from 'react'; +import { View } from 'react-native'; +import { chartTypes, ruleTypes } from '../../utils/constants'; + +const RenderVerticalLines = (props) => { + const { + verticalLinesAr, + verticalLinesSpacing, + spacing, + initialSpacing, + verticalLinesZIndex, + verticalLinesHeight, + verticalLinesThickness, + verticalLinesColor, + verticalLinesType, + verticalLinesShift, + verticalLinesUptoDataPoint, + xAxisThickness, + labelsExtraHeight, + containerHeight, + data, + stackData, + barWidth, + maxValue, + chartType, + containerHeightIncludingBelowXAxis, + } = props; + + const getHeightOfVerticalLine = (index) => { + if (verticalLinesUptoDataPoint) { + if (index < data.length) { + return (data[index].value * containerHeight) / maxValue - xAxisThickness; + } else { + return verticalLinesHeight ?? 0; + } + } else { + return verticalLinesHeight || containerHeightIncludingBelowXAxis - xAxisThickness; + } + }; + + return verticalLinesAr.map((item: any, index: number) => { + let totalSpacing = initialSpacing; + if (verticalLinesSpacing) { + totalSpacing = verticalLinesSpacing * (index + 1); + } else { + if (stackData) { + totalSpacing += (stackData[0].barWidth || barWidth || 30) / 2; + } else { + totalSpacing += (data[0].barWidth || barWidth || 30) / 2; + } + for (let i = 0; i < index; i++) { + let actualSpacing = spacing; + if (stackData) { + if (i >= stackData.length - 1) { + actualSpacing += (barWidth || 30) / 2; + } else { + if (stackData[i].spacing || stackData[i].spacing === 0) { + actualSpacing = stackData[i].spacing; + } + if (stackData[i + 1].barWidth) { + actualSpacing += stackData[i + 1].barWidth; + } else { + actualSpacing += barWidth || 30; + } + } + } else { + if (i >= data.length - 1) { + actualSpacing += (barWidth || 30) / 2; + } else { + if (data[i].spacing || data[i].spacing === 0) { + actualSpacing = data[i].spacing; + } + if (data[i + 1].barWidth) { + actualSpacing += data[i + 1].barWidth; + } else { + actualSpacing += barWidth || 30; + } + } + } + totalSpacing += actualSpacing; + } + } + + return ( + + ); + }); +}; + +export default RenderVerticalLines; diff --git a/src/components/charts/Components/BarSpecificComponents/barBackgroundPattern.tsx b/src/components/charts/Components/BarSpecificComponents/barBackgroundPattern.tsx new file mode 100644 index 00000000..7c0160c4 --- /dev/null +++ b/src/components/charts/Components/BarSpecificComponents/barBackgroundPattern.tsx @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React from 'react'; +import Svg, { Defs, Rect } from 'react-native-svg'; + +const BarBackgroundPattern = (props) => { + const { + barBackgroundPatternFromItem, + barBackgroundPatternFromProps, + patternIdFromItem, + patternIdFromProps, + } = props; + return ( + + + {barBackgroundPatternFromItem + ? barBackgroundPatternFromItem() + : barBackgroundPatternFromProps()} + + + + ); +}; + +export default BarBackgroundPattern; diff --git a/src/components/charts/Components/BarSpecificComponents/cap.tsx b/src/components/charts/Components/BarSpecificComponents/cap.tsx new file mode 100644 index 00000000..c71a7b47 --- /dev/null +++ b/src/components/charts/Components/BarSpecificComponents/cap.tsx @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React from 'react'; +import { View } from 'react-native'; +import { BarDefaults } from '../../utils/constants'; + +const Cap = (props) => { + const { + capThicknessFromItem, + capThicknessFromProps, + capColorFromItem, + capColorFromProps, + capRadiusFromItem, + capRadiusFromProps, + } = props; + return ( + + ); +}; + +export default Cap; diff --git a/src/components/charts/Components/ThreeDBar/index.tsx b/src/components/charts/Components/ThreeDBar/index.tsx new file mode 100644 index 00000000..8fa193f1 --- /dev/null +++ b/src/components/charts/Components/ThreeDBar/index.tsx @@ -0,0 +1,215 @@ +import React from 'react'; +import {View, StyleSheet, ColorValue} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Svg, {Defs, Rect} from 'react-native-svg'; +import {styles} from './styles'; + +type PropTypes = { + style: any; + width: number; + sideWidth: number; + height: number; + color: ColorValue; + showGradient: boolean; + gradientColor: any; + frontColor: ColorValue; + sideColor: ColorValue; + topColor: ColorValue; + topLabelComponent: any; + topLabelContainerStyle: any; + opacity: number; + side: String; + horizontal: boolean; + intactTopLabel: boolean; + value: number; + barBackgroundPattern?: Function; + patternId?: String; + barStyle?: object; + item?: any; +}; + +type TriangleProps = { + color: ColorValue; + width: number; + style: any; +}; + +const TriangleCorner = (props: TriangleProps) => { + return ( + + ); +}; + +const aStyles = StyleSheet.create({ + triangleCorner: { + width: 0, + height: 0, + backgroundColor: 'transparent', + borderStyle: 'solid', + borderRightColor: 'transparent', + transform: [{rotate: '90deg'}], + }, +}); + +const ThreeDBar = (props: PropTypes) => { + const { + width, + sideWidth, + height, + value, + barBackgroundPattern, + patternId, + barStyle, + item, + } = props; + + const showGradient = props.showGradient || false; + const gradientColor = props.gradientColor || 'white'; + + const frontColor = props.frontColor || '#fe2233'; + const sideColor = props.sideColor || '#cc2233'; + const topColor = props.topColor || '#ff4433'; + + const topLabelComponent = props.topLabelComponent || null; + const topLabelContainerStyle = props.topLabelContainerStyle || {}; + + const opacity = props.opacity || 1; + return ( + + {props.height ? ( + + {/******************* Top View *****************/} + + + + + + + + + + + + {/*******************************************************************/} + + + + + + + + + {showGradient && ( + + )} + {barBackgroundPattern && ( + + {barBackgroundPattern()} + + + )} + + + ) : null} + + {/******************* Top Label *****************/} + + {topLabelComponent && ( + + {topLabelComponent()} + + )} + + {/*******************************************************************/} + + ); +}; + +export default ThreeDBar; diff --git a/src/components/charts/Components/ThreeDBar/styles.tsx b/src/components/charts/Components/ThreeDBar/styles.tsx new file mode 100644 index 00000000..956751ff --- /dev/null +++ b/src/components/charts/Components/ThreeDBar/styles.tsx @@ -0,0 +1,14 @@ +import {StyleSheet} from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1, + // width: '100%', + // height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + row: { + flexDirection: 'row', + }, +}); diff --git a/src/components/charts/Components/lineSvg.tsx b/src/components/charts/Components/lineSvg.tsx new file mode 100644 index 00000000..66d106f9 --- /dev/null +++ b/src/components/charts/Components/lineSvg.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import {ColorValue} from 'react-native'; +import Svg, {G, Path} from 'react-native-svg'; +import { ruleTypes } from '../utils/constants'; + +type ruleProps = { + thickness: number; + width: number; + color: ColorValue | String | any; + type: String; + dashWidth: number; + dashGap: number; +}; + +type configType = { + config: ruleProps; +}; + +function Rule(props: configType) { + const {thickness, width, color, type, dashWidth, dashGap} = props.config; + if (type === ruleTypes.SOLID) { + return ( + + + + + + ); + } + return ( + + + + + + ); +} + +export default Rule; diff --git a/src/components/charts/LineChart/LineChartBicolor.tsx b/src/components/charts/LineChart/LineChartBicolor.tsx new file mode 100644 index 00000000..47eb1db3 --- /dev/null +++ b/src/components/charts/LineChart/LineChartBicolor.tsx @@ -0,0 +1,1362 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { View, Animated, Easing, Text, ColorValue } from 'react-native'; +import { styles } from './styles'; +import Svg, { + Path, + LinearGradient, + Stop, + Circle, + Rect, + Text as CanvasText, +} from 'react-native-svg'; +import BarAndLineChartsWrapper from '../Components/BarAndLineChartsWrapper'; +import { getAxesAndRulesProps, getExtendedContainerHeightWithPadding } from '../utils'; +import { AxesAndRulesDefaults, LineDefaults, chartTypes, yAxisSides } from '../utils/constants'; +import { BarAndLineChartsWrapperTypes, HorizSectionsType, RuleType } from '../utils/types'; + +let initialData: Array | null = null; + +type propTypes = { + height?: number; + overflowTop?: number; + noOfSections?: number; + maxValue?: number; + minValue?: number; + stepHeight?: number; + stepValue?: number; + spacing?: number; + initialSpacing?: number; + endSpacing?: number; + data?: Array; + zIndex?: number; + thickness?: number; + strokeDashArray?: Array; + rotateLabel?: boolean; + isAnimated?: boolean; + animationDuration?: number; + onDataChangeAnimationDuration?: number; + animationEasing?: any; + xAxisLength?: number; + xAxisThickness?: number; + xAxisColor?: ColorValue; + xAxisType?: RuleType; + hideRules?: boolean; + rulesLength?: number; + rulesColor?: ColorValue; + rulesThickness?: number; + focusEnabled?: boolean; + onFocus?: Function; + showDataPointOnFocus?: boolean; + showStripOnFocus?: boolean; + showTextOnFocus?: boolean; + stripHeight?: number; + stripWidth?: number; + stripColor?: ColorValue | String | any; + stripOpacity?: number; + onPress?: Function; + unFocusOnPressOut?: boolean; + delayBeforeUnFocus?: number; + + rulesType?: RuleType; + dashWidth?: number; + dashGap?: number; + showReferenceLine1?: boolean; + referenceLine1Config?: referenceConfigType; + referenceLine1Position?: number; + showReferenceLine2?: boolean; + referenceLine2Config?: referenceConfigType; + referenceLine2Position?: number; + showReferenceLine3?: boolean; + referenceLine3Config?: referenceConfigType; + referenceLine3Position?: number; + + showVerticalLines?: boolean; + verticalLinesUptoDataPoint?: boolean; + verticalLinesThickness?: number; + verticalLinesHeight?: number; + verticalLinesColor?: ColorValue; + verticalLinesType?: string; + verticalLinesShift?: number; + verticalLinesZIndex?: number; + noOfVerticalLines?: number; + verticalLinesSpacing?: number; + hideAxesAndRules?: boolean; + areaChart?: boolean; + + disableScroll?: boolean; + showScrollIndicator?: boolean; + indicatorColor?: 'black' | 'default' | 'white'; + + //Indices + + showYAxisIndices?: boolean; + showXAxisIndices?: boolean; + yAxisIndicesHeight?: number; + xAxisIndicesHeight?: number; + yAxisIndicesWidth?: number; + xAxisIndicesWidth?: number; + xAxisIndicesColor?: ColorValue; + yAxisIndicesColor?: ColorValue; + yAxisSide?: yAxisSides; + yAxisOffset?: number; + + startIndex?: number; + endIndex?: number; + + color?: string; + colorNegative?: string; + yAxisThickness?: number; + yAxisColor?: ColorValue; + yAxisLabelContainerStyle?: any; + horizontalRulesStyle?: any; + yAxisTextStyle?: any; + yAxisTextNumberOfLines?: number; + xAxisTextNumberOfLines?: number; + showFractionalValues?: boolean; + roundToDigits?: number; + yAxisLabelWidth?: number; + hideYAxisText?: boolean; + + backgroundColor?: ColorValue; + curved?: boolean; + horizSections?: Array; + + //Data points + + hideDataPoints?: boolean; + dataPointsHeight?: number; + dataPointsWidth?: number; + dataPointsRadius?: number; + dataPointsColor?: string; + dataPointsShape?: string; + customDataPoint?: Function; + + focusedDataPointShape?: String; + focusedDataPointWidth?: number; + focusedDataPointHeight?: number; + focusedDataPointColor?: ColorValue | String | any; + focusedDataPointRadius?: number; + focusedCustomDataPoint?: Function; + dataPointLabelWidth?: number; + dataPointLabelShiftX?: number; + dataPointLabelShiftY?: number; + + startFillColor?: string; + endFillColor?: string; + startFillColorNegative?: string; + endFillColorNegative?: string; + startOpacity?: number; + endOpacity?: number; + startOpacityNegative?: number; + endOpacityNegative?: number; + gradientDirection?: string; + + textFontSize?: number; + textColor?: string; + hideOrigin?: boolean; + textShiftX?: number; + textShiftY?: number; + yAxisLabelTexts?: Array; + xAxisLabelTexts?: Array; + xAxisLabelTextStyle?: any; + width?: number; + yAxisLabelPrefix?: String; + yAxisLabelSuffix?: String; + scrollToEnd?: boolean; + scrollToIndex?: number; + scrollAnimation?: boolean; + noOfSectionsBelowXAxis?: number; + labelsExtraHeight?: number; + adjustToWidth?: boolean; + getPointerProps?: Function; +}; +type referenceConfigType = { + thickness: number; + width: number; + color: ColorValue | String | any; + type: String; + dashWidth: number; + dashGap: number; + labelText: String; + labelTextStyle: any; +}; +type itemType = { + value: number; + label: String; + labelComponent: Function; + labelTextStyle?: any; + dataPointText?: string; + textShiftX?: number; + textShiftY?: number; + textColor?: string; + textFontSize?: number; + + hideDataPoint?: boolean; + dataPointHeight?: number; + dataPointWidth?: number; + dataPointRadius?: number; + dataPointColor?: string; + dataPointShape?: string; + customDataPoint?: Function; + + stripHeight?: number; + stripWidth?: number; + stripColor?: ColorValue | String | any; + stripOpacity?: number; + + focusedDataPointShape?: String; + focusedDataPointWidth?: number; + focusedDataPointHeight?: number; + focusedDataPointColor?: ColorValue | String | any; + focusedDataPointRadius?: number; + focusedCustomDataPoint?: Function; + + dataPointLabelComponent?: Function; + focusedDataPointLabelComponent?: Function; + dataPointLabelWidth?: number; + dataPointLabelShiftX?: number; + dataPointLabelShiftY?: number; + showStrip?: boolean; + + showVerticalLine?: boolean; + verticalLineUptoDataPoint?: boolean; + verticalLineColor?: string; + verticalLineThickness?: number; + pointerShiftX?: number; + pointerShiftY?: number; + onPress?: Function; +}; + +type sectionType = { + value: string; +}; + +type Points = { + points: string; + color: string; +}; + +export const LineChartBicolor = (props: propTypes) => { + const scrollRef = useRef(); + const [toggle, setToggle] = useState(false); + const [pointsArray, setPointsArray] = useState>([]); + const [fillPointsArray, setFillPointsArray] = useState>([]); + const [selectedIndex, setSelectedIndex] = useState(-1); + const containerHeight = props.height || AxesAndRulesDefaults.containerHeight; + const noOfSections = props.noOfSections || AxesAndRulesDefaults.noOfSections; + const data = useMemo(() => { + if (!props.data) { + return []; + } + if (props.yAxisOffset) { + return props.data.map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data; + }, [props.yAxisOffset, props.data]); + + const scrollToEnd = props.scrollToEnd ?? LineDefaults.scrollToEnd; + const scrollAnimation = props.scrollAnimation ?? LineDefaults.scrollAnimation; + + const opacValue = useMemo(() => new Animated.Value(0), []); + const widthValue = useMemo(() => new Animated.Value(0), []); + const labelsExtraHeight = props.labelsExtraHeight || 0; + + const animationDuration = props.animationDuration || LineDefaults.animationDuration; + + const startIndex1 = props.startIndex || 0; + + let endIndex1; + if (props.endIndex === undefined || props.endIndex === null) { + endIndex1 = data.length - 1; + } else { + endIndex1 = props.endIndex; + } + + if (!initialData) { + initialData = [...data]; + } + + const adjustToWidth = props.adjustToWidth || false; + + const initialSpacing = props.initialSpacing ?? LineDefaults.initialSpacing; + const endSpacing = props.endSpacing ?? (adjustToWidth ? 0 : LineDefaults.endSpacing); + const thickness = props.thickness || LineDefaults.thickness; + + const spacing = + props.spacing ?? + (adjustToWidth + ? ((props.width || AxesAndRulesDefaults.width) - initialSpacing) / data.length + : LineDefaults.spacing); + + const xAxisThickness = props.xAxisThickness ?? AxesAndRulesDefaults.xAxisThickness; + const dataPointsHeight1 = props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth1 = props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius1 = props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor1 = props.dataPointsColor ?? LineDefaults.dataPointsColor; + const dataPointsShape1 = props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const labelsAppear = useCallback(() => { + opacValue.setValue(0); + Animated.timing(opacValue, { + toValue: 1, + duration: 500, + easing: Easing.ease, + useNativeDriver: false, + }).start(); + }, [opacValue]); + + const appearingOpacity = opacValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + }); + + const decreaseWidth = useCallback(() => { + widthValue.setValue(0); + Animated.timing(widthValue, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue]); + + const areaChart = props.areaChart || false; + const textFontSize1 = props.textFontSize || LineDefaults.textFontSize; + const textColor1 = props.textColor || LineDefaults.textColor; + + let totalWidth = initialSpacing; + let maxItem = 0, + minItem = 0; + data.forEach((item: itemType) => { + if (item.value > maxItem) { + maxItem = item.value; + } + if (item.value < minItem) { + minItem = item.value; + } + totalWidth += spacing; + }); + + if (props.showFractionalValues || props.roundToDigits) { + maxItem *= 10 * (props.roundToDigits || 1); + maxItem = maxItem + (10 - (maxItem % 10)); + maxItem /= 10 * (props.roundToDigits || 1); + maxItem = parseFloat(maxItem.toFixed(props.roundToDigits || 1)); + + if (minItem !== 0) { + minItem *= 10 * (props.roundToDigits || 1); + minItem = minItem - (10 + (minItem % 10)); + minItem /= 10 * (props.roundToDigits || 1); + minItem = parseFloat(minItem.toFixed(props.roundToDigits || 1)); + } + } else { + maxItem = maxItem + (10 - (maxItem % 10)); + if (minItem !== 0) { + minItem = minItem - (10 + (minItem % 10)); + } + } + + const maxValue = props.maxValue || maxItem; + const minValue = props.minValue || minItem; + + useEffect(() => { + decreaseWidth(); + labelsAppear(); + }, [animationDuration, decreaseWidth, labelsAppear]); + + const extendedContainerHeight = getExtendedContainerHeightWithPadding( + containerHeight, + props.overflowTop, + ); + + const yAtxAxis = extendedContainerHeight - xAxisThickness / 2; + const getX = (index) => initialSpacing + spacing * index; + const getY = (index) => yAtxAxis - (data[index].value * containerHeight) / maxValue; + + useEffect(() => { + const ppArray: Array = []; + let pp = 'M' + initialSpacing + ' ' + getY(0), + prevValuev, + nextValue; + for (let i = 0; i < data.length - 1; i++) { + prevValuev = data[i].value; + nextValue = data[i + 1].value; + + if (prevValuev < 0 && nextValue < 0) { + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + } else if (prevValuev < 0 && nextValue > 0) { + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + const prevX = getX(i); + const prevY = getY(i); + const nextX = getX(i + 1); + const nextY = getY(i + 1); + const slope = (nextY - prevY) / (nextX - prevX); + const x = (yAtxAxis - prevY) / slope + prevX; + pp += 'L' + (x - thickness / 2) + ' ' + yAtxAxis + ' '; + + let pointsOb = { + points: pp.startsWith('L') ? pp.replace('L', 'M') : pp, + color: 'red', + }; + ppArray.push(pointsOb); + setPointsArray([...ppArray]); + pp = 'M' + x + ' ' + yAtxAxis + ' L' + nextX + ' ' + nextY + ' '; + pointsOb = { + points: pp, + color: 'green', + }; + ppArray.push(pointsOb); + } else if (prevValuev > 0 && nextValue < 0) { + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + const prevX = getX(i); + const prevY = getY(i); + const nextX = getX(i + 1); + const nextY = getY(i + 1); + const slope = (nextY - prevY) / (nextX - prevX); + + const x = (yAtxAxis - prevY) / slope + prevX; + pp += 'L' + (x - thickness / 2) + ' ' + yAtxAxis + ' '; + + let pointsOb = { + points: pp.startsWith('L') ? pp.replace('L', 'M') : pp, + color: 'green', + }; + ppArray.push(pointsOb); + setPointsArray([...ppArray]); + pp = 'M' + x + ' ' + yAtxAxis + ' L' + nextX + ' ' + nextY + ' '; + pointsOb = { + points: pp, + color: 'red', + }; + ppArray.push(pointsOb); + } else { + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + } + } + let i = data.length - 1; + prevValuev = data[i - 1].value; + nextValue = data[i].value; + if ((prevValuev > 0 && nextValue > 0) || (prevValuev < 0 && nextValue < 0)) { + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + } + const pointsOb = { + points: pp.startsWith('L') ? pp.replace('L', 'M') : pp, + color: nextValue > 0 ? 'green' : 'red', + }; + ppArray.push(pointsOb); + setPointsArray([...ppArray]); + + /*************************** For Area Charts *************************/ + + let startIndex = -1, + endIndex = -1, + startX, + startY, + endY, + color = 'green', + broken = false; + + const localArray: Array = []; + pp = 'M' + initialSpacing + ' ' + yAtxAxis; + for (i = 0; i < data.length - 1; i++) { + prevValuev = data[i].value; + nextValue = data[i + 1].value; + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + if ((prevValuev > 0 && nextValue < 0) || (prevValuev < 0 && nextValue > 0)) { + const prevX = getX(i); + const prevY = getY(i); + const nextX = getX(i + 1); + const nextY = getY(i + 1); + const slope = (nextY - prevY) / (nextX - prevX); + + const x = (yAtxAxis - prevY) / slope + prevX; + pp += 'L' + (x - thickness / 2) + ' ' + yAtxAxis + ' '; + broken = true; + break; + } + } + if (!broken) { + i = data.length - 1; + pp += + 'L' + + getX(i) + + ' ' + + getY(i) + + ' L' + + getX(i) + + ' ' + + (yAtxAxis - xAxisThickness / 2); + } + localArray.push({ points: pp, color: data[0].value >= 0 ? 'green' : 'red' }); + + const xs: Array = []; + data.forEach((item, index) => { + const x = getX(index); + xs.push(x + ''); + }); + + pointsArray.forEach((item: any, index) => { + const splitArray = item.points.split(' ').filter((spItem) => spItem && spItem !== ' '); + + if ( + splitArray[1] === yAtxAxis + '' && + !xs.includes(splitArray[0].replace('M', '').replace('L', '')) + ) { + startIndex = index; + startX = splitArray[0].replace('M', '').replace('L', ''); + if (splitArray.length > 3) { + startY = splitArray[1].replace('M', '').replace('L', ''); + endY = splitArray[3].replace('M', '').replace('L', ''); + if (Number(startY) < Number(endY)) { + color = 'red'; + } else { + color = 'green'; + } + } + } + if ( + splitArray[splitArray.length - 1] === yAtxAxis + '' && + !xs.includes(splitArray[splitArray.length - 2].replace('M', '').replace('L', '')) + ) { + endIndex = index; + } + if (startX) { + let filPts = ''; + for (let j = startIndex; j <= endIndex; j++) { + if (pointsArray[j]) { + filPts += pointsArray[j].points.replaceAll('M', 'L'); + } + } + filPts += 'L ' + startX + ' ' + yAtxAxis; + localArray.push({ points: filPts.replace('L', 'M'), color }); + } + }); + if (broken) { + pp = 'M' + getX(data.length - 1) + ' ' + yAtxAxis; + for (let i = data.length - 1; i > 0; i--) { + prevValuev = data[i].value; + nextValue = data[i - 1].value; + pp += 'L' + getX(i) + ' ' + getY(i) + ' '; + if ((prevValuev > 0 && nextValue < 0) || (prevValuev < 0 && nextValue > 0)) { + const prevX = getX(i); + const prevY = getY(i); + const nextX = getX(i - 1); + const nextY = getY(i - 1); + const slope = (nextY - prevY) / (nextX - prevX); + + const x = (yAtxAxis - prevY) / slope + prevX; + pp += 'L' + x + ' ' + yAtxAxis + ' '; + break; + } + } + + localArray.push({ + points: pp, + color: data[data.length - 1].value > 0 ? 'green' : 'red', + }); + } + + setFillPointsArray([...localArray]); + setToggle(true); + }, [ + areaChart, + containerHeight, + data, + dataPointsWidth1, + initialSpacing, + spacing, + xAxisThickness, + toggle, + maxValue, + getY, + yAtxAxis, + pointsArray, + getX, + thickness, + ]); + + const horizSections = [{ value: '0' }]; + const horizSectionsBelow: HorizSectionsType = []; + const stepHeight = props.stepHeight || containerHeight / noOfSections; + const stepValue = props.stepValue || maxValue / noOfSections; + const noOfSectionsBelowXAxis = props.noOfSectionsBelowXAxis || -minValue / stepValue; + const thickness1 = props.thickness || LineDefaults.thickness; + const zIndex = props.zIndex || 0; + + const strokeDashArray1 = props.strokeDashArray; + + const rotateLabel = props.rotateLabel ?? AxesAndRulesDefaults.rotateLabel; + const isAnimated = props.isAnimated ?? LineDefaults.isAnimated; + const hideDataPoints1 = props.hideDataPoints ?? LineDefaults.hideDataPoints; + + const color = props.color || 'green'; + const colorNegative = props.colorNegative || 'red'; + + const startFillColor = props.startFillColor || 'lightgreen'; + const endFillColor = props.endFillColor || 'white'; + const startOpacity = props.startOpacity ?? LineDefaults.startOpacity; + const endOpacity = props.endOpacity ?? LineDefaults.endOpacity; + const startFillColorNegative = props.startFillColorNegative || 'pink'; + const endFillColorNegative = props.endFillColorNegative || 'white'; + const startOpacityNegative = props.startOpacityNegative ?? LineDefaults.startOpacity; + const endOpacityNegative = props.endOpacityNegative ?? LineDefaults.endOpacity; + + const gradientDirection = props.gradientDirection || 'vertical'; + + const showXAxisIndices = props.showXAxisIndices ?? AxesAndRulesDefaults.showXAxisIndices; + const xAxisIndicesHeight = props.xAxisIndicesHeight ?? AxesAndRulesDefaults.xAxisIndicesHeight; + const xAxisIndicesWidth = props.xAxisIndicesWidth ?? AxesAndRulesDefaults.xAxisIndicesWidth; + const xAxisIndicesColor = props.xAxisIndicesColor ?? AxesAndRulesDefaults.xAxisIndicesColor; + + const xAxisTextNumberOfLines = + props.xAxisTextNumberOfLines ?? AxesAndRulesDefaults.xAxisTextNumberOfLines; + const horizontalRulesStyle = props.horizontalRulesStyle; + const showFractionalValues = + props.showFractionalValues ?? AxesAndRulesDefaults.showFractionalValues; + const yAxisLabelWidth = + props.yAxisLabelWidth ?? + (props.hideYAxisText + ? AxesAndRulesDefaults.yAxisEmptyLabelWidth + : AxesAndRulesDefaults.yAxisLabelWidth); + + const horizontal = false; + const yAxisAtTop = false; + + const disableScroll = props.disableScroll ?? LineDefaults.disableScroll; + const showScrollIndicator = props.showScrollIndicator || LineDefaults.showScrollIndicator; + + const focusEnabled = props.focusEnabled ?? LineDefaults.focusEnabled; + const showDataPointOnFocus = props.showDataPointOnFocus ?? LineDefaults.showDataPointOnFocus; + const showStripOnFocus = props.showStripOnFocus ?? LineDefaults.showStripOnFocus; + const showTextOnFocus = props.showTextOnFocus ?? LineDefaults.showTextOnFocus; + const stripHeight = props.stripHeight; + const stripWidth = props.stripWidth ?? LineDefaults.stripWidth; + const stripColor = props.stripColor ?? color; + const stripOpacity = props.stripOpacity ?? (startOpacity + endOpacity) / 2; + const unFocusOnPressOut = props.unFocusOnPressOut ?? LineDefaults.unFocusOnPressOut; + const delayBeforeUnFocus = props.delayBeforeUnFocus ?? LineDefaults.delayBeforeUnFocus; + + horizSections.pop(); + for (let i = 0; i <= noOfSections; i++) { + let value = maxValue - stepValue * i; + if (props.showFractionalValues || props.roundToDigits) { + value = parseFloat(value.toFixed(props.roundToDigits || 1)); + } + horizSections.push({ + value: props.yAxisLabelTexts + ? props.yAxisLabelTexts[noOfSections - i] ?? value.toString() + : value.toString(), + }); + } + if (noOfSectionsBelowXAxis) { + for (let i = 1; i <= noOfSectionsBelowXAxis; i++) { + let value = stepValue * -i; + if (props.showFractionalValues || props.roundToDigits) { + value = parseFloat(value.toFixed(props.roundToDigits || 1)); + } + horizSectionsBelow.push({ + value: props.yAxisLabelTexts + ? props.yAxisLabelTexts[noOfSectionsBelowXAxis - i] ?? value.toString() + : value.toString(), + }); + } + } + + const renderLabel = ( + index: number, + label: String, + labelTextStyle: any, + labelComponent: Function, + ) => { + return ( + + {labelComponent ? ( + labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const renderAnimatedLabel = ( + index: number, + label: String, + labelTextStyle: any, + labelComponent: Function, + ) => { + return ( + + {labelComponent ? ( + labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const animatedWidth = widthValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const onStripPress = (item, index) => { + setSelectedIndex(index); + if (props.onFocus) { + props.onFocus(item, index); + } + }; + + const renderDataPoints = ( + dataForRender, + dataPtsShape, + dataPtsWidth, + dataPtsHeight, + dataPtsColor, + dataPtsRadius, + textColor, + textFontSize, + startIndex, + endIndex, + ) => { + return dataForRender.map((item: itemType, index: number) => { + if (index < startIndex || index > endIndex) return null; + if (item.hideDataPoint) { + return null; + } + let dataPointsShape, + dataPointsWidth, + dataPointsHeight, + dataPointsColor, + dataPointsRadius, + text, + customDataPoint, + dataPointLabelComponent; + if (index === selectedIndex) { + dataPointsShape = + item.focusedDataPointShape || + props.focusedDataPointShape || + item.dataPointShape || + dataPtsShape; + dataPointsWidth = + item.focusedDataPointWidth || + props.focusedDataPointWidth || + item.dataPointWidth || + dataPtsWidth; + dataPointsHeight = + item.focusedDataPointHeight || + props.focusedDataPointHeight || + item.dataPointHeight || + dataPtsHeight; + dataPointsColor = + item.focusedDataPointColor || props.focusedDataPointColor || 'orange'; + dataPointsRadius = + item.focusedDataPointRadius || + props.focusedDataPointRadius || + item.dataPointRadius || + dataPtsRadius; + if (showTextOnFocus) { + text = item.dataPointText; + } + customDataPoint = + item.focusedCustomDataPoint || + props.focusedCustomDataPoint || + item.customDataPoint || + props.customDataPoint; + dataPointLabelComponent = + item.focusedDataPointLabelComponent || item.dataPointLabelComponent; + } else { + dataPointsShape = item.dataPointShape || dataPtsShape; + dataPointsWidth = item.dataPointWidth || dataPtsWidth; + dataPointsHeight = item.dataPointHeight || dataPtsHeight; + dataPointsColor = item.dataPointColor || dataPtsColor; + dataPointsRadius = item.dataPointRadius || dataPtsRadius; + if (showTextOnFocus) { + text = ''; + } + customDataPoint = item.customDataPoint || props.customDataPoint; + dataPointLabelComponent = item.dataPointLabelComponent; + } + + const currentStripHeight = item.stripHeight ?? stripHeight; + const currentStripWidth = item.stripWidth ?? stripWidth; + const currentStripOpacity = item.stripOpacity ?? stripOpacity; + const currentStripColor = item.stripColor || stripColor; + + return ( + + {focusEnabled ? ( + <> + {unFocusOnPressOut ? ( + onStripPress(item, index)} + onPressOut={() => + setTimeout(() => setSelectedIndex(-1), delayBeforeUnFocus) + } + x={initialSpacing + (spacing * index - spacing / 2)} + y={8} + width={spacing} + height={containerHeight} + fill={'none'} + /> + ) : ( + onStripPress(item, index)} + x={initialSpacing + (spacing * index - spacing / 2)} + y={8} + width={spacing} + height={containerHeight} + fill={'none'} + /> + )} + + ) : null} + {item.showStrip || + (focusEnabled && index === selectedIndex && showStripOnFocus) ? ( + + ) : null} + {customDataPoint ? ( + + {customDataPoint()} + + ) : null} + {dataPointsShape === 'rectangular' ? ( + + {customDataPoint ? null : ( + { + item.onPress + ? item.onPress(item, index) + : props.onPress + ? props.onPress(item, index) + : null; + }} + /> + )} + + ) : ( + + {customDataPoint ? null : ( + { + item.onPress + ? item.onPress(item, index) + : props.onPress + ? props.onPress(item, index) + : null; + }} + /> + )} + + )} + {dataPointLabelComponent ? ( + !showTextOnFocus || index === selectedIndex ? ( + + {dataPointLabelComponent()} + + ) : null + ) : text || item.dataPointText ? ( + !showTextOnFocus || index === selectedIndex ? ( + + {!showTextOnFocus ? item.dataPointText : text} + + ) : null + ) : null} + + ); + }); + }; + + const renderSpecificVerticalLines = (dataForRender: any) => { + return dataForRender.map((item: itemType, index: number) => { + if (item.showVerticalLine) { + return ( + + ); + } + return null; + }); + }; + + const lineSvgComponent = ( + pointsArray: any, + currentLineThickness: number | undefined, + color: string, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + ) => { + return ( + + {strokeDashArray && + strokeDashArray.length === 2 && + typeof strokeDashArray[0] === 'number' && + typeof strokeDashArray[1] === 'number' + ? pointsArray.map((points, index) => ( + + )) + : pointsArray.map((points, index) => { + return ( + + ); + })} + + {/*********************** For Area Chart ************/} + + {areaChart && ( + <> + + + + + + + + + + )} + {areaChart + ? fillPointsArray.map((item, index) => { + return ( + + ); + }) + : null} + + {/******************************************************************/} + + {renderSpecificVerticalLines(data)} + + {/*** !!! Here it's done thrice intentionally, trying to make it to only 1 breaks things !!! ***/} + {!hideDataPoints1 + ? renderDataPoints( + data, + dataPointsShape1, + dataPointsWidth1, + dataPointsHeight1, + dataPointsColor1, + dataPointsRadius1, + textColor1, + textFontSize1, + startIndex1, + endIndex1, + ) + : null} + + ); + }; + + const renderLine = ( + zIndex: number, + pointsArray: any, + currentLineThickness: number | undefined, + color: string, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + ) => { + return ( + + {pointsArray.length + ? lineSvgComponent( + pointsArray, + currentLineThickness, + color, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray, + ) + : null} + + ); + }; + + const renderAnimatedLine = ( + zIndex: number, + points: any, + animatedWidth: any, + currentLineThickness: number | undefined, + color: string, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + ) => { + return ( + + {lineSvgComponent( + points, + currentLineThickness, + color, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray, + )} + + ); + }; + + const renderChartContent = () => { + return ( + <> + {isAnimated + ? renderAnimatedLine( + zIndex, + pointsArray, + animatedWidth, + thickness1, + color, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray1, + ) + : renderLine( + zIndex, + pointsArray, + thickness1, + color, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray1, + )} + {data.map((item: itemType, index: number) => { + return ( + + {isAnimated + ? renderAnimatedLabel( + index, + item.label || + (props.xAxisLabelTexts && props.xAxisLabelTexts[index] + ? props.xAxisLabelTexts[index] + : ''), + item.labelTextStyle || props.xAxisLabelTextStyle, + item.labelComponent, + ) + : renderLabel( + index, + item.label || + (props.xAxisLabelTexts && props.xAxisLabelTexts[index] + ? props.xAxisLabelTexts[index] + : ''), + item.labelTextStyle || props.xAxisLabelTextStyle, + item.labelComponent, + )} + {/* {renderLabel(index, item.label, item.labelTextStyle)} */} + + ); + })} + + ); + }; + + const barAndLineChartsWrapperProps: BarAndLineChartsWrapperTypes = { + chartType: chartTypes.LINE_BI_COLOR, + containerHeight, + horizSectionsBelow, + stepHeight, + labelsExtraHeight, + yAxisLabelWidth, + horizontal, + rtl: false, + shiftX: 0, + shiftY: 0, + scrollRef, + yAxisAtTop, + initialSpacing, + data, + stackData: undefined, // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + secondaryData: [], + barWidth: 0, // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + xAxisThickness, + totalWidth, + disableScroll, + showScrollIndicator, + scrollToEnd, + scrollToIndex: props.scrollToIndex, + scrollAnimation, + indicatorColor: props.indicatorColor, + setSelectedIndex, + spacing, + showLine: false, + lineConfig: null, + maxValue, + lineData: [], // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + animatedWidth, + lineBehindBars: false, + points: pointsArray, + arrowPoints: [], // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + renderChartContent, + remainingScrollViewProps: {}, + + //horizSectionProps- + width: props.width, + horizSections, + endSpacing, + horizontalRulesStyle, + noOfSections, + showFractionalValues, + + axesAndRulesProps: getAxesAndRulesProps(props, stepValue), + + yAxisLabelTexts: props.yAxisLabelTexts, + yAxisOffset: props.yAxisOffset, + rotateYAxisTexts: 0, + hideAxesAndRules: props.hideAxesAndRules, + + showXAxisIndices, + xAxisIndicesHeight, + xAxisIndicesWidth, + xAxisIndicesColor, + + // These are Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + pointerConfig: null, + getPointerProps: null, + pointerIndex: 0, + pointerX: 0, + pointerY: 0, + }; + + return ; +}; diff --git a/src/components/charts/LineChart/index.tsx b/src/components/charts/LineChart/index.tsx new file mode 100644 index 00000000..2e3c62ac --- /dev/null +++ b/src/components/charts/LineChart/index.tsx @@ -0,0 +1,2921 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { View, Animated, Easing, Text, Dimensions, Platform } from 'react-native'; +import { styles } from './styles'; +import Svg, { + Path, + LinearGradient, + Stop, + Circle, + Rect, + Text as CanvasText, + Line, +} from 'react-native-svg'; +import { + svgPath, + getArrowPoints, + getAxesAndRulesProps, + getExtendedContainerHeightWithPadding, + getSecondaryDataWithOffsetIncluded, + getAllArrowProperties, + computeMaxAndMinItems, +} from '../utils'; +import { + AxesAndRulesDefaults, + LineDefaults, + chartTypes, + defaultArrowConfig, + defaultPointerConfig, +} from '../utils/constants'; +import BarAndLineChartsWrapper from '../Components/BarAndLineChartsWrapper'; +import { LineChartPropsType, itemType } from './types'; +import { BarAndLineChartsWrapperTypes, HorizSectionsType } from '../utils/types'; + +let initialData: Array | null = null; +let animations: Array = []; + +export const LineChart = (props: LineChartPropsType) => { + const scrollRef = props.scrollRef ?? useRef(null); + const curvature = props.curvature ?? LineDefaults.curvature; + const curveType = props.curveType ?? LineDefaults.curveType; + const [scrollX, setScrollX] = useState(0); + const [arrow1Points, setArrow1Points] = useState(''); + const [arrow2Points, setArrow2Points] = useState(''); + const [arrow3Points, setArrow3Points] = useState(''); + const [arrow4Points, setArrow4Points] = useState(''); + const [arrow5Points, setArrow5Points] = useState(''); + const [secondaryArrowPoints, setSecondaryArrowPoints] = useState(''); + const [pointerIndex, setPointerIndex] = useState(-1); + const [pointerX, setPointerX] = useState(0); + const [pointerY, setPointerY] = useState(0); + const [pointerItem, setPointerItem] = useState({ + pointerShiftX: 0, + pointerShiftY: 0, + }); + const [pointerY2, setPointerY2] = useState(0); + const [pointerItem2, setPointerItem2] = useState({ + pointerShiftX: 0, + pointerShiftY: 0, + }); + const [pointerY3, setPointerY3] = useState(0); + const [pointerItem3, setPointerItem3] = useState({ + pointerShiftX: 0, + pointerShiftY: 0, + }); + const [pointerY4, setPointerY4] = useState(0); + const [pointerItem4, setPointerItem4] = useState({ + pointerShiftX: 0, + pointerShiftY: 0, + }); + const [pointerY5, setPointerY5] = useState(0); + const [pointerItem5, setPointerItem5] = useState({ + pointerShiftX: 0, + pointerShiftY: 0, + }); + const [responderStartTime, setResponderStartTime] = useState(0); + const [responderActive, setResponderActive] = useState(false); + const [points, setPoints] = useState(''); + const [points2, setPoints2] = useState(''); + const [points3, setPoints3] = useState(''); + const [points4, setPoints4] = useState(''); + const [points5, setPoints5] = useState(''); + const [secondaryPoints, setSecondaryPoints] = useState(''); + const [fillPoints, setFillPoints] = useState(''); + const [fillPoints2, setFillPoints2] = useState(''); + const [fillPoints3, setFillPoints3] = useState(''); + const [fillPoints4, setFillPoints4] = useState(''); + const [fillPoints5, setFillPoints5] = useState(''); + const [secondaryFillPoints, setSecondaryFillPoints] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(-1); + const noOfSections = props.noOfSections || 10; + const containerHeight = + props.height ?? + ((props.stepHeight ?? 0) * noOfSections || AxesAndRulesDefaults.containerHeight); + const data = useMemo(() => { + if (!props.data) { + return []; + } + if (props.yAxisOffset) { + return JSON.parse(JSON.stringify(props.data)).map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data; + }, [props.yAxisOffset, props.data]); + const data2 = useMemo(() => { + if (!props.data2) { + return []; + } + if (props.yAxisOffset) { + return JSON.parse(JSON.stringify(props.data2)).map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data2; + }, [props.yAxisOffset, props.data2]); + const data3 = useMemo(() => { + if (!props.data3) { + return []; + } + if (props.yAxisOffset) { + return JSON.parse(JSON.stringify(props.data3)).map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data3; + }, [props.yAxisOffset, props.data3]); + const data4 = useMemo(() => { + if (!props.data4) { + return []; + } + if (props.yAxisOffset) { + return JSON.parse(JSON.stringify(props.data4)).map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data4; + }, [props.yAxisOffset, props.data4]); + const data5 = useMemo(() => { + if (!props.data5) { + return []; + } + if (props.yAxisOffset) { + return JSON.parse(JSON.stringify(props.data5)).map((item) => { + item.value = item.value - (props.yAxisOffset ?? 0); + return item; + }); + } + return props.data5; + }, [props.yAxisOffset, props.data5]); + + const secondaryData = + getSecondaryDataWithOffsetIncluded(props.secondaryData, props.secondaryYAxis) || []; + + const scrollToEnd = props.scrollToEnd || LineDefaults.scrollToEnd; + const scrollAnimation = props.scrollAnimation ?? LineDefaults.scrollAnimation; + + const opacValue = useMemo(() => new Animated.Value(0), []); + const widthValue = useMemo(() => new Animated.Value(0), []); + const widthValue2 = useMemo(() => new Animated.Value(0), []); + const widthValue3 = useMemo(() => new Animated.Value(0), []); + const widthValue4 = useMemo(() => new Animated.Value(0), []); + const widthValue5 = useMemo(() => new Animated.Value(0), []); + const labelsExtraHeight = props.labelsExtraHeight || 0; + + const animationDuration = props.animationDuration || LineDefaults.animationDuration; + const onDataChangeAnimationDuration = props.onDataChangeAnimationDuration || 400; + const animateTogether = props.animateTogether || LineDefaults.animateTogether; + const animateOnDataChange = props.yAxisOffset ? false : props.animateOnDataChange || false; + + const startIndex1 = props.startIndex1 ?? props.startIndex ?? 0; + + let endIndex1; + if (props.endIndex1 === undefined || props.endIndex1 === null) { + if (props.endIndex === undefined || props.endIndex === null) { + endIndex1 = data.length - 1; + } else { + endIndex1 = props.endIndex; + } + } else { + endIndex1 = props.endIndex1; + } + + const startIndex2 = props.startIndex2 || 0; + const endIndex2 = props.endIndex2 ?? data2.length - 1; + + const startIndex3 = props.startIndex3 || 0; + const endIndex3 = props.endIndex3 ?? data3.length - 1; + const startIndex4 = props.startIndex4 || 0; + const endIndex4 = props.endIndex4 ?? data4.length - 1; + const startIndex5 = props.startIndex5 || 0; + const endIndex5 = props.endIndex5 ?? data5.length - 1; + + if (!initialData) { + initialData = [...data]; + animations = initialData.map((item) => new Animated.Value(item.value)); + } + + let newPoints = '', + newFillPoints = ''; + let counter = 0; + + const adjustToWidth = props.adjustToWidth || false; + + const initialSpacing = props.initialSpacing ?? LineDefaults.initialSpacing; + const endSpacing = props.endSpacing ?? (adjustToWidth ? 0 : LineDefaults.endSpacing); + + const thickness = props.thickness || LineDefaults.thickness; + + const spacing = + props.spacing ?? + (adjustToWidth + ? ((props.width ?? AxesAndRulesDefaults.width) - initialSpacing) / (data.length - 1) + : LineDefaults.spacing); + + const xAxisThickness = props.xAxisThickness ?? AxesAndRulesDefaults.xAxisThickness; + const dataPointsHeight1 = + props.dataPointsHeight1 ?? props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth1 = + props.dataPointsWidth1 ?? props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius1 = + props.dataPointsRadius1 ?? props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor1 = + props.dataPointsColor1 ?? props.dataPointsColor ?? LineDefaults.dataPointsColor; + const dataPointsShape1 = + props.dataPointsShape1 ?? props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const dataPointsHeight2 = + props.dataPointsHeight2 ?? props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth2 = + props.dataPointsWidth2 ?? props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius2 = + props.dataPointsRadius2 ?? props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor2 = + props.dataPointsColor2 ?? props.dataPointsColor ?? LineDefaults.dataPointsColor2; + const dataPointsShape2 = + props.dataPointsShape2 ?? props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const dataPointsHeight3 = + props.dataPointsHeight3 ?? props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth3 = + props.dataPointsWidth3 ?? props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius3 = + props.dataPointsRadius3 ?? props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor3 = + props.dataPointsColor3 ?? props.dataPointsColor ?? LineDefaults.dataPointsColor3; + const dataPointsShape3 = + props.dataPointsShape3 ?? props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const dataPointsHeight4 = + props.dataPointsHeight4 ?? props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth4 = + props.dataPointsWidth4 ?? props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius4 = + props.dataPointsRadius4 ?? props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor4 = + props.dataPointsColor4 ?? props.dataPointsColor ?? LineDefaults.dataPointsColor; + const dataPointsShape4 = + props.dataPointsShape4 ?? props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const dataPointsHeight5 = + props.dataPointsHeight5 ?? props.dataPointsHeight ?? LineDefaults.dataPointsHeight; + const dataPointsWidth5 = + props.dataPointsWidth5 ?? props.dataPointsWidth ?? LineDefaults.dataPointsWidth; + const dataPointsRadius5 = + props.dataPointsRadius5 ?? props.dataPointsRadius ?? LineDefaults.dataPointsRadius; + const dataPointsColor5 = + props.dataPointsColor5 ?? props.dataPointsColor ?? LineDefaults.dataPointsColor; + const dataPointsShape5 = + props.dataPointsShape5 ?? props.dataPointsShape ?? LineDefaults.dataPointsShape; + + const areaChart = props.areaChart ?? false; + const areaChart1 = props.areaChart1 ?? false; + const areaChart2 = props.areaChart2 ?? false; + const areaChart3 = props.areaChart3 ?? false; + const areaChart4 = props.areaChart4 ?? false; + const areaChart5 = props.areaChart5 ?? false; + + const atLeastOneAreaChart = + areaChart || areaChart1 || areaChart2 || areaChart3 || areaChart4 || areaChart5; + + const textFontSize1 = props.textFontSize1 ?? props.textFontSize ?? LineDefaults.textFontSize; + const textFontSize2 = props.textFontSize2 ?? props.textFontSize ?? LineDefaults.textFontSize; + const textFontSize3 = props.textFontSize3 ?? props.textFontSize ?? LineDefaults.textFontSize; + const textFontSize4 = props.textFontSize4 ?? props.textFontSize ?? LineDefaults.textFontSize; + const textFontSize5 = props.textFontSize5 ?? props.textFontSize ?? LineDefaults.textFontSize; + const textColor1 = props.textColor1 ?? props.textColor ?? LineDefaults.textColor; + const textColor2 = props.textColor2 ?? props.textColor ?? LineDefaults.textColor; + const textColor3 = props.textColor3 ?? props.textColor ?? LineDefaults.textColor; + const textColor4 = props.textColor4 ?? props.textColor ?? LineDefaults.textColor; + const textColor5 = props.textColor5 ?? props.textColor ?? LineDefaults.textColor; + + const totalWidth = initialSpacing + spacing * data.length; + + const { maxItem, minItem } = computeMaxAndMinItems( + data, + props.roundToDigits, + props.showFractionalValues, + ); + + const maxValue = props.maxValue || maxItem; + const minValue = props.minValue || minItem; + + const extendedContainerHeight = getExtendedContainerHeightWithPadding( + containerHeight, + props.overflowTop, + ); + const getX = (index) => initialSpacing + spacing * index - 1; + const getY = (value) => extendedContainerHeight - (value * containerHeight) / maxValue; + + const { maxItem: secondaryMaxItem } = computeMaxAndMinItems( + secondaryData, + props.secondaryYAxis?.roundToDigits, + props.secondaryYAxis?.showFractionalValues, + ); + const secondaryMaxValue = props.secondaryYAxis?.maxValue ?? (secondaryMaxItem || maxValue); + const getSecondaryY = (value) => + extendedContainerHeight - (value * containerHeight) / secondaryMaxValue; + const heightUptoXaxis = extendedContainerHeight - xAxisThickness; + + if (animateOnDataChange) { + animations.forEach((item, index) => { + item.addListener((val) => { + data[index].value = val.value; + let pp = '', + ppp = ''; + if (!props.curved) { + for (let i = 0; i < data.length; i++) { + pp += 'L' + getX(i) + ' ' + getY(data[i].value) + ' '; + } + if (areaChart) { + ppp = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp += pp; + ppp += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + } + newPoints = pp; + newFillPoints = ppp; + setPointsOnChange(); + } + counter++; + }); + }); + } + + const setPointsOnChange = () => { + if (counter === data.length) { + if (!props.curved) { + setPoints(newPoints.replace('L', 'M')); + if (areaChart) { + setFillPoints(newFillPoints.replace('L', 'M')); + } + } + } + }; + + useEffect(() => { + if (animateOnDataChange) { + Animated.parallel( + animations.map((anItem, index) => + Animated.timing(anItem, { + toValue: data[index].value, + useNativeDriver: Platform.OS === 'ios', // if useNativeDriver is set to true, animateOnDataChange feature fails for Android, so setting it true only for iOS + duration: onDataChangeAnimationDuration, + }), + ), + ).start(); + } + }, [animateOnDataChange, data, onDataChangeAnimationDuration]); + + const labelsAppear = useCallback(() => { + opacValue.setValue(0); + Animated.timing(opacValue, { + toValue: 1, + duration: 500, + easing: Easing.ease, + useNativeDriver: false, + }).start(); + }, [opacValue]); + + const appearingOpacity = opacValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + }); + + const decreaseWidth = useCallback(() => { + widthValue.setValue(0); + Animated.timing(widthValue, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue]); + + const decreaseWidth2 = useCallback(() => { + widthValue2.setValue(0); + Animated.timing(widthValue2, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue2]); + + const decreaseWidth3 = useCallback(() => { + widthValue3.setValue(0); + Animated.timing(widthValue3, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue3]); + + const decreaseWidth4 = useCallback(() => { + widthValue4.setValue(0); + Animated.timing(widthValue4, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue4]); + + const decreaseWidth5 = useCallback(() => { + widthValue5.setValue(0); + Animated.timing(widthValue5, { + toValue: 1, + duration: animationDuration, + easing: Easing.linear, + useNativeDriver: false, + }).start(); + }, [animationDuration, widthValue5]); + + useEffect(() => { + decreaseWidth(); + labelsAppear(); + setTimeout( + () => { + decreaseWidth2(); + }, + animateTogether ? 0 : animationDuration, + ); + setTimeout( + () => { + decreaseWidth3(); + }, + animateTogether ? 0 : animationDuration * 2, + ); + setTimeout( + () => { + decreaseWidth4(); + }, + animateTogether ? 0 : animationDuration * 3, + ); + setTimeout( + () => { + decreaseWidth5(); + }, + animateTogether ? 0 : animationDuration * 4, + ); + }, [ + animateTogether, + animationDuration, + decreaseWidth, + decreaseWidth2, + decreaseWidth3, + decreaseWidth4, + decreaseWidth5, + labelsAppear, + ]); + + const showValuesAsDataPointsText = + props.showValuesAsDataPointsText ?? LineDefaults.showValuesAsDataPointsText; + + const thickness1 = props.thickness1 ?? props.thickness ?? LineDefaults.thickness; + const thickness2 = props.thickness2 ?? props.thickness ?? LineDefaults.thickness; + const thickness3 = props.thickness3 ?? props.thickness ?? LineDefaults.thickness; + const thickness4 = props.thickness4 ?? props.thickness ?? LineDefaults.thickness; + const thickness5 = props.thickness5 ?? props.thickness ?? LineDefaults.thickness; + + const zIndex1 = props.zIndex1 ?? 0; + const zIndex2 = props.zIndex2 ?? 0; + const zIndex3 = props.zIndex3 ?? 0; + const zIndex4 = props.zIndex4 ?? 0; + const zIndex5 = props.zIndex5 ?? 0; + + const strokeDashArray1 = props.strokeDashArray1 ?? props.strokeDashArray; + const strokeDashArray2 = props.strokeDashArray2 ?? props.strokeDashArray; + const strokeDashArray3 = props.strokeDashArray3 ?? props.strokeDashArray; + const strokeDashArray4 = props.strokeDashArray4 ?? props.strokeDashArray; + const strokeDashArray5 = props.strokeDashArray5 ?? props.strokeDashArray; + + const rotateLabel = props.rotateLabel ?? false; + const isAnimated = props.isAnimated ?? false; + const hideDataPoints1 = props.hideDataPoints ?? props.hideDataPoints1 ?? false; + const hideDataPoints2 = props.hideDataPoints ?? props.hideDataPoints2 ?? false; + const hideDataPoints3 = props.hideDataPoints ?? props.hideDataPoints3 ?? false; + const hideDataPoints4 = props.hideDataPoints ?? props.hideDataPoints4 ?? false; + const hideDataPoints5 = props.hideDataPoints ?? props.hideDataPoints5 ?? false; + + const color1 = props.color1 ?? props.color ?? LineDefaults.color; + const color2 = props.color2 ?? props.color ?? LineDefaults.color; + const color3 = props.color3 ?? props.color ?? LineDefaults.color; + const color4 = props.color4 ?? props.color ?? LineDefaults.color; + const color5 = props.color5 ?? props.color ?? LineDefaults.color; + + const startFillColor1 = + props.startFillColor1 ?? props.startFillColor ?? LineDefaults.startFillColor; + const endFillColor1 = props.endFillColor1 ?? props.endFillColor ?? LineDefaults.endFillColor; + const startOpacity = props.startOpacity ?? LineDefaults.startOpacity; + const endOpacity = props.endOpacity ?? LineDefaults.endOpacity; + const startOpacity1 = props.startOpacity1 ?? startOpacity; + const endOpacity1 = props.endOpacity1 ?? endOpacity; + + const startFillColor2 = + props.startFillColor2 ?? props.startFillColor ?? LineDefaults.startFillColor; + const endFillColor2 = props.endFillColor2 ?? props.endFillColor ?? LineDefaults.endFillColor; + const startOpacity2 = props.startOpacity2 ?? startOpacity; + const endOpacity2 = props.endOpacity2 ?? endOpacity; + + const startFillColor3 = + props.startFillColor3 ?? props.startFillColor ?? LineDefaults.startFillColor; + const endFillColor3 = props.endFillColor3 ?? props.endFillColor ?? LineDefaults.endFillColor; + const startOpacity3 = props.startOpacity3 ?? startOpacity; + const endOpacity3 = props.endOpacity3 ?? endOpacity; + + const startFillColor4 = + props.startFillColor4 ?? props.startFillColor ?? LineDefaults.startFillColor; + const endFillColor4 = props.endFillColor4 ?? props.endFillColor ?? LineDefaults.endFillColor; + const startOpacity4 = props.startOpacity4 ?? startOpacity; + const endOpacity4 = props.endOpacity4 ?? endOpacity; + + const startFillColor5 = + props.startFillColor5 ?? props.startFillColor ?? LineDefaults.startFillColor; + const endFillColor5 = props.endFillColor5 ?? props.endFillColor ?? LineDefaults.endFillColor; + const startOpacity5 = props.startOpacity5 ?? startOpacity; + const endOpacity5 = props.endOpacity5 ?? endOpacity; + + defaultArrowConfig.strokeWidth = thickness1; + defaultArrowConfig.strokeColor = color1; + + const { + arrowLength1, + arrowWidth1, + arrowStrokeWidth1, + arrowStrokeColor1, + arrowFillColor1, + showArrowBase1, + arrowLength2, + arrowWidth2, + arrowStrokeWidth2, + arrowStrokeColor2, + arrowFillColor2, + showArrowBase2, + arrowLength3, + arrowWidth3, + arrowStrokeWidth3, + arrowStrokeColor3, + arrowFillColor3, + showArrowBase3, + arrowLength4, + arrowWidth4, + arrowStrokeWidth4, + arrowStrokeColor4, + arrowFillColor4, + showArrowBase4, + arrowLength5, + arrowWidth5, + arrowStrokeWidth5, + arrowStrokeColor5, + arrowFillColor5, + showArrowBase5, + } = getAllArrowProperties(props, defaultArrowConfig); + + const secondaryLineConfig = { + zIndex: props.secondaryLineConfig?.zIndex ?? zIndex1, + curved: props.secondaryLineConfig?.curved ?? props.curved, + curvature: props.secondaryLineConfig?.curvature ?? curvature, + curveType: props.secondaryLineConfig?.curveType ?? curveType, + areaChart: props.secondaryLineConfig?.areaChart ?? areaChart, + color: props.secondaryLineConfig?.color ?? color1, + thickness: props.secondaryLineConfig?.thickness ?? thickness1, + zIndex1: props.secondaryLineConfig?.zIndex1 ?? zIndex1, + strokeDashArray: props.secondaryLineConfig?.strokeDashArray ?? strokeDashArray1, + startIndex: props.secondaryLineConfig?.startIndex ?? startIndex1, + endIndex: props.secondaryLineConfig?.endIndex ?? endIndex1, + hideDataPoints: props.secondaryLineConfig?.hideDataPoints ?? hideDataPoints1, + dataPointsHeight: props.secondaryLineConfig?.dataPointsHeight ?? dataPointsHeight1, + dataPointsWidth: props.secondaryLineConfig?.dataPointsWidth ?? dataPointsWidth1, + dataPointsRadius: props.secondaryLineConfig?.dataPointsRadius ?? dataPointsRadius1, + dataPointsColor: props.secondaryLineConfig?.dataPointsColor ?? dataPointsColor1, + dataPointsShape: props.secondaryLineConfig?.dataPointsShape ?? dataPointsShape1, + showValuesAsDataPointsText: + props.secondaryLineConfig?.showValuesAsDataPointsText ?? showValuesAsDataPointsText, + startFillColor: props.secondaryLineConfig?.startFillColor ?? startFillColor1, + endFillColor: props.secondaryLineConfig?.endFillColor ?? endFillColor1, + startOpacity: props.secondaryLineConfig?.startOpacity ?? startOpacity1, + endOpacity: props.secondaryLineConfig?.endOpacity ?? endOpacity1, + textFontSize: props.secondaryLineConfig?.textFontSize ?? textFontSize1, + textColor: props.secondaryLineConfig?.textColor ?? textColor1, + showArrow: props.secondaryLineConfig?.showArrow ?? props.showArrows, + arrowConfig: props.secondaryLineConfig?.arrowConfig ?? props.arrowConfig, + }; + + const addLeadingAndTrailingPathForAreaFill = (initialPath, value, dataLength) => { + return ( + 'M ' + + initialSpacing + + ',' + + heightUptoXaxis + + ' ' + + 'L ' + + initialSpacing + + ',' + + getY(value) + + ' ' + + initialPath + + ' ' + + 'L ' + + (initialSpacing + spacing * (dataLength - 1)) + + ',' + + heightUptoXaxis + + ' ' + + 'L ' + + initialSpacing + + ',' + + heightUptoXaxis + + ' ' + ); + }; + useEffect(() => { + let pp = '', + pp2 = '', + pp3 = '', + pp4 = '', + pp5 = ''; + if (!props.curved) { + for (let i = 0; i < data.length; i++) { + if (i >= startIndex1 && i <= endIndex1 && !animateOnDataChange) { + pp += 'L' + getX(i) + ' ' + getY(data[i].value) + ' '; + } + if (data2.length && i >= startIndex2 && i <= endIndex2) { + pp2 += 'L' + getX(i) + ' ' + getY(data2[i].value) + ' '; + } + if (data3.length && i >= startIndex3 && i <= endIndex3) { + pp3 += 'L' + getX(i) + ' ' + getY(data3[i].value) + ' '; + } + if (data4.length && i >= startIndex4 && i <= endIndex4) { + pp4 += 'L' + getX(i) + ' ' + getY(data4[i].value) + ' '; + } + if (data5.length && i >= startIndex5 && i <= endIndex5) { + pp5 += 'L' + getX(i) + ' ' + getY(data5[i].value) + ' '; + } + } + setPoints2(pp2.replace('L', 'M')); + setPoints3(pp3.replace('L', 'M')); + setPoints4(pp4.replace('L', 'M')); + setPoints5(pp5.replace('L', 'M')); + + setPoints(pp.replace('L', 'M')); + + if (data.length > 1 && (props.showArrow1 || props.showArrows)) { + const ppArray = pp.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength1, + arrowWidth1, + showArrowBase1, + ); + + setArrow1Points(arrowPoints); + } + + if (data2.length > 1 && (props.showArrow2 || props.showArrows)) { + const ppArray = pp2.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength2, + arrowWidth2, + showArrowBase2, + ); + + setArrow2Points(arrowPoints); + } + + if (data3.length > 1 && (props.showArrow3 || props.showArrows)) { + const ppArray = pp3.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength3, + arrowWidth3, + showArrowBase3, + ); + + setArrow3Points(arrowPoints); + } + + if (data4.length > 1 && (props.showArrow4 || props.showArrows)) { + const ppArray = pp4.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength4, + arrowWidth4, + showArrowBase4, + ); + + setArrow4Points(arrowPoints); + } + + if (data5.length > 1 && (props.showArrow5 || props.showArrows)) { + const ppArray = pp5.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength5, + arrowWidth5, + showArrowBase5, + ); + + setArrow5Points(arrowPoints); + } + + /*************************** For Area Charts *************************/ + if (atLeastOneAreaChart) { + let ppp = '', + ppp2 = '', + ppp3 = '', + ppp4 = '', + ppp5 = ''; + + if ((areaChart || areaChart1) && data.length && !animateOnDataChange) { + ppp = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp += pp; + ppp += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setFillPoints(ppp.replace('L', 'M')); + } + + if ((areaChart || areaChart2) && data2.length) { + ppp2 = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp2 += pp2; + ppp2 += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp2 += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setFillPoints2(ppp2.replace('L', 'M')); + } + + if ((areaChart || areaChart3) && data3.length) { + ppp3 = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp3 += pp3; + ppp3 += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp3 += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setFillPoints3(ppp3.replace('L', 'M')); + } + if ((areaChart || areaChart4) && data4.length) { + ppp4 = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp4 += pp4; + ppp4 += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp4 += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setFillPoints4(ppp4.replace('L', 'M')); + } + + if ((areaChart || areaChart5) && data5.length) { + ppp5 = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp5 += pp5; + ppp5 += + 'L' + + (initialSpacing + spacing * (data.length - 1)) + + ' ' + + heightUptoXaxis; + ppp5 += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setFillPoints5(ppp5.replace('L', 'M')); + } + } + + /*************************************************************************************/ + } else { + const p1Array: Array> = [], + p2Array: Array> = [], + p3Array: Array> = [], + p4Array: Array> = [], + p5Array: Array> = []; + for (let i = 0; i < data.length; i++) { + if (i >= startIndex1 && i <= endIndex1) { + p1Array.push([getX(i), getY(data[i].value)]); + } + if (data2.length && i >= startIndex2 && i <= endIndex2) { + p2Array.push([getX(i), getY(data2[i].value)]); + } + if (data3.length && i >= startIndex3 && i <= endIndex3) { + p3Array.push([getX(i), getY(data3[i].value)]); + } + if (data4.length && i >= startIndex4 && i <= endIndex4) { + p4Array.push([getX(i), getY(data4[i].value)]); + } + if (data5.length && i >= startIndex5 && i <= endIndex5) { + p5Array.push([getX(i), getY(data5[i].value)]); + } + } + + let xx = svgPath(p1Array, curveType, curvature); + let xx2 = svgPath(p2Array, curveType, curvature); + let xx3 = svgPath(p3Array, curveType, curvature); + let xx4 = svgPath(p4Array, curveType, curvature); + let xx5 = svgPath(p5Array, curveType, curvature); + + setPoints(xx); + setPoints2(xx2); + setPoints3(xx3); + setPoints4(xx4); + setPoints5(xx5); + + if (data.length > 1 && (props.showArrow1 || props.showArrows)) { + const arrowTipY = p1Array[p1Array.length - 1][1]; + const arrowTipX = p1Array[p1Array.length - 1][0]; + const y1 = p1Array[p1Array.length - 2][1]; + const x1 = p1Array[p1Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength1, + arrowWidth1, + showArrowBase1, + ); + + setArrow1Points(arrowPoints); + } + + if (data2.length > 1 && (props.showArrow2 || props.showArrows)) { + const arrowTipY = p2Array[p2Array.length - 1][1]; + const arrowTipX = p2Array[p2Array.length - 1][0]; + const y1 = p2Array[p2Array.length - 2][1]; + const x1 = p2Array[p2Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength2, + arrowWidth2, + showArrowBase2, + ); + + setArrow2Points(arrowPoints); + } + + if (data3.length > 1 && (props.showArrow3 || props.showArrows)) { + const arrowTipY = p3Array[p3Array.length - 1][1]; + const arrowTipX = p3Array[p3Array.length - 1][0]; + const y1 = p3Array[p3Array.length - 2][1]; + const x1 = p3Array[p3Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength3, + arrowWidth3, + showArrowBase3, + ); + + setArrow2Points(arrowPoints); + } + + if (data4.length > 1 && (props.showArrow4 || props.showArrows)) { + const arrowTipY = p4Array[p4Array.length - 1][1]; + const arrowTipX = p4Array[p4Array.length - 1][0]; + const y1 = p4Array[p4Array.length - 2][1]; + const x1 = p4Array[p4Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength4, + arrowWidth4, + showArrowBase4, + ); + + setArrow2Points(arrowPoints); + } + + if (data5.length > 1 && (props.showArrow5 || props.showArrows)) { + const arrowTipY = p5Array[p5Array.length - 1][1]; + const arrowTipX = p5Array[p5Array.length - 1][0]; + const y1 = p5Array[p5Array.length - 2][1]; + const x1 = p5Array[p5Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength5, + arrowWidth5, + showArrowBase5, + ); + + setArrow2Points(arrowPoints); + } + + /*************************** For Area Charts *************************/ + + if (atLeastOneAreaChart) { + if ((areaChart || areaChart1) && data.length) { + xx = addLeadingAndTrailingPathForAreaFill(xx, data[0].value, data.length); + setFillPoints(xx); + } + + if ((areaChart || areaChart2) && data2.length) { + xx2 = addLeadingAndTrailingPathForAreaFill(xx2, data2[0].value, data2.length); + setFillPoints2(xx2); + } + + if ((areaChart || areaChart3) && data3.length) { + xx3 = addLeadingAndTrailingPathForAreaFill(xx3, data3[0].value, data3.length); + setFillPoints3(xx3); + } + + if ((areaChart || areaChart4) && data4.length) { + xx4 = addLeadingAndTrailingPathForAreaFill(xx, data4[0].value, data4.length); + setFillPoints4(xx4); + } + + if ((areaChart || areaChart5) && data5.length) { + xx5 = addLeadingAndTrailingPathForAreaFill(xx5, data5[0].value, data5.length); + setFillPoints5(xx5); + } + } + + /*************************************************************************************/ + } + }, [ + animateOnDataChange, + areaChart, + areaChart1, + areaChart2, + containerHeight, + data, + data2, + data3, + data4, + data5, + dataPointsWidth1, + dataPointsWidth2, + dataPointsWidth3, + dataPointsWidth4, + dataPointsWidth5, + initialSpacing, + maxValue, + props.curved, + spacing, + xAxisThickness, + startIndex1, + endIndex1, + startIndex2, + endIndex2, + startIndex3, + endIndex3, + startIndex4, + endIndex4, + startIndex5, + endIndex5, + arrowLength1, + arrowWidth1, + showArrowBase1, + props.showArrow1, + props.showArrows, + props.showArrow2, + props.showArrow3, + props.showArrow4, + props.showArrow5, + arrowLength2, + arrowWidth2, + showArrowBase2, + arrowLength3, + arrowWidth3, + showArrowBase3, + arrowLength4, + arrowWidth4, + showArrowBase4, + arrowLength5, + arrowWidth5, + showArrowBase5, + atLeastOneAreaChart, + getX, + getY, + areaChart3, + areaChart4, + areaChart5, + heightUptoXaxis, + curveType, + curvature, + addLeadingAndTrailingPathForAreaFill, + ]); + + useEffect(() => { + let pp = ''; + if (!secondaryLineConfig.curved) { + for (let i = 0; i < secondaryData.length; i++) { + if ( + i >= secondaryLineConfig.startIndex && + i <= secondaryLineConfig.endIndex && + !animateOnDataChange + ) { + pp += 'L' + getX(i) + ' ' + getSecondaryY(secondaryData[i].value) + ' '; + } + } + + setSecondaryPoints(pp.replace('L', 'M')); + + if (secondaryData.length > 1 && secondaryLineConfig.showArrow) { + const ppArray = pp.trim().split(' '); + const arrowTipY = parseInt(ppArray[ppArray.length - 1]); + const arrowTipX = parseInt(ppArray[ppArray.length - 2].replace('L', '')); + const y1 = parseInt(ppArray[ppArray.length - 3]); + const x1 = parseInt(ppArray[ppArray.length - 4].replace('L', '')); + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + secondaryLineConfig.arrowConfig?.length, + secondaryLineConfig.arrowConfig?.width, + secondaryLineConfig.arrowConfig?.showArrowBase, + ); + + setSecondaryArrowPoints(arrowPoints); + } + + /*************************** For Area Chart *************************/ + if (secondaryLineConfig.areaChart) { + let ppp = ''; + + if (secondaryData.length && !animateOnDataChange) { + ppp = 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + ppp += pp; + ppp += + 'L' + + (initialSpacing + spacing * (secondaryData.length - 1)) + + ' ' + + heightUptoXaxis; + ppp += 'L' + initialSpacing + ' ' + heightUptoXaxis + ' '; + setSecondaryFillPoints(ppp.replace('L', 'M')); + } + } + } else { + /*************************** For Curved Charts *************************/ + const p1Array: Array> = []; + for (let i = 0; i < secondaryData.length; i++) { + if (i >= secondaryLineConfig.startIndex && i <= secondaryLineConfig.endIndex) { + p1Array.push([getX(i), getSecondaryY(secondaryData[i].value)]); + } + } + + let xx = svgPath(p1Array, secondaryLineConfig.curveType, secondaryLineConfig.curvature); + + setSecondaryPoints(xx); + + if (secondaryData.length > 1 && (props.showArrow1 || props.showArrows)) { + const arrowTipY = p1Array[p1Array.length - 1][1]; + const arrowTipX = p1Array[p1Array.length - 1][0]; + const y1 = p1Array[p1Array.length - 2][1]; + const x1 = p1Array[p1Array.length - 2][0]; + + const arrowPoints = getArrowPoints( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength1, + arrowWidth1, + showArrowBase1, + ); + + setSecondaryArrowPoints(arrowPoints); + } + + /*************************** For Curved Area Charts *************************/ + + if (secondaryLineConfig.areaChart) { + if (secondaryData.length) { + xx = addLeadingAndTrailingPathForAreaFill( + xx, + secondaryData[0].value, + secondaryData.length, + ); + setSecondaryFillPoints(xx); + } + } + } + }, [secondaryData, secondaryLineConfig]); + + const gradientDirection = props.gradientDirection ?? 'vertical'; + const horizSections = [{ value: '0' }]; + const horizSectionsBelow: HorizSectionsType = []; + const stepHeight = props.stepHeight || containerHeight / noOfSections; + const stepValue = props.stepValue || maxValue / noOfSections; + const noOfSectionsBelowXAxis = props.noOfSectionsBelowXAxis || -minValue / stepValue; + + const showXAxisIndices = props.showXAxisIndices ?? AxesAndRulesDefaults.showXAxisIndices; + const xAxisIndicesHeight = props.xAxisIndicesHeight ?? AxesAndRulesDefaults.xAxisIndicesHeight; + const xAxisIndicesWidth = props.xAxisIndicesWidth ?? AxesAndRulesDefaults.xAxisIndicesWidth; + const xAxisIndicesColor = props.xAxisIndicesColor ?? AxesAndRulesDefaults.xAxisIndicesColor; + + // const yAxisThickness = props.yAxisThickness ?? 1; + const xAxisTextNumberOfLines = + props.xAxisTextNumberOfLines ?? AxesAndRulesDefaults.xAxisTextNumberOfLines; + const horizontalRulesStyle = props.horizontalRulesStyle; + const showFractionalValues = + props.showFractionalValues ?? AxesAndRulesDefaults.showFractionalValues; + const yAxisLabelWidth = + props.yAxisLabelWidth ?? + (props.hideYAxisText + ? AxesAndRulesDefaults.yAxisEmptyLabelWidth + : AxesAndRulesDefaults.yAxisLabelWidth); + + const horizontal = false; + const yAxisAtTop = false; + + defaultPointerConfig.pointerStripHeight = containerHeight; + + const pointerConfig = props.pointerConfig || null; + const getPointerProps = props.getPointerProps || null; + const pointerHeight = pointerConfig?.height + ? pointerConfig.height + : defaultPointerConfig.height; + const pointerWidth = pointerConfig?.width ? pointerConfig.width : defaultPointerConfig.width; + const pointerRadius = pointerConfig?.radius + ? pointerConfig.radius + : defaultPointerConfig.radius; + const pointerColor = pointerConfig?.pointerColor + ? pointerConfig.pointerColor + : defaultPointerConfig.pointerColor; + const pointerComponent = pointerConfig?.pointerComponent + ? pointerConfig.pointerComponent + : defaultPointerConfig.pointerComponent; + + const showPointerStrip = + pointerConfig?.showPointerStrip === false ? false : defaultPointerConfig.showPointerStrip; + const pointerStripHeight = pointerConfig?.pointerStripHeight + ? pointerConfig.pointerStripHeight + : defaultPointerConfig.pointerStripHeight; + const pointerStripWidth = pointerConfig?.pointerStripWidth + ? pointerConfig.pointerStripWidth + : defaultPointerConfig.pointerStripWidth; + const pointerStripColor = pointerConfig?.pointerStripColor + ? pointerConfig.pointerStripColor + : defaultPointerConfig.pointerStripColor; + const pointerStripUptoDataPoint = pointerConfig?.pointerStripUptoDataPoint + ? pointerConfig.pointerStripUptoDataPoint + : defaultPointerConfig.pointerStripUptoDataPoint; + const pointerLabelComponent = pointerConfig?.pointerLabelComponent + ? pointerConfig.pointerLabelComponent + : defaultPointerConfig.pointerLabelComponent; + const stripOverPointer = pointerConfig?.stripOverPointer + ? pointerConfig.stripOverPointer + : defaultPointerConfig.stripOverPointer; + const shiftPointerLabelX = pointerConfig?.shiftPointerLabelX + ? pointerConfig.shiftPointerLabelX + : defaultPointerConfig.shiftPointerLabelX; + const shiftPointerLabelY = pointerConfig?.shiftPointerLabelY + ? pointerConfig.shiftPointerLabelY + : defaultPointerConfig.shiftPointerLabelY; + const pointerLabelWidth = pointerConfig?.pointerLabelWidth + ? pointerConfig.pointerLabelWidth + : defaultPointerConfig.pointerLabelWidth; + const pointerLabelHeight = pointerConfig?.pointerLabelHeight + ? pointerConfig.pointerLabelHeight + : defaultPointerConfig.pointerLabelHeight; + const autoAdjustPointerLabelPosition = + pointerConfig?.autoAdjustPointerLabelPosition ?? + defaultPointerConfig.autoAdjustPointerLabelPosition; + const pointerVanishDelay = pointerConfig?.pointerVanishDelay + ? pointerConfig.pointerVanishDelay + : defaultPointerConfig.pointerVanishDelay; + const activatePointersOnLongPress = pointerConfig?.activatePointersOnLongPress + ? pointerConfig.activatePointersOnLongPress + : defaultPointerConfig.activatePointersOnLongPress; + const activatePointersDelay = pointerConfig?.activatePointersDelay + ? pointerConfig.activatePointersDelay + : defaultPointerConfig.activatePointersDelay; + const hidePointer1 = pointerConfig?.hidePointer1 + ? pointerConfig.hidePointer1 + : defaultPointerConfig.hidePointer1; + const hidePointer2 = pointerConfig?.hidePointer2 + ? pointerConfig.hidePointer2 + : defaultPointerConfig.hidePointer2; + const hidePointer3 = pointerConfig?.hidePointer3 + ? pointerConfig.hidePointer3 + : defaultPointerConfig.hidePointer3; + const hidePointer4 = pointerConfig?.hidePointer4 + ? pointerConfig.hidePointer4 + : defaultPointerConfig.hidePointer4; + const hidePointer5 = pointerConfig?.hidePointer5 + ? pointerConfig.hidePointer5 + : defaultPointerConfig.hidePointer5; + const disableScroll = + props.disableScroll || + (pointerConfig + ? activatePointersOnLongPress + ? responderActive + ? true + : false + : true + : false); + const showScrollIndicator = props.showScrollIndicator ?? LineDefaults.showScrollIndicator; + + const focusEnabled = props.focusEnabled ?? LineDefaults.focusEnabled; + const showDataPointOnFocus = props.showDataPointOnFocus ?? LineDefaults.showDataPointOnFocus; + const showStripOnFocus = props.showStripOnFocus ?? LineDefaults.showStripOnFocus; + const showTextOnFocus = props.showTextOnFocus ?? LineDefaults.showTextOnFocus; + const stripHeight = props.stripHeight; + const stripWidth = props.stripWidth ?? LineDefaults.stripWidth; + const stripColor = props.stripColor ?? color1; + const stripOpacity = props.stripOpacity ?? (startOpacity1 + endOpacity1) / 2; + const unFocusOnPressOut = props.unFocusOnPressOut ?? LineDefaults.unFocusOnPressOut; + const delayBeforeUnFocus = props.delayBeforeUnFocus ?? LineDefaults.delayBeforeUnFocus; + + // horizSections.pop(); + // for (let i = 0; i <= noOfSections; i++) { + // let value = maxValue - stepValue * i; + // if (props.showFractionalValues || props.roundToDigits) { + // value = parseFloat(value.toFixed(props.roundToDigits || 1)); + // } + // horizSections.push({ + // value: props.yAxisLabelTexts + // ? props.yAxisLabelTexts[noOfSections - i] ?? value.toString() + // : value.toString(), + // }); + // } + if (noOfSectionsBelowXAxis) { + for (let i = 1; i <= noOfSectionsBelowXAxis; i++) { + let value = stepValue * -i; + if (props.showFractionalValues || props.roundToDigits) { + value = parseFloat(value.toFixed(props.roundToDigits || 1)); + } + horizSectionsBelow.push({ + value: props.yAxisLabelTexts + ? props.yAxisLabelTexts[noOfSectionsBelowXAxis - i] ?? value.toString() + : value.toString(), + }); + } + } + + const containerHeightIncludingBelowXAxis = + extendedContainerHeight + horizSectionsBelow.length * stepHeight; + + const renderLabel = ( + index: number, + label: String, + labelTextStyle: any, + labelComponent: Function | undefined, + ) => { + return ( + + {labelComponent ? ( + labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const renderAnimatedLabel = ( + index: number, + label: String, + labelTextStyle: any, + labelComponent: Function | undefined, + ) => { + return ( + + {labelComponent ? ( + labelComponent() + ) : ( + + {label || ''} + + )} + + ); + }; + + const animatedWidth = widthValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const animatedWidth2 = widthValue2.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const animatedWidth3 = widthValue3.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const animatedWidth4 = widthValue4.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const animatedWidth5 = widthValue5.interpolate({ + inputRange: [0, 1], + outputRange: [0, totalWidth], + }); + + const onStripPress = (item, index) => { + setSelectedIndex(index); + if (props.onFocus) { + props.onFocus(item, index); + } + }; + + const renderDataPoints = ( + hideDataPoints, + dataForRender, + originalDataFromProps, + dataPtsShape, + dataPtsWidth, + dataPtsHeight, + dataPtsColor, + dataPtsRadius, + textColor, + textFontSize, + startIndex, + endIndex, + isSecondary, + showValuesAsDataPointsText, + ) => { + const getYOrSecondaryY = isSecondary ? getSecondaryY : getY; + return dataForRender.map((item: itemType, index: number) => { + if (index < startIndex || index > endIndex) return null; + if (item.hideDataPoint) { + return null; + } + let dataPointsShape, + dataPointsWidth, + dataPointsHeight, + dataPointsColor, + dataPointsRadius, + text, + customDataPoint, + dataPointLabelComponent; + if (index === selectedIndex) { + dataPointsShape = + item.focusedDataPointShape || + props.focusedDataPointShape || + item.dataPointShape || + dataPtsShape; + dataPointsWidth = + item.focusedDataPointWidth || + props.focusedDataPointWidth || + item.dataPointWidth || + dataPtsWidth; + dataPointsHeight = + item.focusedDataPointHeight || + props.focusedDataPointHeight || + item.dataPointHeight || + dataPtsHeight; + dataPointsColor = + item.focusedDataPointColor || props.focusedDataPointColor || 'orange'; + dataPointsRadius = + item.focusedDataPointRadius || + props.focusedDataPointRadius || + item.dataPointRadius || + dataPtsRadius; + if (showTextOnFocus) { + text = item.dataPointText; + } + customDataPoint = + item.focusedCustomDataPoint || + props.focusedCustomDataPoint || + item.customDataPoint || + props.customDataPoint; + dataPointLabelComponent = + item.focusedDataPointLabelComponent || item.dataPointLabelComponent; + } else { + dataPointsShape = item.dataPointShape || dataPtsShape; + dataPointsWidth = item.dataPointWidth || dataPtsWidth; + dataPointsHeight = item.dataPointHeight || dataPtsHeight; + dataPointsColor = item.dataPointColor || dataPtsColor; + dataPointsRadius = item.dataPointRadius || dataPtsRadius; + if (showTextOnFocus) { + text = ''; + } + customDataPoint = item.customDataPoint || props.customDataPoint; + dataPointLabelComponent = item.dataPointLabelComponent; + } + + if (showValuesAsDataPointsText) { + text = originalDataFromProps[index].value; + } + + const currentStripHeight = item.stripHeight ?? stripHeight; + const currentStripWidth = item.stripWidth ?? stripWidth; + const currentStripOpacity = item.stripOpacity ?? stripOpacity; + const currentStripColor = item.stripColor || stripColor; + + return ( + + {focusEnabled ? ( + <> + {unFocusOnPressOut ? ( // remove strip on onFocus + onStripPress(item, index)} + onPressOut={() => + setTimeout(() => setSelectedIndex(-1), delayBeforeUnFocus) + } + x={initialSpacing + (spacing * index - spacing / 2)} + y={8} + width={spacing} + height={containerHeight - 0} + fill={'none'} + /> + ) : ( + onStripPress(item, index)} + x={initialSpacing + (spacing * index - spacing / 2)} + y={8} + width={spacing} + height={containerHeight - 0} + fill={'none'} + /> + )} + + ) : null} + {item.showStrip || + (focusEnabled && index === selectedIndex && showStripOnFocus) ? ( + + ) : null} + {hideDataPoints ? null : ( + <> + {customDataPoint ? ( + + {customDataPoint()} + + ) : null} + {dataPointsShape === 'rectangular' ? ( + + {customDataPoint ? null : ( + { + item.onPress + ? item.onPress(item, index) + : props.onPress + ? props.onPress(item, index) + : null; + }} + /> + )} + + ) : ( + + {customDataPoint ? null : ( + { + item.onPress + ? item.onPress(item, index) + : props.onPress + ? props.onPress(item, index) + : null; + }} + /> + )} + + )} + {dataPointLabelComponent ? ( + !showTextOnFocus || index === selectedIndex ? ( + + {dataPointLabelComponent()} + + ) : null + ) : text || item.dataPointText ? ( + !showTextOnFocus || index === selectedIndex ? ( + + {!showTextOnFocus && !showValuesAsDataPointsText + ? item.dataPointText + : text} + + ) : null + ) : null} + + )} + + ); + }); + }; + + const renderSpecificVerticalLines = (dataForRender: any) => { + return dataForRender.map((item: itemType, index: number) => { + if (item.showVerticalLine) { + return ( + + ); + } + return null; + }); + }; + + const renderPointer = (lineNumber: number) => { + if (lineNumber === 1 && hidePointer1) return; + if (lineNumber === 2 && hidePointer2) return; + if (lineNumber === 3 && hidePointer3) return; + if (lineNumber === 4 && hidePointer4) return; + if (lineNumber === 5 && hidePointer5) return; + let pointerItemLocal, pointerYLocal, pointerColorLocal; + switch (lineNumber) { + case 1: + pointerItemLocal = pointerItem; + pointerYLocal = pointerY; + pointerColorLocal = pointerConfig?.pointer1Color || pointerColor; + break; + case 2: + pointerItemLocal = pointerItem2; + pointerYLocal = pointerY2; + pointerColorLocal = pointerConfig?.pointer2Color || pointerColor; + break; + case 3: + pointerItemLocal = pointerItem3; + pointerYLocal = pointerY3; + pointerColorLocal = pointerConfig?.pointer3Color || pointerColor; + break; + case 4: + pointerItemLocal = pointerItem4; + pointerYLocal = pointerY4; + pointerColorLocal = pointerConfig?.pointer4Color || pointerColor; + break; + case 5: + pointerItemLocal = pointerItem5; + pointerYLocal = pointerY5; + pointerColorLocal = pointerConfig?.pointer5Color || pointerColor; + break; + } + + return ( + + {pointerComponent ? ( + pointerComponent() + ) : ( + + )} + + ); + }; + + const renderStripAndLabel = () => { + const pointerItemLocal = [pointerItem]; + const arr = [pointerY]; + if (pointerY2 !== 0) { + arr.push(pointerY2); + pointerItemLocal.push(pointerItem2); + } + if (pointerY3 !== 0) { + arr.push(pointerY3); + pointerItemLocal.push(pointerItem3); + } + if (pointerY4 !== 0) { + arr.push(pointerY4); + pointerItemLocal.push(pointerItem4); + } + if (pointerY5 !== 0) { + arr.push(pointerY5); + pointerItemLocal.push(pointerItem5); + } + const pointerYLocal = Math.min(...arr); + + let left = 0, + top = 0; + if (autoAdjustPointerLabelPosition) { + if (pointerX < pointerLabelWidth / 2) { + left = 7; + } else if ( + activatePointersOnLongPress && + pointerX - scrollX < pointerLabelWidth / 2 - 10 + ) { + left = 7; + } else { + if ( + !activatePointersOnLongPress && + pointerX > + (props.width || Dimensions.get('window').width - yAxisLabelWidth - 15) - + pointerLabelWidth / 2 + ) { + left = -pointerLabelWidth - 4; + } else if ( + activatePointersOnLongPress && + pointerX - scrollX > + ((props.width ?? 0) + 10 || + Dimensions.get('window').width - yAxisLabelWidth - 15) - + pointerLabelWidth / 2 + ) { + left = -pointerLabelWidth - 4; + } else { + left = -pointerLabelWidth / 2 + 5; + } + } + } else { + left = (pointerRadius || pointerWidth / 2) - 10 + shiftPointerLabelX; + } + + if (autoAdjustPointerLabelPosition) { + if (pointerLabelHeight - pointerYLocal > 10) { + top = 10; + } else { + top = -pointerLabelHeight; + } + } else { + top = + (pointerStripUptoDataPoint + ? pointerRadius || pointerStripHeight / 2 + : -pointerYLocal + 8) - + pointerLabelWidth / 2 + + shiftPointerLabelY; + } + + return ( + + {showPointerStrip && ( + + + + + + )} + + {pointerLabelComponent && ( + + {pointerLabelComponent(pointerItemLocal)} + + )} + + ); + }; + + const lineSvgComponent = ( + points: any, + currentLineThickness: number | undefined, + color: string, + fillPoints: any, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + showArrow: boolean, + arrowPoints, + arrowStrokeWidth, + arrowStrokeColor, + arrowFillColor, + ) => { + return ( + + {strokeDashArray && + strokeDashArray.length === 2 && + typeof strokeDashArray[0] === 'number' && + typeof strokeDashArray[1] === 'number' ? ( + + ) : ( + + )} + + {/*********************** For Area Chart ************/} + + {atLeastOneAreaChart && ( + + + + + )} + {atLeastOneAreaChart && ( + + )} + + {/******************************************************************/} + + {renderSpecificVerticalLines(data)} + {renderSpecificVerticalLines(data2)} + {renderSpecificVerticalLines(data3)} + {renderSpecificVerticalLines(data4)} + {renderSpecificVerticalLines(data5)} + + {/*** !!! Here it's done thrice intentionally, trying to make it to only 1 breaks things !!! ***/} + {renderDataPoints( + hideDataPoints1, + data, + props.data, + dataPointsShape1, + dataPointsWidth1, + dataPointsHeight1, + dataPointsColor1, + dataPointsRadius1, + textColor1, + textFontSize1, + startIndex1, + endIndex1, + false, + showValuesAsDataPointsText, + )} + {renderDataPoints( + hideDataPoints2, + data2, + props.data2, + dataPointsShape2, + dataPointsWidth2, + dataPointsHeight2, + dataPointsColor2, + dataPointsRadius2, + textColor2, + textFontSize2, + startIndex2, + endIndex2, + false, + showValuesAsDataPointsText, + )} + {renderDataPoints( + hideDataPoints3, + data3, + props.data3, + dataPointsShape3, + dataPointsWidth3, + dataPointsHeight3, + dataPointsColor3, + dataPointsRadius3, + textColor3, + textFontSize3, + startIndex3, + endIndex3, + false, + showValuesAsDataPointsText, + )} + {renderDataPoints( + hideDataPoints4, + data4, + props.data4, + dataPointsShape4, + dataPointsWidth4, + dataPointsHeight4, + dataPointsColor4, + dataPointsRadius4, + textColor4, + textFontSize4, + startIndex4, + endIndex4, + false, + showValuesAsDataPointsText, + )} + {renderDataPoints( + hideDataPoints5, + data5, + props.data5, + dataPointsShape5, + dataPointsWidth5, + dataPointsHeight5, + dataPointsColor5, + dataPointsRadius5, + textColor5, + textFontSize5, + startIndex5, + endIndex5, + false, + showValuesAsDataPointsText, + )} + {secondaryData?.length + ? renderDataPoints( + secondaryLineConfig.hideDataPoints, + secondaryData, + props.secondaryData, + secondaryLineConfig.dataPointsShape, + secondaryLineConfig.dataPointsWidth, + secondaryLineConfig.dataPointsHeight, + secondaryLineConfig.dataPointsColor, + secondaryLineConfig.dataPointsRadius, + secondaryLineConfig.textColor, + secondaryLineConfig.textFontSize, + secondaryLineConfig.startIndex, + secondaryLineConfig.endIndex, + true, + secondaryLineConfig.showValuesAsDataPointsText, + ) + : null} + {showArrow && ( + + )} + + ); + }; + + const renderLine = ( + zIndex: number, + points: any, + currentLineThickness: number | undefined, + color: string, + fillPoints: any, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + showArrow, + arrowPoints, + arrowStrokeWidth, + arrowStrokeColor, + arrowFillColor, + ) => { + return ( + (pointerConfig ? true : false)} + onMoveShouldSetResponder={(_) => (pointerConfig ? true : false)} + onResponderGrant={(evt) => { + if (!pointerConfig) return; + setResponderStartTime(evt.timeStamp); + if (activatePointersOnLongPress) { + return; + } + const x = evt.nativeEvent.locationX; + if ( + !activatePointersOnLongPress && + x > (props.width || Dimensions.get('window').width) + ) + return; + let factor = (x - initialSpacing) / spacing; + factor = Math.round(factor); + factor = Math.min(factor, data.length - 1); + factor = Math.max(factor, 0); + const z = + initialSpacing + spacing * factor - (pointerRadius || pointerWidth / 2) - 2; + setPointerX(z); + setPointerIndex(factor); + let item, y; + item = data[factor]; + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY(y); + setPointerItem(item); + if (data2 && data2.length) { + item = data2[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY2(y); + setPointerItem2(item); + } + } + if (data3 && data3.length) { + item = data3[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY3(y); + setPointerItem3(item); + } + } + if (data4 && data4.length) { + item = data4[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY4(y); + setPointerItem4(item); + } + } + if (data5 && data5.length) { + item = data5[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY5(y); + setPointerItem5(item); + } + } + }} + onResponderMove={(evt) => { + if (!pointerConfig) return; + if ( + activatePointersOnLongPress && + evt.timeStamp - responderStartTime < activatePointersDelay + ) { + return; + } else { + setResponderActive(true); + } + const x = evt.nativeEvent.locationX; + if ( + !activatePointersOnLongPress && + x > (props.width || Dimensions.get('window').width) + ) + return; + let factor = (x - initialSpacing) / spacing; + factor = Math.round(factor); + factor = Math.min(factor, data.length - 1); + factor = Math.max(factor, 0); + const z = + initialSpacing + spacing * factor - (pointerRadius || pointerWidth / 2) - 2; + let item, y; + setPointerX(z); + setPointerIndex(factor); + item = data[factor]; + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY(y); + setPointerItem(item); + if (data2 && data2.length) { + item = data2[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY2(y); + setPointerItem2(item); + } + } + if (data3 && data3.length) { + item = data3[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY3(y); + setPointerItem3(item); + } + } + if (data4 && data4.length) { + item = data4[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY4(y); + setPointerItem4(item); + } + } + if (data5 && data5.length) { + item = data5[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY5(y); + setPointerItem5(item); + } + } + }} + // onResponderReject={evt => { + // console.log('evt...reject.......',evt); + // }} + onResponderEnd={(evt) => { + // console.log('evt...end.......',evt); + setResponderStartTime(0); + setPointerIndex(-1); + setResponderActive(false); + setTimeout(() => setPointerX(0), pointerVanishDelay); + }} + onResponderTerminationRequest={(evt) => false} + // onResponderTerminate={evt => { + // console.log('evt...terminate.......',evt); + // }} + // onResponderRelease={evt => { + // setResponderStartTime(0); + // setResponderActive(false); + // setTimeout(() => setPointerX(0), pointerVanishDelay); + // }} + style={{ + position: 'absolute', + height: + containerHeightIncludingBelowXAxis + + (props.overflowBottom ?? dataPointsRadius1), + bottom: 60 + labelsExtraHeight - (props.overflowBottom ?? dataPointsRadius1), + width: totalWidth, + zIndex: zIndex, + }} + > + {lineSvgComponent( + points, + currentLineThickness, + color, + fillPoints, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray, + showArrow, + arrowPoints, + arrowStrokeWidth, + arrowStrokeColor, + arrowFillColor, + )} + + ); + }; + + const renderAnimatedLine = ( + zIndex: number, + points: any, + animatedWidth: any, + currentLineThickness: number | undefined, + color: string, + fillPoints: any, + startFillColor: string, + endFillColor: string, + startOpacity: number, + endOpacity: number, + strokeDashArray: Array | undefined | null, + showArrow, + arrowPoints, + arrowStrokeWidth, + arrowStrokeColor, + arrowFillColor, + ) => { + // console.log('animatedWidth is-------->', animatedWidth); + return ( + (pointerConfig ? true : false)} + onMoveShouldSetResponder={(evt) => (pointerConfig ? true : false)} + onResponderGrant={(evt) => { + if (!pointerConfig) return; + setResponderStartTime(evt.timeStamp); + if (activatePointersOnLongPress) { + return; + } + const x = evt.nativeEvent.locationX; + if ( + !activatePointersOnLongPress && + x > (props.width || Dimensions.get('window').width) + ) + return; + let factor = (x - initialSpacing) / spacing; + factor = Math.round(factor); + factor = Math.min(factor, data.length - 1); + factor = Math.max(factor, 0); + const z = + initialSpacing + spacing * factor - (pointerRadius || pointerWidth / 2) - 2; + setPointerX(z); + setPointerIndex(factor); + let item, y; + item = data[factor]; + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY(y); + setPointerItem(item); + if (data2 && data2.length) { + item = data2[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY2(y); + setPointerItem2(item); + } + } + if (data3 && data3.length) { + item = data3[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY3(y); + setPointerItem3(item); + } + } + if (data4 && data4.length) { + item = data4[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY4(y); + setPointerItem4(item); + } + } + if (data5 && data5.length) { + item = data5[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY5(y); + setPointerItem5(item); + } + } + }} + onResponderMove={(evt) => { + if (!pointerConfig) return; + if ( + activatePointersOnLongPress && + evt.timeStamp - responderStartTime < activatePointersDelay + ) { + return; + } else { + setResponderActive(true); + } + const x = evt.nativeEvent.locationX; + if ( + !activatePointersOnLongPress && + x > (props.width || Dimensions.get('window').width) + ) + return; + let factor = (x - initialSpacing) / spacing; + factor = Math.round(factor); + factor = Math.min(factor, data.length - 1); + factor = Math.max(factor, 0); + const z = + initialSpacing + spacing * factor - (pointerRadius || pointerWidth / 2) - 2; + let item, y; + setPointerX(z); + setPointerIndex(factor); + item = data[factor]; + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY(y); + setPointerItem(item); + if (data2 && data2.length) { + item = data2[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY2(y); + setPointerItem2(item); + } + } + if (data3 && data3.length) { + item = data3[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY3(y); + setPointerItem3(item); + } + } + if (data4 && data4.length) { + item = data4[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY4(y); + setPointerItem4(item); + } + } + if (data5 && data5.length) { + item = data5[factor]; + if (item) { + y = + containerHeight - + (item.value * containerHeight) / maxValue - + (pointerRadius || pointerHeight / 2) + + 10; + setPointerY5(y); + setPointerItem5(item); + } + } + }} + // onResponderReject={evt => { + // console.log('evt...reject.......',evt); + // }} + onResponderEnd={(evt) => { + // console.log('evt...end.......',evt); + setResponderStartTime(0); + setPointerIndex(-1); + setResponderActive(false); + setTimeout(() => setPointerX(0), pointerVanishDelay); + }} + onResponderTerminationRequest={(evt) => false} + // onResponderTerminate={evt => { + // console.log('evt...terminate.......',evt); + // }} + // onResponderRelease={evt => { + // setResponderStartTime(0); + // setResponderActive(false); + // setTimeout(() => setPointerX(0), pointerVanishDelay); + // }} + style={{ + position: 'absolute', + height: containerHeightIncludingBelowXAxis, + bottom: 60, //stepHeight * -0.5 + xAxisThickness, + width: animatedWidth, + zIndex: zIndex, + // backgroundColor: 'wheat', + }} + > + {lineSvgComponent( + points, + currentLineThickness, + color, + fillPoints, + startFillColor, + endFillColor, + startOpacity, + endOpacity, + strokeDashArray, + showArrow, + arrowPoints, + arrowStrokeWidth, + arrowStrokeColor, + arrowFillColor, + )} + + ); + }; + + const remainingScrollViewProps = { + onScroll: (ev: any) => { + if ( + pointerConfig && + pointerConfig.activatePointersOnLongPress && + pointerConfig.autoAdjustPointerLabelPosition + ) { + setScrollX(ev.nativeEvent.contentOffset.x); + } + }, + }; + + const renderChartContent = () => { + return ( + <> + {isAnimated + ? renderAnimatedLine( + zIndex1, + points, + animatedWidth, + thickness1, + color1, + fillPoints, + startFillColor1, + endFillColor1, + startOpacity1, + endOpacity1, + strokeDashArray1, + props.showArrow1 || props.showArrows, + arrow1Points, + arrowStrokeWidth1, + arrowStrokeColor1, + arrowFillColor1, + ) + : renderLine( + zIndex1, + points, + thickness1, + color1, + fillPoints, + startFillColor1, + endFillColor1, + startOpacity1, + endOpacity1, + strokeDashArray1, + props.showArrow1 || props.showArrows, + arrow1Points, + arrowStrokeWidth1, + arrowStrokeColor1, + arrowFillColor1, + )} + {secondaryPoints + ? isAnimated + ? renderAnimatedLine( + secondaryLineConfig.zIndex, + secondaryPoints, + animatedWidth, + secondaryLineConfig.thickness, + secondaryLineConfig.color.toString(), + secondaryFillPoints, + secondaryLineConfig.startFillColor, + secondaryLineConfig.endFillColor, + secondaryLineConfig.startOpacity, + secondaryLineConfig.endOpacity, + secondaryLineConfig.strokeDashArray, + secondaryLineConfig.showArrow, + secondaryArrowPoints, + secondaryLineConfig.arrowConfig?.strokeWidth, + secondaryLineConfig.arrowConfig?.strokeColor, + secondaryLineConfig.arrowConfig?.fillColor, + ) + : renderLine( + secondaryLineConfig.zIndex, + secondaryPoints, + secondaryLineConfig.thickness, + secondaryLineConfig.color.toString(), + secondaryFillPoints, + secondaryLineConfig.startFillColor, + secondaryLineConfig.endFillColor, + secondaryLineConfig.startOpacity, + secondaryLineConfig.endOpacity, + secondaryLineConfig.strokeDashArray, + secondaryLineConfig.showArrow, + secondaryArrowPoints, + secondaryLineConfig.arrowConfig?.strokeWidth, + secondaryLineConfig.arrowConfig?.strokeColor, + secondaryLineConfig.arrowConfig?.fillColor, + ) + : null} + {points2 + ? isAnimated + ? renderAnimatedLine( + zIndex2, + points2, + animatedWidth2, + thickness2, + color2, + fillPoints2, + startFillColor2, + endFillColor2, + startOpacity2, + endOpacity2, + strokeDashArray2, + props.showArrow2 || props.showArrows, + arrow2Points, + arrowStrokeWidth2, + arrowStrokeColor2, + arrowFillColor2, + ) + : renderLine( + zIndex2, + points2, + thickness2, + color2, + fillPoints2, + startFillColor2, + endFillColor2, + startOpacity2, + endOpacity2, + strokeDashArray2, + props.showArrow2 || props.showArrows, + arrow2Points, + arrowStrokeWidth2, + arrowStrokeColor2, + arrowFillColor2, + ) + : null} + {points3 + ? isAnimated + ? renderAnimatedLine( + zIndex3, + points3, + animatedWidth3, + thickness3, + color3, + fillPoints3, + startFillColor3, + endFillColor3, + startOpacity3, + endOpacity3, + strokeDashArray3, + props.showArrow3 || props.showArrows, + arrow3Points, + arrowStrokeWidth3, + arrowStrokeColor3, + arrowFillColor3, + ) + : renderLine( + zIndex3, + points3, + thickness3, + color3, + fillPoints3, + startFillColor3, + endFillColor3, + startOpacity3, + endOpacity3, + strokeDashArray3, + props.showArrow3 || props.showArrows, + arrow3Points, + arrowStrokeWidth3, + arrowStrokeColor3, + arrowFillColor3, + ) + : null} + {points4 + ? isAnimated + ? renderAnimatedLine( + zIndex4, + points4, + animatedWidth4, + thickness4, + color4, + fillPoints4, + startFillColor4, + endFillColor4, + startOpacity4, + endOpacity4, + strokeDashArray4, + props.showArrow4 || props.showArrows, + arrow4Points, + arrowStrokeWidth4, + arrowStrokeColor4, + arrowFillColor4, + ) + : renderLine( + zIndex4, + points4, + thickness4, + color4, + fillPoints4, + startFillColor4, + endFillColor4, + startOpacity4, + endOpacity4, + strokeDashArray4, + props.showArrow4 || props.showArrows, + arrow4Points, + arrowStrokeWidth4, + arrowStrokeColor4, + arrowFillColor4, + ) + : null} + {points5 + ? isAnimated + ? renderAnimatedLine( + zIndex5, + points5, + animatedWidth5, + thickness5, + color5, + fillPoints5, + startFillColor5, + endFillColor5, + startOpacity5, + endOpacity5, + strokeDashArray5, + props.showArrow5 || props.showArrows, + arrow5Points, + arrowStrokeWidth5, + arrowStrokeColor5, + arrowFillColor5, + ) + : renderLine( + zIndex5, + points5, + thickness5, + color5, + fillPoints5, + startFillColor5, + endFillColor5, + startOpacity5, + endOpacity5, + strokeDashArray5, + props.showArrow5 || props.showArrows, + arrow5Points, + arrowStrokeWidth5, + arrowStrokeColor5, + arrowFillColor5, + ) + : null} + {pointerX > 0 ? ( + + {!stripOverPointer && renderStripAndLabel()} + {renderPointer(1)} + {points2 ? renderPointer(2) : null} + {points3 ? renderPointer(3) : null} + {points4 ? renderPointer(4) : null} + {points5 ? renderPointer(5) : null} + {stripOverPointer && renderStripAndLabel()} + + ) : null} + {data.map((item: itemType, index: number) => { + // console.log('item', item) + return ( + + {isAnimated + ? renderAnimatedLabel( + index, + item.label || + (props.xAxisLabelTexts && props.xAxisLabelTexts[index] + ? props.xAxisLabelTexts[index] + : ''), + item.labelTextStyle || props.xAxisLabelTextStyle, + item.labelComponent, + ) + : renderLabel( + index, + item.label || + (props.xAxisLabelTexts && props.xAxisLabelTexts[index] + ? props.xAxisLabelTexts[index] + : ''), + item.labelTextStyle || props.xAxisLabelTextStyle, + item.labelComponent, + )} + {/* {renderLabel(index, item.label, item.labelTextStyle)} */} + + ); + })} + + ); + }; + + const barAndLineChartsWrapperProps: BarAndLineChartsWrapperTypes = { + chartType: chartTypes.LINE, + containerHeight, + horizSectionsBelow, + stepHeight, + labelsExtraHeight, + yAxisLabelWidth, + horizontal, + rtl: false, + shiftX: 0, + shiftY: 0, + scrollRef, + yAxisAtTop, + initialSpacing, + data, + stackData: undefined, // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + secondaryData: secondaryData, + barWidth: 0, // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + xAxisThickness, + totalWidth, + disableScroll, + showScrollIndicator, + scrollToEnd, + scrollToIndex: props.scrollToIndex, + scrollAnimation, + indicatorColor: props.indicatorColor, + setSelectedIndex, + spacing, + showLine: false, + lineConfig: null, + maxValue, + lineData: [], // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + animatedWidth, + lineBehindBars: false, + points, + arrowPoints: [], // Not needed but passing this prop to maintain consistency (between LineChart and BarChart props) + renderChartContent, + remainingScrollViewProps, + + //horizSectionProps- + width: props.width, + horizSections, + endSpacing, + horizontalRulesStyle, + noOfSections, + showFractionalValues, + + axesAndRulesProps: getAxesAndRulesProps(props, stepValue), + + yAxisLabelTexts: props.yAxisLabelTexts, + yAxisOffset: props.yAxisOffset, + rotateYAxisTexts: 0, + hideAxesAndRules: props.hideAxesAndRules, + + showXAxisIndices, + xAxisIndicesHeight, + xAxisIndicesWidth, + xAxisIndicesColor, + pointerConfig, + getPointerProps, + pointerIndex, + pointerX, + pointerY, + }; + + return ; +}; diff --git a/src/components/charts/LineChart/styles.tsx b/src/components/charts/LineChart/styles.tsx new file mode 100644 index 00000000..f6af90e2 --- /dev/null +++ b/src/components/charts/LineChart/styles.tsx @@ -0,0 +1,47 @@ +import {StyleSheet} from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + width: '100%', + marginBottom: 40, + marginRight: 40, + }, + horizBar: { + flexDirection: 'row', + }, + leftLabel: { + justifyContent: 'center', + alignItems: 'center', + }, + lastLeftLabel: { + justifyContent: 'center', + alignItems: 'center', + }, + leftPart: { + justifyContent: 'center', + width: '100%', + }, + lastLeftPart: { + justifyContent: 'flex-end', + width: '100%', + }, + line: { + width: '100%', + height: 1, + backgroundColor: 'gray', + opacity: 0.5, + }, + lastLine: { + width: '100%', + height: 1, + backgroundColor: 'black', + }, + bottomLabel: { + width: '100%', + }, + customDataPointContainer: { + position: 'absolute', + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/src/components/charts/LineChart/types.ts b/src/components/charts/LineChart/types.ts new file mode 100644 index 00000000..2aba241d --- /dev/null +++ b/src/components/charts/LineChart/types.ts @@ -0,0 +1,381 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import { ColorValue } from 'react-native'; +import { yAxisSides } from '../utils/constants'; +import { + CurveType, + RuleType, + arrowConfigType, + secondaryLineConfigType, + secondaryYAxisType, +} from '../utils/types'; + +export type LineChartPropsType = { + height?: number; + overflowTop?: number; + overflowBottom?: number; + noOfSections?: number; + maxValue?: number; + minValue?: number; + stepHeight?: number; + stepValue?: number; + spacing?: number; + initialSpacing?: number; + endSpacing?: number; + data?: Array; + data2?: Array; + data3?: Array; + data4?: Array; + data5?: Array; + zIndex1?: number; + zIndex2?: number; + zIndex3?: number; + zIndex4?: number; + zIndex5?: number; + thickness?: number; + thickness1?: number; + thickness2?: number; + thickness3?: number; + thickness4?: number; + thickness5?: number; + strokeDashArray?: Array; + strokeDashArray1?: Array; + strokeDashArray2?: Array; + strokeDashArray3?: Array; + strokeDashArray4?: Array; + strokeDashArray5?: Array; + rotateLabel?: boolean; + isAnimated?: boolean; + animateOnDataChange?: boolean; + animationDuration?: number; + onDataChangeAnimationDuration?: number; + animationEasing?: any; + animateTogether?: boolean; + xAxisLength?: number; + xAxisThickness?: number; + xAxisColor?: ColorValue; + xAxisType?: RuleType; + hideRules?: boolean; + rulesLength?: number; + rulesColor?: ColorValue; + rulesThickness?: number; + focusEnabled?: boolean; + onFocus?: Function; + showDataPointOnFocus?: boolean; + showStripOnFocus?: boolean; + showTextOnFocus?: boolean; + stripHeight?: number; + stripWidth?: number; + stripColor?: ColorValue | String | any; + stripOpacity?: number; + onPress?: Function; + unFocusOnPressOut?: boolean; + delayBeforeUnFocus?: number; + showValuesAsDataPointsText?: boolean; + + rulesType?: RuleType; + dashWidth?: number; + dashGap?: number; + showReferenceLine1?: boolean; + referenceLine1Config?: referenceConfigType; + referenceLine1Position?: number; + showReferenceLine2?: boolean; + referenceLine2Config?: referenceConfigType; + referenceLine2Position?: number; + showReferenceLine3?: boolean; + referenceLine3Config?: referenceConfigType; + referenceLine3Position?: number; + + showVerticalLines?: boolean; + verticalLinesUptoDataPoint?: boolean; + verticalLinesThickness?: number; + verticalLinesHeight?: number; + verticalLinesColor?: ColorValue; + verticalLinesType?: string; + verticalLinesShift?: number; + verticalLinesZIndex?: number; + noOfVerticalLines?: number; + verticalLinesSpacing?: number; + hideAxesAndRules?: boolean; + areaChart?: boolean; + areaChart1?: boolean; + areaChart2?: boolean; + areaChart3?: boolean; + areaChart4?: boolean; + areaChart5?: boolean; + + disableScroll?: boolean; + pointerConfig?: Pointer; + showScrollIndicator?: boolean; + indicatorColor?: 'black' | 'default' | 'white'; + + //Indices + + showYAxisIndices?: boolean; + showXAxisIndices?: boolean; + yAxisIndicesHeight?: number; + xAxisIndicesHeight?: number; + yAxisIndicesWidth?: number; + xAxisIndicesWidth?: number; + xAxisIndicesColor?: ColorValue; + yAxisIndicesColor?: ColorValue; + yAxisSide?: yAxisSides; + yAxisOffset?: number; + + startIndex?: number; + startIndex1?: number; + startIndex2?: number; + startIndex3?: number; + startIndex4?: number; + startIndex5?: number; + endIndex?: number; + endIndex1?: number; + endIndex2?: number; + endIndex3?: number; + endIndex4?: number; + endIndex5?: number; + + color?: string; + color1?: string; + color2?: string; + color3?: string; + color4?: string; + color5?: string; + yAxisThickness?: number; + yAxisColor?: ColorValue; + yAxisLabelContainerStyle?: any; + horizontalRulesStyle?: any; + yAxisTextStyle?: any; + yAxisTextNumberOfLines?: number; + xAxisTextNumberOfLines?: number; + showFractionalValues?: boolean; + roundToDigits?: number; + yAxisLabelWidth?: number; + hideYAxisText?: boolean; + + backgroundColor?: ColorValue; + curved?: boolean; + curvature?: number; + curveType?: CurveType; + horizSections?: Array; + + //Data points + + hideDataPoints?: boolean; + dataPointsHeight?: number; + dataPointsWidth?: number; + dataPointsRadius?: number; + dataPointsColor?: string; + dataPointsShape?: string; + hideDataPoints1?: boolean; + dataPointsHeight1?: number; + dataPointsWidth1?: number; + dataPointsRadius1?: number; + dataPointsColor1?: string; + dataPointsShape1?: string; + hideDataPoints2?: boolean; + dataPointsHeight2?: number; + dataPointsWidth2?: number; + dataPointsRadius2?: number; + dataPointsColor2?: string; + dataPointsShape2?: string; + hideDataPoints3?: boolean; + dataPointsHeight3?: number; + dataPointsWidth3?: number; + dataPointsRadius3?: number; + dataPointsColor3?: string; + dataPointsShape3?: string; + hideDataPoints4?: boolean; + dataPointsHeight4?: number; + dataPointsWidth4?: number; + dataPointsRadius4?: number; + dataPointsColor4?: string; + dataPointsShape4?: string; + hideDataPoints5?: boolean; + dataPointsHeight5?: number; + dataPointsWidth5?: number; + dataPointsRadius5?: number; + dataPointsColor5?: string; + dataPointsShape5?: string; + customDataPoint?: Function; + + focusedDataPointShape?: String; + focusedDataPointWidth?: number; + focusedDataPointHeight?: number; + focusedDataPointColor?: ColorValue | String | any; + focusedDataPointRadius?: number; + focusedCustomDataPoint?: Function; + dataPointLabelWidth?: number; + dataPointLabelShiftX?: number; + dataPointLabelShiftY?: number; + + startFillColor?: string; + endFillColor?: string; + startOpacity?: number; + endOpacity?: number; + startFillColor1?: string; + endFillColor1?: string; + startOpacity1?: number; + endOpacity1?: number; + startFillColor2?: string; + endFillColor2?: string; + startOpacity2?: number; + endOpacity2?: number; + startFillColor3?: string; + endFillColor3?: string; + startOpacity3?: number; + endOpacity3?: number; + startFillColor4?: string; + endFillColor4?: string; + startOpacity4?: number; + endOpacity4?: number; + startFillColor5?: string; + endFillColor5?: string; + startOpacity5?: number; + endOpacity5?: number; + gradientDirection?: string; + + textFontSize?: number; + textColor?: string; + textFontSize1?: number; + textColor1?: string; + textFontSize2?: number; + textColor2?: string; + textFontSize3?: number; + textColor3?: string; + textFontSize4?: number; + textColor4?: string; + textFontSize5?: number; + textColor5?: string; + hideOrigin?: boolean; + textShiftX?: number; + textShiftY?: number; + yAxisLabelTexts?: Array; + xAxisLabelTexts?: Array; + xAxisLabelTextStyle?: any; + width?: number; + yAxisLabelPrefix?: String; + yAxisLabelSuffix?: String; + scrollRef?: any; + scrollToEnd?: boolean; + scrollToIndex?: number; + scrollAnimation?: boolean; + noOfSectionsBelowXAxis?: number; + labelsExtraHeight?: number; + adjustToWidth?: boolean; + getPointerProps?: Function; + showArrows?: boolean; + arrowConfig?: arrowConfigType; + showArrow1?: boolean; + arrowConfig1?: arrowConfigType; + showArrow2?: boolean; + arrowConfig2?: arrowConfigType; + showArrow3?: boolean; + arrowConfig3?: arrowConfigType; + showArrow4?: boolean; + arrowConfig4?: arrowConfigType; + showArrow5?: boolean; + arrowConfig5?: arrowConfigType; + + secondaryData?: Array; + secondaryYAxis?: secondaryYAxisType; + secondaryLineConfig?: secondaryLineConfigType; +}; + +type referenceConfigType = { + thickness: number; + width: number; + color: ColorValue | String | any; + type: String; + dashWidth: number; + dashGap: number; + labelText: String; + labelTextStyle: any; +}; +export type itemType = { + value: number; + label?: String; + labelComponent?: Function; + labelTextStyle?: any; + dataPointText?: string; + textShiftX?: number; + textShiftY?: number; + textColor?: string; + textFontSize?: number; + + hideDataPoint?: boolean; + dataPointHeight?: number; + dataPointWidth?: number; + dataPointRadius?: number; + dataPointColor?: string; + dataPointShape?: string; + customDataPoint?: Function; + + stripHeight?: number; + stripWidth?: number; + stripColor?: ColorValue | String | any; + stripOpacity?: number; + + focusedDataPointShape?: String; + focusedDataPointWidth?: number; + focusedDataPointHeight?: number; + focusedDataPointColor?: ColorValue | String | any; + focusedDataPointRadius?: number; + focusedCustomDataPoint?: Function; + + dataPointLabelComponent?: Function; + focusedDataPointLabelComponent?: Function; + dataPointLabelWidth?: number; + dataPointLabelShiftX?: number; + dataPointLabelShiftY?: number; + showStrip?: boolean; + + showVerticalLine?: boolean; + verticalLineUptoDataPoint?: boolean; + verticalLineColor?: string; + verticalLineThickness?: number; + pointerShiftX?: number; + pointerShiftY?: number; + onPress?: Function; + showXAxisIndex?: boolean; +}; + +type sectionType = { + value: string; +}; + +type Pointer = { + height?: number; + width?: number; + radius?: number; + pointerColor?: ColorValue; + pointer1Color?: ColorValue; + pointer2Color?: ColorValue; + pointer3Color?: ColorValue; + pointer4Color?: ColorValue; + pointer5Color?: ColorValue; + pointerComponent?: Function; + showPointerStrip?: boolean; + pointerStripWidth?: number; + pointerStripHeight?: number; + pointerStripColor?: ColorValue; + pointerStripUptoDataPoint?: boolean; + pointerLabelComponent?: Function; + stripOverPointer?: boolean; + autoAdjustPointerLabelPosition?: boolean; + shiftPointerLabelX?: number; + shiftPointerLabelY?: number; + pointerLabelWidth?: number; + pointerLabelHeight?: number; + pointerVanishDelay?: number; + activatePointersOnLongPress?: boolean; + activatePointersDelay?: number; + hidePointer1?: boolean; + hidePointer2?: boolean; + hidePointer3?: boolean; + hidePointer4?: boolean; + hidePointer5?: boolean; + strokeDashArray?: Array; +}; diff --git a/src/components/charts/PieChart/index.tsx b/src/components/charts/PieChart/index.tsx new file mode 100644 index 00000000..f502c7e2 --- /dev/null +++ b/src/components/charts/PieChart/index.tsx @@ -0,0 +1,154 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React, { useState } from 'react'; +import { View, ColorValue } from 'react-native'; +import { PieChartMain } from './main'; +import { FontStyle } from 'react-native-svg'; +import { pieColors } from '../utils/constants'; + +type propTypes = { + radius?: number; + isThreeD?: boolean; + donut?: boolean; + innerRadius?: number; + shadow?: boolean; + innerCircleColor?: ColorValue; + innerCircleBorderWidth?: number; + innerCircleBorderColor?: ColorValue; + shiftInnerCenterX?: number; + shiftInnerCenterY?: number; + shadowColor?: string; + shadowWidth?: number; + strokeWidth?: number; + strokeColor?: string; + backgroundColor?: string; + data: Array; + semiCircle?: boolean; + + showText?: boolean; + textColor?: string; + textSize?: number; + fontStyle?: FontStyle; + fontWeight?: string; + font?: string; + showTextBackground?: boolean; + textBackgroundColor?: string; + textBackgroundRadius?: number; + showValuesAsLabels?: boolean; + + centerLabelComponent?: Function; + tiltAngle?: string; + initialAngle?: number; + labelsPosition?: 'onBorder' | 'outward' | 'inward' | 'mid'; + showGradient?: boolean; + gradientCenterColor?: string; + onPress?: Function; + focusOnPress?: boolean; + toggleFocusOnPress?: boolean; + selectedIndex?: number; + setSelectedIndex?: Function; + sectionAutoFocus?: boolean; + onLabelPress?: Function; + extraRadiusForFocused?: number; +}; +type itemType = { + value: number; + shiftX?: number; + shiftY?: number; + color?: string; + gradientCenterColor?: string; + text?: string; + textColor?: string; + textSize?: number; + fontStyle?: FontStyle; + fontWeight?: string; + font?: string; + textBackgroundColor?: string; + textBackgroundRadius?: number; + shiftTextX?: number; + shiftTextY?: number; + labelPosition?: 'onBorder' | 'outward' | 'inward' | 'mid'; + onPress?: Function; + onLabelPress?: Function; + strokeWidth?: number; + strokeColor?: string; + focused?: boolean; +}; + +export const PieChart = (props: propTypes) => { + const radius = props.radius || 120; + const extraRadiusForFocused = props.extraRadiusForFocused || radius / 10; + const pi = props.semiCircle ? Math.PI / 2 : Math.PI; + const [selectedIndex, setSelectedIndex] = useState( + props.data.findIndex((item) => item.focused === true), + ); + let startAngle = props.initialAngle || (props.semiCircle ? -pi : 0); + let total = 0; + props.data.forEach((item) => { + total += item.value; + }); + if (selectedIndex !== 0) { + let start = 0; + for (let i = 0; i < selectedIndex; i++) { + start += props.data[i].value; + } + startAngle += (2 * pi * start) / total; + } + return ( + + {!( + props.data.length <= 1 || + !(props.focusOnPress || props.sectionAutoFocus) || + selectedIndex === -1 + ) && ( + + + + )} + + + + + ); +}; diff --git a/src/components/charts/PieChart/main.tsx b/src/components/charts/PieChart/main.tsx new file mode 100644 index 00000000..b57e54e0 --- /dev/null +++ b/src/components/charts/PieChart/main.tsx @@ -0,0 +1,507 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import React from 'react'; +import { ColorValue, View } from 'react-native'; +import Svg, { + Path, + Circle, + Text as SvgText, + FontStyle, + Defs, + RadialGradient, + Stop, +} from 'react-native-svg'; +import { pieColors } from '../utils/constants'; + +type propTypes = { + radius?: number; + isThreeD?: boolean; + donut?: boolean; + innerRadius?: number; + shadow?: boolean; + innerCircleColor?: ColorValue; + innerCircleBorderWidth?: number; + innerCircleBorderColor?: ColorValue; + shiftInnerCenterX?: number; + shiftInnerCenterY?: number; + shadowColor?: string; + shadowWidth?: number; + strokeWidth?: number; + strokeColor?: string; + backgroundColor?: string; + data: Array; + semiCircle?: boolean; + + showText?: boolean; + textColor?: string; + textSize?: number; + fontStyle?: FontStyle; + fontWeight?: string; + font?: string; + showTextBackground?: boolean; + textBackgroundColor?: string; + textBackgroundRadius?: number; + showValuesAsLabels?: boolean; + + centerLabelComponent?: Function; + tiltAngle?: string; + initialAngle?: number; + labelsPosition?: 'onBorder' | 'outward' | 'inward' | 'mid'; + showGradient?: boolean; + gradientCenterColor?: string; + onPress?: Function; + focusOnPress?: boolean; + toggleFocusOnPress?: boolean; + selectedIndex?: number; + setSelectedIndex: Function; + onLabelPress?: Function; + isBiggerPie?: boolean; +}; +type itemType = { + value: number; + shiftX?: number; + shiftY?: number; + color?: string; + gradientCenterColor?: string; + text?: string; + textColor?: string; + textSize?: number; + fontStyle?: FontStyle; + fontWeight?: string; + font?: string; + textBackgroundColor?: string; + textBackgroundRadius?: number; + shiftTextX?: number; + shiftTextY?: number; + labelPosition?: 'onBorder' | 'outward' | 'inward' | 'mid'; + onPress?: Function; + onLabelPress?: Function; + strokeWidth?: number; + strokeColor?: string; + peripheral?: boolean; +}; + +export const PieChartMain = (props: propTypes) => { + const { isThreeD } = props; + const propData = props.data; + const data: Array = []; + if (propData) { + for (let i = 0; i < propData.length; i++) { + if (propData[i].value !== 0) { + data.push(propData[i]); + } + } + } + const radius = props.radius || 120; + const canvasWidth = radius * 2; + const canvasHeight = isThreeD ? radius * 2.3 : radius * 2; + const shadowWidth = props.shadowWidth || radius / 5; + const backgroundColor = props.backgroundColor || 'transparent'; + const shadowColor = props.shadowColor || 'lightgray'; + const semiCircle = props.semiCircle || false; + let pi = Math.PI; + const initialAngle = props.initialAngle || (semiCircle ? pi / -2 : 0); + const shadow = props.shadow || false; + const donut = props.donut || false; + const strokeWidth = props.strokeWidth || 0; + const strokeColor = props.strokeColor || (strokeWidth ? 'gray' : 'transparent'); + const innerRadius = props.innerRadius || radius / 2.5; + const innerCircleColor = props.innerCircleColor || props.backgroundColor || 'white'; + const innerCircleBorderWidth = + props.innerCircleBorderWidth || (props.innerCircleBorderColor ? strokeWidth || 2 : 0); + const innerCircleBorderColor = props.innerCircleBorderColor || 'lightgray'; + const shiftInnerCenterX = props.shiftInnerCenterX || 0; + const shiftInnerCenterY = props.shiftInnerCenterY || 0; + + const showText = props.showText || false; + const textColor = props.textColor || ''; + const textSize = props.textSize ? Math.min(props.textSize, radius / 5) : 16; + let tiltAngle = props.tiltAngle || '55deg'; + if (tiltAngle && !isNaN(Number(tiltAngle)) && !(tiltAngle + '').endsWith('deg')) { + tiltAngle += 'deg'; + } + // const tilt = props.tilt ? Math.min(props.tilt, 1) : props.isThreeD ? 0.5 : 1; + const labelsPosition = props.labelsPosition + ? props.labelsPosition + : donut || props.centerLabelComponent + ? 'outward' + : 'mid'; + + const showTextBackground = props.showTextBackground || false; + const textBackgroundColor = props.textBackgroundColor || 'white'; + const showValuesAsLabels = props.showValuesAsLabels || false; + const showGradient = props.showGradient || false; + const gradientCenterColor = props.gradientCenterColor || 'white'; + const toggleFocusOnPress = props.toggleFocusOnPress === false ? false : true; + + let isDataShifted = false; + let minShiftX = 0, + maxShiftX = 0, + minShiftY = 0, + maxShiftY = 0, + total = 0; + + data.forEach((item: any) => { + total += item.value; + if (item.shiftX || item.shiftY) { + isDataShifted = true; + if (minShiftX > item.shiftX) { + minShiftX = item.shiftX; + } + if (minShiftY > item.shiftY) { + minShiftY = item.shiftY; + } + if (maxShiftX < item.shiftX) { + maxShiftX = item.shiftX; + } + if (maxShiftY < item.shiftY) { + maxShiftY = item.shiftY; + } + } + }); + + const horizAdjustment = maxShiftX - minShiftX; + const vertAdjustment = maxShiftY - minShiftY; + + if (semiCircle) { + pi = Math.PI / 2; + } + + const cx = radius, + cy = radius; + + total = data && data.length ? data.map((item) => item.value).reduce((v, a) => v + a) : 0; + let acc = 0; + let pData = data.map((item) => { + acc += item.value / total; + return acc; + }); + acc = 0; + const mData = data.map((item) => { + const pAcc = acc; + acc += item.value / total; + return pAcc + (acc - pAcc) / 2; + }); + pData = [0, ...pData]; + + return ( + + + + {data.map((item, index) => { + return ( + + + + + ); + })} + + {data.length === 1 ? ( + <> + { + data[0].onPress + ? data[0].onPress() + : props.onPress + ? props.onPress(data[0], 0) + : null; + }} + /> + + ) : ( + data.map((item, index) => { + // console.log('index', index); + let nextItem; + if (index === pData.length - 1) nextItem = pData[0]; + else nextItem = pData[index + 1]; + const sx = + cx * (1 + Math.sin(2 * pi * pData[index] + initialAngle)) + + (item.shiftX || 0); + const sy = + cy * (1 - Math.cos(2 * pi * pData[index] + initialAngle)) + + (item.shiftY || 0); + const ax = + cx * (1 + Math.sin(2 * pi * nextItem + initialAngle)) + + (item.shiftX || 0); + const ay = + cy * (1 - Math.cos(2 * pi * nextItem + initialAngle)) + + (item.shiftY || 0); + + return ( + total / 2 ? 1 : 0 + } 1 ${ax} ${ay} L ${cx + (item.shiftX || 0)} ${ + cy + (item.shiftY || 0) + }`} + stroke={item.strokeColor || strokeColor} + strokeWidth={ + props.focusOnPress && props.selectedIndex === index + ? 0 + : item.strokeWidth === 0 + ? 0 + : item.strokeWidth || strokeWidth + } + fill={ + props.selectedIndex === index || item.peripheral + ? 'transparent' + : showGradient + ? `url(#grad${index})` + : item.color || pieColors[index % 9] + } + onPress={() => { + if (item.onPress) { + item.onPress(); + } else if (props.onPress) { + props.onPress(item, index); + } + if (props.focusOnPress) { + if (props.selectedIndex === index || props.isBiggerPie) { + if (toggleFocusOnPress) { + props.setSelectedIndex(-1); + } + } else { + props.setSelectedIndex(index); + } + } + }} + /> + ); + }) + )} + + {showText && + data.map((item, index) => { + const mx = cx * (1 + Math.sin(2 * pi * mData[index] + initialAngle)); + const my = cy * (1 - Math.cos(2 * pi * mData[index] + initialAngle)); + + const midx = (mx + cx) / 2; + const midy = (my + cy) / 2; + + let x = midx, + y = midy; + + const labelPosition = item.labelPosition || labelsPosition; + + if (labelPosition === 'onBorder') { + x = mx; + y = my; + } else if (labelPosition === 'outward') { + x = (midx + mx) / 2; + y = (midy + my) / 2; + } else if (labelPosition === 'inward') { + x = (midx + cx) / 2; + y = (midy + cy) / 2; + } + + x += item.shiftX || 0; + y += item.shiftY || 0; + + if (data.length === 1) { + if (donut) { + y = + (radius - + innerRadius + + (item.textBackgroundRadius || + props.textBackgroundRadius || + item.textSize || + textSize)) / + 2; + } else { + y = cy; + } + } + + // console.log('sx', sx); + // console.log('sy', sy); + // console.log('ax', ax); + // console.log('ay', ay); + return ( + + {/* */} + {showTextBackground && ( + { + item.onLabelPress + ? item.onLabelPress() + : props.onLabelPress + ? props.onLabelPress(item, index) + : item.onPress + ? item.onPress() + : props.onPress + ? props.onPress(item, index) + : null; + if (props.focusOnPress) { + if (props.selectedIndex === index) { + if (toggleFocusOnPress) { + props.setSelectedIndex(-1); + } + } else { + props.setSelectedIndex(index); + } + } + }} + /> + )} + { + item.onLabelPress + ? item.onLabelPress() + : props.onLabelPress + ? props.onLabelPress(item, index) + : item.onPress + ? item.onPress() + : props.onPress + ? props.onPress(item, index) + : null; + if (props.focusOnPress) { + if (props.selectedIndex === index) { + if (toggleFocusOnPress) { + props.setSelectedIndex(-1); + } + } else { + props.setSelectedIndex(index); + } + } + }} + > + {item.text || (showValuesAsLabels ? item.value + '' : '')} + + + ); + })} + + {(props.centerLabelComponent || (donut && !isDataShifted)) && ( + + + {props.centerLabelComponent ? props.centerLabelComponent() : null} + + + )} + {isThreeD && shadow && !semiCircle ? ( + + ) : null} + + ); +}; diff --git a/src/components/charts/utils/constants.ts b/src/components/charts/utils/constants.ts new file mode 100644 index 00000000..bbcfdff2 --- /dev/null +++ b/src/components/charts/utils/constants.ts @@ -0,0 +1,237 @@ +import {defaultLineConfigType} from '../BarChart/types'; +import {CurveType} from './types'; + +// Global + +export enum chartTypes { + BAR, + LINE, + LINE_BI_COLOR, +} + +const defaultCurvature = 0.2; +const defaultCurveType = CurveType.CUBIC; +const defaultAnimationDuration = 800; + +// Bar and Line chart Specific + +export enum yAxisSides { + LEFT, + RIGHT, +} + +export const ruleTypes = { + SOLID: 'solid', + DASHED: 'dashed', + DOTTED: 'dotted', +}; + +export const AxesAndRulesDefaults = { + yAxisSide: yAxisSides.LEFT, + yAxisColor: 'black', + yAxisThickness: 1, + xAxisColor: 'black', + xAxisThickness: 1, + xAxisType: ruleTypes.SOLID, + xAxisTextNumberOfLines: 1, + dashWidth: 4, + dashGap: 8, + backgroundColor: 'transparent', + + hideRules: false, + rulesType: ruleTypes.DASHED, + rulesThickness: 1, + rulesColor: 'lightgray', + + rotateLabel: false, + + showYAxisIndices: false, + yAxisIndicesHeight: 2, + yAxisIndicesWidth: 4, + yAxisIndicesColor: 'black', + + showXAxisIndices: false, + xAxisIndicesHeight: 2, + xAxisIndicesWidth: 4, + xAxisIndicesColor: 'black', + + hideOrigin: false, + hideYAxisText: false, + yAxisTextNumberOfLines: 1, + + showVerticalLines: false, + verticalLinesThickness: 1, + verticalLinesColor: 'lightgray', + verticalLinesType: 'solid', + verticalLinesShift: 0, + verticalLinesZIndex: -1, + verticalLinesSpacing: 0, + verticalLinesUptoDataPoint: false, + + noOfSections: 10, + containerHeight: 200, + width: 200, + + labelWidth: 0, + labelsExtraHeight: 0, + + yAxisLabelWidth: 35, + yAxisEmptyLabelWidth: 10, + + showFractionalValues: false, + roundToDigits: 1, +}; + +export const defaultArrowConfig = { + length: 10, + width: 10, + strokeWidth: 1, + strokeColor: 'black', + fillColor: 'none', + showArrowBase: true, +}; + +// Bar chart specific + +export const BarDefaults = { + barWidth: 30, + spacing: 20, + capThickness: 6, + capColor: 'gray', + capRadius: 0, + + horizontal: false, + rtl: false, + labelsWidthForHorizontal: 30, + yAxisAtTop: false, + rotateYAxisTexts: undefined, + intactTopLabel: false, + + showLine: false, + lineBehindBars: false, + + disableScroll: false, + scrollToEnd: false, + scrollAnimation: true, + showScrollIndicator: false, + + side: '', + isAnimated: false, + animationDuration: 800, + opacity: 1, + isThreeD: false, +}; + +export const defaultLineConfig: defaultLineConfigType = { + initialSpacing: BarDefaults.spacing, // gets updated to spacing before being used + curved: false, + curvature: defaultCurvature, + curveType: defaultCurveType, + isAnimated: false, + animationDuration: defaultAnimationDuration, + thickness: 1, + color: 'black', + hideDataPoints: false, + dataPointsShape: 'circular', + dataPointsWidth: 4, + dataPointsHeight: 4, + dataPointsColor: 'black', + dataPointsRadius: 3, + textColor: 'gray', + textFontSize: 10, + textShiftX: 0, + textShiftY: 0, + shiftX: 0, + shiftY: 0, + delay: 0, + startIndex: 0, + endIndex: 0, // gets updated to lineData.length - 1 + showArrow: false, + arrowConfig: defaultArrowConfig, +}; + +// Line chart specific + +export const LineDefaults = { + color: 'black', + curvature: defaultCurvature, + curveType: defaultCurveType, + thickness: 2, + isAnimated: false, + hideDataPoints: false, + spacing: 50, + initialSpacing: 20, + endSpacing: 20, + animationDuration: defaultAnimationDuration, + animateTogether: false, + disableScroll: false, + scrollToEnd: false, + scrollAnimation: true, + showScrollIndicator: false, + showValuesAsDataPointsText: false, + + dataPointsHeight: 4, + dataPointsWidth: 4, + dataPointsRadius: 3, + dataPointsColor: 'black', + dataPointsColor2: 'blue', + dataPointsColor3: 'red', + dataPointsShape: 'circular', + + textFontSize: 10, + textColor: 'gray', + + startFillColor: 'gray', + endFillColor: 'white', + startOpacity: 1, + endOpacity: 1, + + focusEnabled: false, + showDataPointOnFocus: false, + showStripOnFocus: false, + showTextOnFocus: false, + stripWidth: 2, + unFocusOnPressOut: true, + delayBeforeUnFocus: 300, +}; + +export const defaultPointerConfig = { + height: 0, + width: 0, + radius: 5, + pointerColor: 'red', + pointerComponent: null, + showPointerStrip: true, + pointerStripHeight: AxesAndRulesDefaults.containerHeight, // gets updated to actual containerHeight + pointerStripWidth: 1, + pointerStripColor: 'black', + pointerStripUptoDataPoint: false, + pointerLabelComponent: null, + stripOverPointer: false, + shiftPointerLabelX: 0, + shiftPointerLabelY: 0, + pointerLabelWidth: 20, + pointerLabelHeight: 20, + autoAdjustPointerLabelPosition: false, + pointerVanishDelay: 150, + activatePointersOnLongPress: false, + activatePointersDelay: 150, + hidePointer1: false, + hidePointer2: false, + hidePointer3: false, + hidePointer4: false, + hidePointer5: false, +}; + +// Pie chart specific + +export const pieColors = [ + 'cyan', + 'green', + 'orange', + 'purple', + '#bbff00', + 'red', + 'blue', + 'pink', +]; diff --git a/src/components/charts/utils/index.tsx b/src/components/charts/utils/index.tsx new file mode 100644 index 00000000..5836677d --- /dev/null +++ b/src/components/charts/utils/index.tsx @@ -0,0 +1,453 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck +import { arrowConfigType, CurveType } from './types'; + +export const getCumulativeWidth = (data: any, index: number, spacing: number) => { + let cumWidth = 0; + for (let i = 0; i < index; i++) { + let { barWidth } = data[i]; + barWidth = barWidth || 30; + cumWidth += barWidth + (spacing ?? 20); + } + return cumWidth; +}; + +export const getLighterColor = (color: String) => { + let r, + g, + b, + lighter = '#'; + if (color.startsWith('#')) { + if (color.length < 7) { + r = parseInt(color[1], 16); + g = parseInt(color[2], 16); + b = parseInt(color[3], 16); + // console.log('r', r); + // console.log('g', g); + // console.log('b', b); + if (r < 14) { + r += 2; + lighter += r.toString(16); + } + if (g < 14) { + g += 2; + lighter += g.toString(16); + } + if (b < 14) { + b += 2; + lighter += b.toString(16); + } + // console.log('lighter', lighter); + } else { + r = parseInt(color[1] + color[2], 16); + g = parseInt(color[3] + color[4], 16); + b = parseInt(color[5] + color[6], 16); + // console.log('r', r); + // console.log('g', g); + // console.log('b', b); + + if (r < 224) { + r += 32; + lighter += r.toString(16); + } + if (g < 224) { + g += 32; + lighter += g.toString(16); + } + if (b < 224) { + b += 32; + lighter += b.toString(16); + } + // console.log('lighter', lighter); + } + } + return lighter; +}; + +export const svgQuadraticCurvePath = (points) => { + let path = 'M' + points[0][0] + ',' + points[0][1]; + + for (let i = 0; i < points.length - 1; i++) { + const xMid = (points[i][0] + points[i + 1][0]) / 2; + const yMid = (points[i][1] + points[i + 1][1]) / 2; + const cpX1 = (xMid + points[i][0]) / 2; + const cpX2 = (xMid + points[i + 1][0]) / 2; + path += + 'Q ' + + cpX1 + + ', ' + + points[i][1] + + ', ' + + xMid + + ', ' + + yMid + + (' Q ' + + cpX2 + + ', ' + + points[i + 1][1] + + ', ' + + points[i + 1][0] + + ', ' + + points[i + 1][1]); + } + + return path; +}; + +export const svgPath = (points: Array>, curveType: CurveType, curvature: number) => { + if (!points?.length) return ''; + if (curveType === CurveType.QUADRATIC) { + return svgQuadraticCurvePath(points); + } + // build the d attributes by looping over the points + const d = points.reduce( + (acc, point, i, a) => + i === 0 + ? // if first point + `M ${point[0]},${point[1]}` + : // else + `${acc} ${bezierCommand(point, i, a, curvature)}`, + '', + ); + return d; +}; + +const line = (pointA: Array, pointB: Array) => { + const lengthX = pointB[0] - pointA[0]; + const lengthY = pointB[1] - pointA[1]; + return { + length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)), + angle: Math.atan2(lengthY, lengthX), + }; +}; + +const controlPoint = ( + curvature: number, + current: Array, + previous: Array, + next: Array, + reverse?: any, +) => { + // When 'current' is the first or last point of the array + // 'previous' or 'next' don't exist. + // Replace with 'current' + const p = previous || current; + const n = next || current; + // The smoothing ratio + const smoothing = curvature; + // Properties of the opposed-line + const o = line(p, n); + // If is end-control-point, add PI to the angle to go backward + const angle = o.angle + (reverse ? Math.PI : 0); + const length = o.length * smoothing; + // The control point position is relative to the current point + const x = current[0] + Math.cos(angle) * length; + const y = current[1] + Math.sin(angle) * length; + return [x, y]; +}; + +export const bezierCommand = ( + point: Array, + i: number, + a: Array>, + curvature: number, +) => { + // start control point + const [cpsX, cpsY] = controlPoint(curvature, a[i - 1], a[i - 2], point); + // end control point + const [cpeX, cpeY] = controlPoint(curvature, point, a[i - 1], a[i + 1], true); + return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point[0]},${point[1]}`; +}; + +export const getArrowPoints = ( + arrowTipX, + arrowTipY, + x1, + y1, + arrowLength, + arrowWidth, + showArrowBase, +) => { + const dataLineSlope = (arrowTipY - y1) / (arrowTipX - x1); + const d = arrowLength; + const d2 = arrowWidth / 2; + const interSectionX = arrowTipX - Math.sqrt((d * d) / (dataLineSlope * dataLineSlope + 1)); + const interSectionY = arrowTipY - dataLineSlope * (arrowTipX - interSectionX); + + let arrowBasex1, arrowBaseY1, arrowBaseX2, arrowBaseY2; + if (dataLineSlope === 0) { + arrowBasex1 = interSectionX; + arrowBaseY1 = interSectionY - d2; + arrowBaseX2 = interSectionX; + arrowBaseY2 = interSectionY + d2; + } else { + const arrowBaseSlope = -1 / dataLineSlope; + arrowBasex1 = interSectionX - Math.sqrt((d2 * d2) / (arrowBaseSlope * arrowBaseSlope + 1)); + arrowBaseY1 = interSectionY - arrowBaseSlope * (interSectionX - arrowBasex1); + + arrowBaseX2 = interSectionX + Math.sqrt((d2 * d2) / (arrowBaseSlope * arrowBaseSlope + 1)); + arrowBaseY2 = interSectionY + arrowBaseSlope * (interSectionX - arrowBasex1); + } + let arrowPoints = ` M${interSectionX} ${interSectionY}`; + arrowPoints += ` ${showArrowBase ? 'L' : 'M'}${arrowBasex1} ${arrowBaseY1}`; + arrowPoints += ` L${arrowTipX} ${arrowTipY}`; + arrowPoints += ` M${interSectionX} ${interSectionY}`; + arrowPoints += ` ${showArrowBase ? 'L' : 'M'}${arrowBaseX2} ${arrowBaseY2}`; + arrowPoints += ` L${arrowTipX} ${arrowTipY}`; + + return arrowPoints; +}; + +export const getAxesAndRulesProps = (props, stepValue) => { + return { + yAxisSide: props.yAxisSide, + yAxisLabelContainerStyle: props.yAxisLabelContainerStyle, + yAxisColor: props.yAxisColor, + yAxisThickness: props.yAxisThickness, + xAxisColor: props.xAxisColor, + xAxisLength: props.xAxisLength, + xAxisType: props.xAxisType, + dashWidth: props.dashWidth, + dashGap: props.dashGap, + backgroundColor: props.backgroundColor, + hideRules: props.hideRules, + rulesLength: props.rulesLength, + rulesType: props.rulesType, + rulesThickness: props.rulesThickness, + rulesColor: props.rulesColor, + showYAxisIndices: props.showYAxisIndices, + yAxisIndicesHeight: props.yAxisIndicesHeight, + yAxisIndicesWidth: props.yAxisIndicesWidth, + yAxisIndicesColor: props.yAxisIndicesColor, + hideOrigin: props.hideOrigin, + hideYAxisText: props.hideYAxisText, + yAxisTextNumberOfLines: props.yAxisTextNumberOfLines, + yAxisLabelPrefix: props.yAxisLabelPrefix, + yAxisLabelSuffix: props.yAxisLabelSuffix, + yAxisTextStyle: props.yAxisTextStyle, + + referenceLinesConfig: { + showReferenceLine1: props.showReferenceLine1, + referenceLine1Position: props.referenceLine1Position, + referenceLine1Config: props.referenceLine1Config, + showReferenceLine2: props.showReferenceLine2, + referenceLine2Position: props.referenceLine2Position, + referenceLine2Config: props.referenceLine2Config, + showReferenceLine3: props.showReferenceLine3, + referenceLine3Position: props.referenceLine3Position, + referenceLine3Config: props.referenceLine3Config, + }, + + showVerticalLines: props.showVerticalLines, + verticalLinesThickness: props.verticalLinesThickness, + verticalLinesHeight: props.verticalLinesHeight, + verticalLinesColor: props.verticalLinesColor, + verticalLinesType: props.verticalLinesType, + verticalLinesShift: props.verticalLinesShift, + verticalLinesZIndex: props.verticalLinesZIndex, + verticalLinesSpacing: props.verticalLinesSpacing, + noOfVerticalLines: props.noOfVerticalLines, + + //specific to Line charts- + verticalLinesUptoDataPoint: props.verticalLinesUptoDataPoint, + + roundToDigits: props.roundToDigits, + stepValue, + + secondaryYAxis: props.secondaryYAxis, + }; +}; + +export const getExtendedContainerHeightWithPadding = (containerHeight, overflowTop) => + containerHeight + (overflowTop ?? 0) + 10; + +export const getSecondaryDataWithOffsetIncluded = (secondaryData, secondaryYAxis) => { + if (secondaryData && secondaryYAxis?.yAxisOffset) { + return secondaryData?.map((item) => { + item.value = item.value - (secondaryYAxis?.yAxisOffset ?? 0); + return item; + }); + } + return secondaryData; +}; + +export const getArrowProperty = ( + property: string, + count: number, + props: any, + defaultArrowConfig: arrowConfigType, +) => { + return ( + props[`arrowConfig${count}`]?.[`${property}`] ?? + props[`arrowConfig`]?.[`${property}`] ?? + defaultArrowConfig[`${property}`] + ); +}; + +export const getAllArrowProperties = (props: any, defaultArrowConfig: arrowConfigType) => { + const arrowLength1 = getArrowProperty('length', 1, props, defaultArrowConfig); + const arrowWidth1 = getArrowProperty('width', 1, props, defaultArrowConfig); + const arrowStrokeWidth1 = getArrowProperty('strokeWidth', 1, props, defaultArrowConfig); + const arrowStrokeColor1 = getArrowProperty('strokeColor', 1, props, defaultArrowConfig); + const arrowFillColor1 = getArrowProperty('fillColor', 1, props, defaultArrowConfig); + const showArrowBase1 = getArrowProperty('showArrowBase', 1, props, defaultArrowConfig); + + const arrowLength2 = getArrowProperty('length', 2, props, defaultArrowConfig); + const arrowWidth2 = getArrowProperty('width', 2, props, defaultArrowConfig); + const arrowStrokeWidth2 = getArrowProperty('strokeWidth', 2, props, defaultArrowConfig); + const arrowStrokeColor2 = getArrowProperty('strokeColor', 2, props, defaultArrowConfig); + const arrowFillColor2 = getArrowProperty('fillColor', 2, props, defaultArrowConfig); + const showArrowBase2 = getArrowProperty('showArrowBase', 2, props, defaultArrowConfig); + + const arrowLength3 = getArrowProperty('length', 3, props, defaultArrowConfig); + const arrowWidth3 = getArrowProperty('width', 3, props, defaultArrowConfig); + const arrowStrokeWidth3 = getArrowProperty('strokeWidth', 3, props, defaultArrowConfig); + const arrowStrokeColor3 = getArrowProperty('strokeColor', 3, props, defaultArrowConfig); + const arrowFillColor3 = getArrowProperty('fillColor', 3, props, defaultArrowConfig); + const showArrowBase3 = getArrowProperty('showArrowBase', 3, props, defaultArrowConfig); + + const arrowLength4 = getArrowProperty('length', 4, props, defaultArrowConfig); + const arrowWidth4 = getArrowProperty('width', 4, props, defaultArrowConfig); + const arrowStrokeWidth4 = getArrowProperty('strokeWidth', 4, props, defaultArrowConfig); + const arrowStrokeColor4 = getArrowProperty('strokeColor', 4, props, defaultArrowConfig); + const arrowFillColor4 = getArrowProperty('fillColor', 4, props, defaultArrowConfig); + const showArrowBase4 = getArrowProperty('showArrowBase', 4, props, defaultArrowConfig); + + const arrowLength5 = getArrowProperty('length', 5, props, defaultArrowConfig); + const arrowWidth5 = getArrowProperty('width', 5, props, defaultArrowConfig); + const arrowStrokeWidth5 = getArrowProperty('strokeWidth', 5, props, defaultArrowConfig); + const arrowStrokeColor5 = getArrowProperty('strokeColor', 5, props, defaultArrowConfig); + const arrowFillColor5 = getArrowProperty('fillColor', 5, props, defaultArrowConfig); + const showArrowBase5 = getArrowProperty('showArrowBase', 5, props, defaultArrowConfig); + + return { + arrowLength1, + arrowWidth1, + arrowStrokeWidth1, + arrowStrokeColor1, + arrowFillColor1, + showArrowBase1, + arrowLength2, + arrowWidth2, + arrowStrokeWidth2, + arrowStrokeColor2, + arrowFillColor2, + showArrowBase2, + arrowLength3, + arrowWidth3, + arrowStrokeWidth3, + arrowStrokeColor3, + arrowFillColor3, + showArrowBase3, + arrowLength4, + arrowWidth4, + arrowStrokeWidth4, + arrowStrokeColor4, + arrowFillColor4, + showArrowBase4, + arrowLength5, + arrowWidth5, + arrowStrokeWidth5, + arrowStrokeColor5, + arrowFillColor5, + showArrowBase5, + }; +}; + +type MaxAndMin = { + maxItem: number; + minItem: number; +}; + +export const maxAndMinUtil = (maxItem, minItem, roundToDigits, showFractionalValues): MaxAndMin => { + if (showFractionalValues || roundToDigits) { + maxItem *= 10 * (roundToDigits || 1); + maxItem = maxItem + (10 - (maxItem % 10)); + maxItem /= 10 * (roundToDigits || 1); + maxItem = parseFloat(maxItem.toFixed(roundToDigits || 1)); + + if (minItem !== 0) { + minItem *= 10 * (roundToDigits || 1); + minItem = minItem - (10 + (minItem % 10)); + minItem /= 10 * (roundToDigits || 1); + minItem = parseFloat(minItem.toFixed(roundToDigits || 1)); + } + } else { + maxItem = maxItem + (10 - (maxItem % 10)); + if (minItem !== 0) { + minItem = minItem - (10 + (minItem % 10)); + } + } + + return { maxItem, minItem }; +}; + +export const computeMaxAndMinItems = (data, roundToDigits, showFractionalValues): MaxAndMin => { + if (!data) { + return { maxItem: 0, minItem: 0 }; + } + let maxItem = 0, + minItem = 0; + + data.forEach((item: any) => { + if (item.value > maxItem) { + maxItem = item.value; + } + if (item.value < minItem) { + minItem = item.value; + } + }); + + return maxAndMinUtil(maxItem, minItem, roundToDigits, showFractionalValues); +}; + +export const getLabelTextUtil = ( + val, + index, + showFractionalValues, + yAxisLabelTexts, + yAxisOffset, + yAxisLabelPrefix, + yAxisLabelSuffix, +) => { + let label = ''; + if (showFractionalValues || (yAxisLabelTexts && yAxisLabelTexts[index] !== undefined)) { + if (val) { + label = yAxisOffset ? (Number(val) + yAxisOffset).toString() : val; + } else { + label = yAxisOffset ? yAxisOffset.toString() : '0'; + } + } else { + if (val) { + label = val.toString().split('.')[0]; + if (yAxisOffset) { + label = (Number(label) + yAxisOffset).toString(); + } + } else { + label = yAxisOffset ? yAxisOffset.toString() : '0'; + } + } + + return yAxisLabelPrefix + label + yAxisLabelSuffix; +}; + +export const getXForLineInBar = ( + index, + firstBarWidth, + currentBarWidth, + yAxisLabelWidth, + lineConfig, + spacing, +) => + yAxisLabelWidth + + firstBarWidth / 2 + + lineConfig.initialSpacing + + (currentBarWidth + spacing) * index + + lineConfig.shiftX - + lineConfig.dataPointsWidth / 2 - + 32; + +export const getYForLineInBar = (value, shiftY, containerHeight, maxValue) => + containerHeight - shiftY - (value * containerHeight) / maxValue; diff --git a/src/components/charts/utils/types.ts b/src/components/charts/utils/types.ts new file mode 100644 index 00000000..32a70cab --- /dev/null +++ b/src/components/charts/utils/types.ts @@ -0,0 +1,212 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +//@ts-nocheck + +import { ColorValue } from 'react-native'; +import { chartTypes, yAxisSides } from './constants'; + +export type RuleType = 'solid' | 'dashed' | 'dotted'; + +export enum CurveType { + CUBIC, + QUADRATIC, +} + +export type secondaryYAxisType = { + noOfSections?: number; + maxValue?: number; + minValue?: number; + stepValue?: number; + stepHeight?: number; + showFractionalValues?: boolean; + roundToDigits?: number; + + showYAxisIndices?: boolean; + yAxisIndicesHeight?: number; + yAxisIndicesWidth?: number; + yAxisIndicesColor?: ColorValue; + + yAxisSide?: yAxisSides; + yAxisOffset?: number; + yAxisThickness?: number; + yAxisColor?: ColorValue; + yAxisLabelContainerStyle?: any; + yAxisLabelTexts?: Array | undefined; + yAxisTextStyle?: any; + yAxisTextNumberOfLines?: number; + yAxisLabelWidth?: number; + hideYAxisText?: boolean; + yAxisLabelPrefix?: string; + yAxisLabelSuffix?: string; + hideOrigin?: boolean; +}; + +export type secondaryLineConfigType = { + zIndex?: number; + curved?: boolean; + curvature?: number; + curveType?: CurveType; + areaChart?: boolean; + color?: ColorValue; + thickness?: number; + zIndex1?: number; + strokeDashArray?: Array; + startIndex?: number; + endIndex?: number; + hideDataPoints?: boolean; + dataPointsHeight?: number; + dataPointsWidth?: number; + dataPointsRadius?: number; + dataPointsColor?: string; + dataPointsShape?: string; + showValuesAsDataPointsText?: boolean; + startFillColor?: string; + endFillColor?: string; + startOpacity?: number; + endOpacity?: number; + textFontSize?: number; + textColor?: string; + showArrow?: boolean; + arrowConfig?: arrowConfigType; +}; + +export type arrowConfigType = { + length: number; + width: number; + strokeWidth: number; + strokeColor: string; + fillColor: string; + showArrowBase: boolean; +}; + +export type horizSectionPropTypes = { + chartType: chartTypes; + width: number | undefined; + horizSections: Array; + horizSectionsBelow: Array; + totalWidth: number; + endSpacing: number; + yAxisSide: yAxisSides; + horizontalRulesStyle: any; + noOfSections: number; + stepHeight: number; + yAxisLabelWidth: number; + yAxisLabelContainerStyle: any; + yAxisThickness: number; + yAxisColor: string; + xAxisThickness: number; + xAxisColor: string; + xAxisLength: number; + xAxisType: RuleType; + dashWidth: number; + dashGap: number; + backgroundColor: string; + hideRules: boolean; + rulesLength: number; + rulesType: RuleType; + rulesThickness: number; + rulesColor: string; + spacing: number; + showYAxisIndices: boolean; + yAxisIndicesHeight: number; + yAxisIndicesWidth: number; + yAxisIndicesColor: string; + + hideOrigin: boolean; + hideYAxisText: boolean; + showFractionalValues: boolean; + yAxisTextNumberOfLines: number; + yAxisLabelPrefix: string; + yAxisLabelSuffix: string; + yAxisTextStyle: any; + rotateYAxisTexts: number | undefined; + rtl: boolean; + + containerHeight: number; + maxValue: number; + + referenceLinesConfig: any; + + yAxisLabelTexts: Array | undefined; + yAxisOffset: number | undefined; + + horizontal: boolean; + yAxisAtTop: boolean; + + stepValue: number; + roundToDigits: number | undefined; + + secondaryData: Array | undefined; + secondaryYAxis: secondaryYAxisType | null; +}; + +type HorizSectionObject = { + value: string; +}; + +export type HorizSectionsType = Array; + +export type BarAndLineChartsWrapperTypes = { + chartType: chartTypes; + containerHeight: number; + horizSectionsBelow: HorizSectionsType; + stepHeight: number; + labelsExtraHeight: number; + yAxisLabelWidth: number; + horizontal: boolean; + rtl: boolean; + shiftX: number; + shiftY: number; + scrollRef: any; + yAxisAtTop: boolean; + initialSpacing: number; + data: Array; + stackData: Array | undefined; + secondaryData: Array | undefined; + barWidth: number | undefined; + xAxisThickness: number; + totalWidth: number; + disableScroll: boolean; + showScrollIndicator: boolean; + scrollToEnd: boolean; + scrollToIndex: number | undefined; + scrollAnimation: boolean; + indicatorColor: 'black' | 'default' | 'white' | undefined; + setSelectedIndex: any; + spacing: number; + showLine: boolean; + lineConfig: any; + maxValue: number; + lineData: Array; + animatedWidth: any; + lineBehindBars: boolean; + points: string | Array; + arrowPoints: any; + renderChartContent: any; + remainingScrollViewProps: any; + + width: number | undefined; + horizSections: HorizSectionsType; + endSpacing: number; + horizontalRulesStyle: any; + noOfSections: number; + showFractionalValues: boolean; + axesAndRulesProps: any; + + yAxisLabelTexts: Array | undefined; + yAxisOffset: number | undefined; + rotateYAxisTexts: number | undefined; + hideAxesAndRules: boolean | undefined; + + showXAxisIndices: boolean; + xAxisIndicesHeight: number; + xAxisIndicesWidth: number; + xAxisIndicesColor: ColorValue; + + pointerConfig: any; + getPointerProps: any; + pointerIndex: number; + pointerX: number; + pointerY: number; +}; diff --git a/src/components/index.tsx b/src/components/index.tsx index e8ef8c41..272d3287 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -25,6 +25,7 @@ import { Tab } from './tab/Tab'; import { Label } from './label/Label'; import { Product } from './product/Product'; import { Divider } from './divider/Divider'; +import { LineChart } from './charts/LineChart'; export { Body, @@ -54,4 +55,5 @@ export { Label, Product, Divider, + LineChart, }; diff --git a/src/index.tsx b/src/index.tsx index cf0994ad..ec890556 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -26,6 +26,7 @@ import { Label, Product, Divider, + LineChart, } from './components'; import { ThemeProvider, useTheme } from './styles/themes'; @@ -59,4 +60,5 @@ export { Label, Product, Divider, + LineChart, }; diff --git a/tsconfig.json b/tsconfig.json index b25b450d..bc719405 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,6 @@ "allowUnusedLabels": false, "declaration": true, "esModuleInterop": true, - "importsNotUsedAsValues": "error", "forceConsistentCasingInFileNames": true, "jsx": "react", "lib": ["esnext"], @@ -27,5 +26,6 @@ "strict": true, "target": "esnext" }, - "include": ["src", "example", "./.eslintrc.json"] + "include": ["src", "example", "./.eslintrc.json"], + "exclude": ["src/components/charts/*"] }