Merge pull request #80 from Abhinandan-Kushwaha/feat/negative-values

Added support for negative values in Bar Chart
This commit is contained in:
Abhinandan Kushwaha 2022-02-03 03:59:43 +05:30 committed by GitHub
commit 6ce00c6fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 147 additions and 28 deletions

View File

@ -10,7 +10,9 @@
| width | number | Width of the Bar chart | width of the parent |
| height | number | Height of the Bar chart (excluding the bottom label) | 200 |
| maxValue | number | Maximum value shown in the Y axis | 200 |
| minValue | number | Minimum negative value shown in the Y axis (to be used only if the data set has negative values too) | \_ |
| noOfSections | number | Number of sections in the Y axis | 10 |
| noOfSectionsBelowXAxis | number | Number of sections in the Y axis below X axis (in case the data set has negative values too) | 0 |
| stepValue | number | Value of 1 step/section in the Y axis | 20 |
| stepHeight | number | Height of 1 step/section in the Y axis | 20 |
| spacing | number | Distance between 2 consecutive bars in the Bar chart | 20 |
@ -19,6 +21,7 @@
| showScrollIndicator | Boolean | To show horizontal scroll indicator | false |
| showLine | Boolean | To show a Line chart over the Bar chart with the same data | false |
| lineConfig | lineConfigType | Properties of the Line chart shown over the Bar chart (lineConfigType) is described below | defaultLineConfig |
| autoShiftLabels | Boolean | When set to true, automatically shifts the X axis labels for negative values | false |
---

View File

