How to add horizontal scrolling with fixed y axis?
Also looking at this, has anyone found a solution?
I have edited line-chart.js file.
import React from "react"; import { View, ScrollView, StyleSheet, Animated, TextInput } from "react-native"; import { Svg, Circle, Polygon, Polyline, Path, Rect, G } from "react-native-svg"; import AbstractChart from "../../node_modules/react-native-chart-kit/src/abstract-chart"; import { LegendItem } from "../../node_modules/react-native-chart-kit/src/line-chart/legend-item";
let AnimatedCircle = Animated.createAnimatedComponent(Circle);
class LineChart extends AbstractChart { label = React.createRef();
state = { scrollableDotHorizontalOffset: new Animated.Value(0) };
getColor = (dataset, opacity) => { return (dataset.color || this.props.chartConfig.color)(opacity); };
getStrokeWidth = dataset => { return dataset.strokeWidth || this.props.chartConfig.strokeWidth || 3; };
getDatas = data => data.reduce((acc, item) => (item.data ? [...acc, ...item.data] : acc), []);
getPropsForDots = (x, i) => { const { getDotProps, chartConfig = {} } = this.props; if (typeof getDotProps === "function") { return getDotProps(x, i); } const { propsForDots = {} } = chartConfig; return { r: "4", ...propsForDots }; }; renderDots = config => { const { data, width, height, paddingTop, paddingRight, onDataPointClick } = config; const output = []; const datas = this.getDatas(data); const baseHeight = this.calcBaseHeight(datas, height); const { getDotColor, hidePointsAtIndex = [], renderDotContent = () => { return null; } } = this.props;
data.forEach(dataset => {
if (dataset.withDots == false) return;
dataset.data.forEach((x, i) => {
if (hidePointsAtIndex.includes(i)) {
return;
}
const cx =
paddingRight + (i * (width - paddingRight)) / dataset.data.length;
const cy =
((baseHeight - this.calcHeight(x, datas, height)) / 4) * 3 +
paddingTop;
const onPress = () => {
if (!onDataPointClick || hidePointsAtIndex.includes(i)) {
return;
}
onDataPointClick({
index: i,
value: x,
dataset,
x: cx,
y: cy,
getColor: opacity => this.getColor(dataset, opacity)
});
};
output.push(
<Circle
key={Math.random()}
cx={cx}
cy={cy}
fill={
typeof getDotColor === "function"
? getDotColor(x, i)
: this.getColor(dataset, 0.9)
}
onPress={onPress}
{...this.getPropsForDots(x, i)}
/>,
<Circle
key={Math.random()}
cx={cx}
cy={cy}
r="14"
fill="#fff"
fillOpacity={0}
onPress={onPress}
/>,
renderDotContent({ x: cx, y: cy, index: i })
);
});
});
return output;
};
renderScrollableDot = config => { const { data, width, height, paddingTop, paddingRight, scrollableDotHorizontalOffset, scrollableDotFill, scrollableDotStrokeColor, scrollableDotStrokeWidth, scrollableDotRadius, scrollableInfoViewStyle, scrollableInfoTextStyle, scrollableInfoSize, scrollableInfoOffset } = config; const output = []; const datas = this.getDatas(data); const baseHeight = this.calcBaseHeight(datas, height);
let vl = [];
const perData = width / data[0].data.length;
for (let index = 0; index < data[0].data.length; index++) {
vl.push(index * perData);
}
let lastIndex;
scrollableDotHorizontalOffset.addListener(value => {
const index = value.value / perData;
if (!lastIndex) {
lastIndex = index;
}
let abs = Math.floor(index);
let percent = index - abs;
abs = data[0].data.length - abs - 1;
if (index >= data[0].data.length - 1) {
this.label.current.setNativeProps({
text: `${Math.floor(data[0].data[0])}`
});
} else {
if (index > lastIndex) {
// to right
const base = data[0].data[abs];
const prev = data[0].data[abs - 1];
if (prev > base) {
let rest = prev - base;
this.label.current.setNativeProps({
text: `${Math.floor(base + percent * rest)}`
});
} else {
let rest = base - prev;
this.label.current.setNativeProps({
text: `${Math.floor(base - percent * rest)}`
});
}
} else {
// to left
const base = data[0].data[abs - 1];
const next = data[0].data[abs];
percent = 1 - percent;
if (next > base) {
let rest = next - base;
this.label.current.setNativeProps({
text: `${Math.floor(base + percent * rest)}`
});
} else {
let rest = base - next;
this.label.current.setNativeProps({
text: `${Math.floor(base - percent * rest)}`
});
}
}
}
lastIndex = index;
});
data.forEach(dataset => {
if (dataset.withScrollableDot == false) return;
const perData = width / dataset.data.length;
let values = [];
let yValues = [];
let xValues = [];
let yValuesLabel = [];
let xValuesLabel = [];
for (let index = 0; index < dataset.data.length; index++) {
values.push(index * perData);
const yval =
((baseHeight -
this.calcHeight(
dataset.data[dataset.data.length - index - 1],
datas,
height
)) /
4) *
3 +
paddingTop;
yValues.push(yval);
const xval =
paddingRight +
((dataset.data.length - index - 1) * (width - paddingRight)) /
dataset.data.length;
xValues.push(xval);
yValuesLabel.push(
yval - (scrollableInfoSize.height + scrollableInfoOffset)
);
xValuesLabel.push(xval - scrollableInfoSize.width / 2);
}
const translateX = scrollableDotHorizontalOffset.interpolate({
inputRange: values,
outputRange: xValues,
extrapolate: "clamp"
});
const translateY = scrollableDotHorizontalOffset.interpolate({
inputRange: values,
outputRange: yValues,
extrapolate: "clamp"
});
const labelTranslateX = scrollableDotHorizontalOffset.interpolate({
inputRange: values,
outputRange: xValuesLabel,
extrapolate: "clamp"
});
const labelTranslateY = scrollableDotHorizontalOffset.interpolate({
inputRange: values,
outputRange: yValuesLabel,
extrapolate: "clamp"
});
output.push([
<Animated.View
key={Math.random()}
style={[
scrollableInfoViewStyle,
{
transform: [
{ translateX: labelTranslateX },
{ translateY: labelTranslateY }
],
width: scrollableInfoSize.width,
height: scrollableInfoSize.height
}
]}
>
<TextInput
onLayout={() => {
this.label.current.setNativeProps({
text: `${Math.floor(data[0].data[data[0].data.length - 1])}`
});
}}
style={scrollableInfoTextStyle}
ref={this.label}
/>
</Animated.View>,
<AnimatedCircle
key={Math.random()}
cx={translateX}
cy={translateY}
r={scrollableDotRadius}
stroke={scrollableDotStrokeColor}
strokeWidth={scrollableDotStrokeWidth}
fill={scrollableDotFill}
/>
]);
});
return output;
};
renderShadow = config => { if (this.props.bezier) { return this.renderBezierShadow(config); }
const { data, width, height, paddingRight, paddingTop, useColorFromDataset } = config;
const datas = this.getDatas(data);
const baseHeight = this.calcBaseHeight(datas, height);
return config.data.map((dataset, index) => {
return (
<Polygon
key={index}
points={
dataset.data
.map((d, i) => {
const x =
paddingRight +
(i * (width - paddingRight)) / dataset.data.length;
const y =
((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
paddingTop;
return `${x},${y}`;
})
.join(" ") +
` ${paddingRight +
((width - paddingRight) / dataset.data.length) *
(dataset.data.length - 1)},${(height / 4) * 3 +
paddingTop} ${paddingRight},${(height / 4) * 3 + paddingTop}`
}
fill={`url(#fillShadowGradient${useColorFromDataset ? `_${index}` : ''})`}
strokeWidth={0}
/>
);
});
};
renderLine = config => { if (this.props.bezier) { return this.renderBezierLine(config); }
const {
width,
height,
paddingRight,
paddingTop,
data,
linejoinType
} = config;
const output = [];
const datas = this.getDatas(data);
const baseHeight = this.calcBaseHeight(datas, height);
data.forEach((dataset, index) => {
const points = dataset.data.map((d, i) => {
const x =
(i * (width - paddingRight)) / dataset.data.length + paddingRight;
const y =
((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
paddingTop;
return `${x},${y}`;
});
output.push(
<Polyline
key={index}
strokeLinejoin={linejoinType}
points={points.join(" ")}
fill="none"
stroke={this.getColor(dataset, 0.2)}
strokeWidth={this.getStrokeWidth(dataset)}
/>
);
});
return output;
};
getBezierLinePoints = (dataset, config) => { const { width, height, paddingRight, paddingTop, data } = config; if (dataset.data.length === 0) { return "M0,0"; }
const datas = this.getDatas(data);
const x = i =>
Math.floor(
paddingRight + (i * (width - paddingRight)) / dataset.data.length
);
const baseHeight = this.calcBaseHeight(datas, height);
const y = i => {
const yHeight = this.calcHeight(dataset.data[i], datas, height);
return Math.floor(((baseHeight - yHeight) / 4) * 3 + paddingTop);
};
return [`M${x(0)},${y(0)}`]
.concat(
dataset.data.slice(0, -1).map((_, i) => {
const x_mid = (x(i) + x(i + 1)) / 2;
const y_mid = (y(i) + y(i + 1)) / 2;
const cp_x1 = (x_mid + x(i)) / 2;
const cp_x2 = (x_mid + x(i + 1)) / 2;
return (
`Q ${cp_x1}, ${y(i)}, ${x_mid}, ${y_mid}` +
` Q ${cp_x2}, ${y(i + 1)}, ${x(i + 1)}, ${y(i + 1)}`
);
})
)
.join(" ");
};
renderBezierLine = config => { return config.data.map((dataset, index) => { const result = this.getBezierLinePoints(dataset, config); return ( <Path key={index} d={result} fill="none" stroke={this.getColor(dataset, 0.2)} strokeWidth={this.getStrokeWidth(dataset)} /> ); }); };
renderBezierShadow = config => {
const { width, height, paddingRight, paddingTop, data, useColorFromDataset } = config;
return data.map((dataset, index) => {
const d =
this.getBezierLinePoints(dataset, config) +
L${paddingRight + ((width - paddingRight) / dataset.data.length) * (dataset.data.length - 1)},${(height / 4) * 3 + paddingTop} L${paddingRight},${(height / 4) * 3 + paddingTop} Z;
return (
<Path
key={index}
d={d}
fill={url(#fillShadowGradient${useColorFromDataset ? _${index} : ''})}
strokeWidth={0}
/>
);
});
};
renderLegend = (width, legendOffset) => { const { legend, datasets } = this.props.data; const baseLegendItemX = width / (legend.length + 1);
return legend.map((legendItem, i) => (
<G key={Math.random()}>
<LegendItem
index={i}
iconColor={this.getColor(datasets[i], 0.9)}
baseLegendItemX={baseLegendItemX}
legendText={legendItem}
labelProps={{ ...this.getPropsForLabels() }}
legendOffset={legendOffset}
/>
</G>
));
};
render() { const { width, height, data, withScrollableDot = false, withShadow = true, withDots = true, withInnerLines = true, withOuterLines = true, withHorizontalLabels = true, withVerticalLabels = true, style = {}, decorator, onDataPointClick, verticalLabelRotation = 0, horizontalLabelRotation = 0, formatYLabel = yLabel => yLabel, formatXLabel = xLabel => xLabel, segments, transparent = false, chartConfig = {}, } = this.props; const { scrollableDotHorizontalOffset } = this.state; const { labels = [] } = data; const { borderRadius = 0, paddingTop = 16, paddingRight = 64, margin = 0, marginRight = 0, paddingBottom = 0 } = style;
const config = {
width,
height,
verticalLabelRotation,
horizontalLabelRotation
};
const datas = this.getDatas(data.datasets);
let count = Math.min(...datas) === Math.max(...datas) ? 1 : 4;
if (segments) {
count = segments;
}
const legendOffset = this.props.data.legend ? height * 0.15 : 0;
return (
<View style={{flex:1,flexDirection:'row'}}>
<View>
<Svg
height={height + paddingBottom + legendOffset}
//width={width - margin * 2 - marginRight}
width={60}
>
<Rect
width="100%"
height={height + legendOffset}
rx={borderRadius}
ry={borderRadius}
fill="url(#backgroundGradient)"
fillOpacity={transparent ? 0 : 1}
/>
{this.props.data.legend &&
this.renderLegend(config.width, legendOffset)}
<G x="0" y={legendOffset}>
{ this.renderDefs({
...config,
...chartConfig,
data: data.datasets
})}
<G>
{ withHorizontalLabels
? this.renderHorizontalLabels({
...config,
count: count,
data: datas,
paddingTop,
paddingRight,
formatYLabel,
decimalPlaces: chartConfig.decimalPlaces
})
: null
}
</G>
</G>
</Svg>
</View>
<View style={{ marginTop:-5, width:'80%' }}>
<ScrollView horizontal={ true }
style={{}}
>
<Svg
height={ height + paddingBottom + legendOffset }
width={ width }
marginLeft={ -30 }
>
<G>
{ withVerticalLabels
? this.renderVerticalLabels({
...config,
width: width,
labels,
paddingRight,
paddingTop,
formatXLabel
})
: null
}
</G>
<G>
{ withInnerLines
? this.renderHorizontalLines({
...config,
width : width,
count: count,
paddingTop,
// paddingRight
})
: withOuterLines
? this.renderHorizontalLine({
...config,
width: width,
paddingTop,
// paddingRight
})
: null
}
</G>
<G>
{ withInnerLines
? this.renderVerticalLines({
...config,
width: width,
data: data.datasets[0].data,
paddingTop,
paddingRight
})
: withOuterLines
? this.renderVerticalLine({
...config,
width: width,
paddingTop,
// paddingRight
})
: null
}
</G>
<G>
{ this.renderLine({
...config,
width: width,
...chartConfig,
paddingRight,
paddingTop,
data: data.datasets
})}
</G>
{/*<G>
{withShadow &&
this.renderShadow({
...config,
data: data.datasets,
paddingRight,
paddingTop,
useColorFromDataset: chartConfig.useShadowColorFromDataset,
})}
</G>*/}
<G>
{ withDots &&
this.renderDots({
...config,
width: width,
data: data.datasets,
paddingTop,
paddingRight,
onDataPointClick
})}
</G>
<G>
{ withScrollableDot &&
this.renderScrollableDot({
...config,
width: width,
...chartConfig,
data: data.datasets,
paddingTop,
paddingRight,
onDataPointClick,
scrollableDotHorizontalOffset
})
}
</G>
<G>
{ decorator &&
decorator({
...config,
width: width,
data: data.datasets,
paddingTop,
paddingRight
})
}
</G>
</Svg>
</ScrollView>
</View>
{ withScrollableDot && (
<ScrollView
style={StyleSheet.absoluteFill}
contentContainerStyle={{ width: width * 2 }}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: { x: scrollableDotHorizontalOffset }
}
}
])}
horizontal
bounces={false}
/>
)}
</View>
);
} }
export default LineChart;
this.calcBaseHeight(datas, height);
can you provide file for typescript version ? i have no file line-chart.js
i have LineChart.js in node_modules/react-native-chart-kit/dist/line-chart/LineChart.js but its have a little bit different syntax.
thanks in advance
react-native-chart-kit.zip Hi @sashastg, I think you are using current version. I worked on 1 year ago on version "^5.5.0". This is my react-native-chart-kit zip file ,if possible try to downgrade library and use this attached zip file.
@sashastg i want to horizontal scroll with x axis data . any solution for this one