@ -1,6 +1,6 @@
{
"name": "react-native-gifted-charts",
"version": "1.0.0",
"version": "1.0.1",
"description": "The most complete library for Bar, Line, Area, Pie and Donut charts in React Native. Allows 2D, 3D, gradient, animations and live data updates.",
"main": "src/index.tsx",
"files": [

View File

@ -223,12 +223,13 @@ const Animated2DWithGradient = (props: propTypes) => {
height: item.barWidth || 30,
width: item.barWidth || 30,
justifyContent:
props.horizontal && !props.intactTopLabel
(props.horizontal && !props.intactTopLabel) || item.value < 0
? 'center'
: 'flex-end',
alignItems: 'center',
opacity: opacity,
},
item.value < 0 && {transform:[{rotate:'180deg'}]},
props.horizontal &&
!props.intactTopLabel && {transform: [{rotate: '270deg'}]},
item.topLabelContainerStyle,
@ -242,4 +243,4 @@ const Animated2DWithGradient = (props: propTypes) => {
);
};
export default Animated2DWithGradient;
export default Animated2DWithGradient;

View File

@ -57,6 +57,7 @@ type Props = {
horizontal: Boolean;
intactTopLabel: Boolean;
barBorderRadius?: number;
autoShiftLabels?: Boolean;
};
type itemType = {
value?: number;
@ -99,8 +100,9 @@ const RenderBars = (props: Props) => {
appearingOpacity,
opacity,
animationDuration,
autoShiftLabels,
} = props;
const renderLabel = (label: String, labelTextStyle: any) => {
const renderLabel = (label: String, labelTextStyle: any, value: number) => {
return (
<View
style={[
@ -119,10 +121,12 @@ const RenderBars = (props: Props) => {
rotateLabel
? props.horizontal
? {transform: [{rotate: '330deg'}]}
: {transform: [{rotate: '60deg'}]}
: {transform: [{rotate: value < 0 ? '240deg' : '60deg'}, {translateX: value < 0 ? 56 : 0}, {translateY: value < 0 ? 32 : 0}]}
: props.horizontal
? {transform: [{rotate: '-90deg'}]}
: {},
: value < 0
?{transform:[{rotate:'180deg'},{translateY:autoShiftLabels?0:32}]}
:{},
]}>
{item.labelComponent ? (
item.labelComponent()
@ -137,7 +141,7 @@ const RenderBars = (props: Props) => {
);
};
const renderAnimatedLabel = (label: String, labelTextStyle: any) => {
const renderAnimatedLabel = (label: String, labelTextStyle: any, value: number) => {
return (
<Animated.View
style={[
@ -150,9 +154,11 @@ const RenderBars = (props: Props) => {
30) +
spacing / 2,
position: 'absolute',
left: -4,
bottom: rotateLabel ? -40 : -25,
opacity: appearingOpacity,
},
value < 0 && {transform:[{rotate:'180deg'}]},
rotateLabel
? props.horizontal
? {transform: [{rotate: '330deg'}]}
@ -244,11 +250,12 @@ const RenderBars = (props: Props) => {
height: item.barWidth || props.barWidth || 30,
width: item.barWidth || props.barWidth || 30,
justifyContent:
props.horizontal && !props.intactTopLabel
(props.horizontal && !props.intactTopLabel) || item.value < 0
? 'center'
: 'flex-end',
alignItems: 'center',
},
item.value < 0 && {transform:[{rotate:'180deg'}]},
props.horizontal &&
!props.intactTopLabel && {transform: [{rotate: '270deg'}]},
item.topLabelContainerStyle,
@ -270,12 +277,13 @@ const RenderBars = (props: Props) => {
// overflow: 'visible',
marginBottom: 60,
width: item.barWidth || props.barWidth || 30,
height: item.topLabelComponent
height: item.value>=0 &&(!isThreeD || isAnimated) && item.topLabelComponent
? (item.topLabelComponentHeight || 30) +
(item.value * (containerHeight || 200)) / (maxValue || 200)
: (item.value * (containerHeight || 200)) / (maxValue || 200),
(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)
: (Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200),
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},
@ -325,7 +333,7 @@ const RenderBars = (props: Props) => {
topLabelComponent={item.topLabelComponent}
opacity={opacity || 1}
animationDuration={animationDuration || 800}
height={(item.value * (containerHeight || 200)) / (maxValue || 200)}
height={(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)}
intactTopLabel={props.intactTopLabel}
horizontal={props.horizontal}
/>
@ -350,7 +358,8 @@ const RenderBars = (props: Props) => {
opacity={opacity || 1}
horizontal={props.horizontal}
intactTopLabel={props.intactTopLabel}
height={(item.value * (containerHeight || 200)) / (maxValue || 200)}
height={(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)}
value={item.value}
/>
)
) : item.showGradient || props.showGradient ? (
@ -364,7 +373,7 @@ const RenderBars = (props: Props) => {
roundedTop={props.roundedTop || false}
gradientColor={props.gradientColor}
frontColor={props.frontColor || 'black'}
height={(item.value * (containerHeight || 200)) / (maxValue || 200)}
height={(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)}
cappedBars={props.cappedBars}
capThickness={props.capThickness}
capColor={props.capColor}
@ -387,7 +396,7 @@ const RenderBars = (props: Props) => {
gradientColor={props.gradientColor}
noGradient
frontColor={props.frontColor || 'black'}
height={(item.value * (containerHeight || 200)) / (maxValue || 200)}
height={(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)}
cappedBars={props.cappedBars}
capThickness={props.capThickness}
capColor={props.capColor}
@ -408,7 +417,7 @@ const RenderBars = (props: Props) => {
noGradient
noAnimation
frontColor={props.frontColor || 'black'}
height={(item.value * (containerHeight || 200)) / (maxValue || 200)}
height={(Math.abs(item.value) * (containerHeight || 200)) / (maxValue || 200)}
cappedBars={props.cappedBars}
capThickness={props.capThickness}
capColor={props.capColor}
@ -419,10 +428,10 @@ const RenderBars = (props: Props) => {
/>
)}
{isAnimated
? renderAnimatedLabel(item.label || '', item.labelTextStyle)
: renderLabel(item.label || '', item.labelTextStyle)}
? renderAnimatedLabel(item.label || '', item.labelTextStyle,item.value)
: renderLabel(item.label || '', item.labelTextStyle,item.value)}
</TouchableOpacity>
);
};
export default RenderBars;
export default RenderBars;

View File

@ -24,7 +24,9 @@ type PropTypes = {
width?: number;
height?: number;
noOfSections?: number;
noOfSectionsBelowXAxis?: number;
maxValue?: number;
minValue?: number;
stepHeight?: number;
stepValue?: number;
spacing?: number;
@ -114,6 +116,7 @@ type PropTypes = {
yAxisLabelTexts?: Array<string>;
yAxisLabelPrefix?: String;
yAxisLabelSuffix?: String;
autoShiftLabels?: Boolean;
};
type lineConfigType = {
curved?: Boolean;
@ -216,13 +219,14 @@ export const BarChart = (props: PropTypes) => {
const containerHeight = props.height || 200;
const noOfSections = props.noOfSections || 10;
const horizSections = [{value: '0'}];
const horizSectionsBelow = [];
const stepHeight = props.stepHeight || containerHeight / noOfSections;
const data = useMemo(() => props.data || [], [props.data]);
const spacing = props.spacing === 0 ? 0 : props.spacing ? props.spacing : 20;
const labelWidth = props.labelWidth || 0;
let totalWidth = spacing;
let maxItem = 0;
let maxItem = 0, minItem = 0;
if (props.stackData) {
props.stackData.forEach(stackItem => {
// console.log('stackItem', stackItem);
@ -243,6 +247,9 @@ export const BarChart = (props: PropTypes) => {
if (item.value > maxItem) {
maxItem = item.value;
}
if(item.value < minItem){
minItem = item.value;
}
totalWidth +=
(item.barWidth || props.barWidth || 30) +
(item.spacing === 0 ? 0 : item.spacing || spacing);
@ -256,11 +263,16 @@ export const BarChart = (props: PropTypes) => {
maxItem = parseFloat(maxItem.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;
const stepValue = props.stepValue || maxValue / noOfSections;
const noOfSectionsBelowXAxis = props.noOfSectionsBelowXAxis || (-minValue / stepValue);
const disableScroll = props.disableScroll || false;
const showScrollIndicator = props.showScrollIndicator || false;
const initialSpacing =
@ -326,6 +338,7 @@ export const BarChart = (props: PropTypes) => {
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);
@ -505,6 +518,19 @@ export const BarChart = (props: PropTypes) => {
: 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 animatedHeight = heightValue.interpolate({
inputRange: [0, 1],
@ -683,6 +709,81 @@ export const BarChart = (props: PropTypes) => {
</View>
);
})}
{horizSectionsBelow.map((sectionItems, index) => {
let label = getLabel(sectionItems.value);
if (hideOrigin && index === horizSections.length - 1) {
label = '';
}
return (
<View
key={index}
style={[
styles.horizBar,
{
width: horizontal
? props.width || totalWidth
: props.width || totalWidth + 11,
},
index===0&&{marginTop:stepHeight/2}
]}>
<View
style={[
styles.leftLabel,
{
borderRightWidth: yAxisThickness,
borderColor: yAxisColor,
},
horizontal &&
!yAxisAtTop && {
transform: [{translateX: totalWidth + yAxisThickness}],
},
{
height: index===0?stepHeight*1.5:stepHeight,
width: yAxisLabelWidth,
},
index===0&&{marginTop:-stepHeight/2}
]}>
{!hideYAxisText ? (
<Text
numberOfLines={1}
ellipsizeMode={'clip'}
style={[
yAxisTextStyle,
index === 0 && {marginBottom: stepHeight / -2},
horizontal && {
transform: [
{rotate: '270deg'},
{translateY: yAxisAtTop ? 0 : 50},
],
},
]}>
{label}
</Text>
) : null}
</View>
<View
style={[
styles.leftPart,
{backgroundColor: backgroundColor},
]}>
{hideRules ? null : (
<Rule
config={{
thickness: rulesThickness,
color: rulesColor,
width: horizontal
? props.width || totalWidth
: (props.width || totalWidth) + 11,
dashWidth: dashWidth,
dashGap: dashGap,
type: rulesType,
}}
/>
)}
</View>
</View>
)
})}
</>
);
};
@ -975,7 +1076,7 @@ export const BarChart = (props: PropTypes) => {
style={[
styles.container,
{
height: containerHeight,
height: containerHeight + horizSectionsBelow.length * stepHeight,
},
props.width && {width: props.width},
horizontal && {transform: [{rotate: '90deg'}, {translateY: -15}]},
@ -995,8 +1096,9 @@ export const BarChart = (props: PropTypes) => {
contentContainerStyle={[
{
// backgroundColor: 'yellow',
height: containerHeight + 130,
height: containerHeight + 130 + horizSectionsBelow.length * stepHeight,
paddingLeft: initialSpacing,
paddingBottom:horizSectionsBelow.length * stepHeight,
alignItems: 'flex-end',
},
!props.width && {width: totalWidth},
@ -1083,10 +1185,11 @@ export const BarChart = (props: PropTypes) => {
horizontal={horizontal}
intactTopLabel={intactTopLabel}
barBorderRadius={props.barBorderRadius}
autoShiftLabels={autoShiftLabels}
/>
))}
</Fragment>
</ScrollView>
</View>
);
};
};

View File

@ -87,7 +87,7 @@ const AnimatedBar = (props: animatedBarPropTypes) => {
const elevate = () => {
LayoutAnimation.configureNext({
duration: animationDuration,
update: {type: 'linear', property: 'scaleXY'},
update: {type: 'linear', property: 'scaleY'},
});
setHeight(props.height);
};
@ -95,7 +95,7 @@ const AnimatedBar = (props: animatedBarPropTypes) => {
const layoutAppear = () => {
LayoutAnimation.configureNext({
duration: Platform.OS == 'ios' ? animationDuration : 20,
create: {type: 'linear', property: 'scaleXY'},
create: {type: 'linear', property: 'scaleY'},
// update: { type: 'linear' }
});
setInitialRender(false);
@ -237,4 +237,4 @@ const AnimatedBar = (props: animatedBarPropTypes) => {
);
};
export default AnimatedBar;
export default AnimatedBar;

View File

@ -20,6 +20,7 @@ type PropTypes = {
side: String;
horizontal: Boolean;
intactTopLabel: Boolean;
value: number;
};
type TriangleProps = {
@ -59,6 +60,7 @@ const ThreeDBar = (props: PropTypes) => {
const width = props.width;
const sideWidth = props.sideWidth;
const height = props.height;
const value = props.value;
const showGradient = props.showGradient || false;
const gradientColor = props.gradientColor || 'white';
@ -160,12 +162,13 @@ const ThreeDBar = (props: PropTypes) => {
style={[
{
position: 'absolute',
top: width * -2,
top: value < 0 ? width * -1 : width * -2,
height: (width * 3) / 2,
width: width,
justifyContent: 'flex-end',
alignItems: 'center',
},
value < 0 && {transform:[{rotate:'180deg'}]},
props.horizontal &&
!props.intactTopLabel && {transform: [{rotate: '270deg'}]},
props.side === 'right'
@ -182,4 +185,4 @@ const ThreeDBar = (props: PropTypes) => {
);
};
export default ThreeDBar;
export default ThreeDBar;