Using Recharts in React Native project
1. Introduction to Recharts
Recharts is a chart library built with React and D3. Recharts supports many kinds of charts such as Area Chart, Bar Chart, Line Chart, and Pie Chart. The strength of Recharts is easy to extend and customize. For example, we can change the active cursor, legend, and tick in Axises to our own component and styling. The following is an example of how to customize a tick on the X Axis:
const CustomizedAxisTick = React.createClass({
render () {
const {x, y, stroke, payload} = this.props;
return <g>
<text x={0} y={0} dy={16} textAnchor="end"
fill="#666" transform="rotate(-35)">
{payload.value}
</text>
</g>
}
});
const SimpleLineChart = React.createClass({
render () {
return <LineChart>
<XAxis dataKey="name" tick={<CustomizedAxisTick/>}/>
<YAxis/>
<CartesianGrid />
<Line type="monotone" dataKey="pv" />
</LineChart>
}
});
Find more examples here and the full Recharts API here.
2. Project Structure
In my React Native project, I add a ‘web' directory for a React project which uses the Recharts library:
In development, web part runs at port 9000 and is loaded by the Webview Bridge component in the React Native part. I chose Webview Bridge over built-in Webview because it supports communication between React Native and the web. For example, when a user clicks on a custom button in a chart component we want to change the screen to another scene or when they click on the Navigation Button, the chart content will be changed. All of these things can be done with Webview Bridge. Below is an example of how to work with Webview Bridge:
Using web parts, listen and send a message to the React Native part:
if (WebViewBridge) {
WebViewBridge.onMessage = function (message) {
console.log(message)
};
WebViewBridge.send("hello from webview");
}
In the React Native part, listen and send a message to web parts:
let Sample = React.createClass({
onBridgeMessage(message) {
const { webviewbridge } = this.refs;
switch (message) {
case "hello from webview":
webviewbridge.sendToBridge("hello from react-native");
break;
}
},
render() {
return <WebViewBridge
ref="webviewbridge"
onBridgeMessage={this.onBridgeMessage.bind(this)}
source={{uri: "http://localhost:9000"}}
/>
}
});
3. Responsive Design for Charts
To make the app work with a wide range of devices, charts must be built with a responsive design. In the ChartPage
component, we need to listen for a “window resize” event and store the screen width in the component state:
componentWillMount() {
this.props.getPoints();
$(window).resize(() => {
this.setState({
chartWidth: $(window).width()
});
});
// fire resize:
$(window).trigger('resize');
}
chartWidth
will be set with width property to render a responsive chart in the ChartPage.renderChart
function:
// File: web/ui/page/ChartPage.jsx
let props = {
width:this.state.chartWidth,
height: this.state.chartHeight,
lock: this.handleLock.bind(this),
unlock: this.handleUnlock.bind(this),
margin: {top: 5, right: MARGIN_RIGHT, left: 0, bottom: 5}
}
if(chartConfig.type != 'pie') {
props.data = this.props.stats.points[chartConfig.dataKey];
}
return <div key={'chart-' + chartConfig.dataKey} className='gt-chart'>
<div className='gt-tooltip-contents'>
<span className='header'>{label}</span>
</div>
<Chart {...props}>
{this.renderChartBody(chartConfig)}
</Chart>
</div>
And unregister before component unmount:
// File: web/ui/page/ChartPage.jsx
componentWillUnmount() {
$(window).off('resize');
}
After all that, here’re the results:
Line Chart and Bar Chart:
Area Chart and Pie Chart:
4. Patch Recharts to Support iOS Gesture
There's a difference between touch gesture in iOS and Android. In Android, Webview components can react with both vertical and horizontal at the same time, but in iOS, it cannot. This makes moving your chart cursor in iOS very difficult. If the user touches and moves the cursor a bit vertically, it will turn the move to Webview scrollbar instead of keeping the chart cursor moving.
My solution is to patch Recharts to lock the Webview scrollbar while the user is using the chart cursor and to release it when they finish. In the file web/ui/components/recharts/generateCategoricalChart.js
, I added some functions to listen to touch events to detect whether the user is using a chart cursor or not. If they start using a chart cursor, send a lock message to Webview Bridge:
// File: web/ui/components/recharts/generateCategoricalChart.js
this.handleTouchMove = function (e) {};
this.handleTouchStart = function(e) {}
this.handleTouchEnd = function() {}
this.lock = function() {
window.locked = true;
if(self.props.lock != null) {
self.props.lock();
}
}
this.unlock = function() {
window.locked = false;
if(self.props.unlock != null) {
self.props.unlock();
}
}
In ChartScreen.js, store the 'locked' value in the component state and set it to WebViewBridge.scrollEnabled:
// File: screens/ChartScreen.js
render() {
let contentInset = {
right: -1 * config.chartScrollbarWidth,
top: 0,
bottom: 0,
left: 0
}
let browserStyle = {
...styles.browser,
}
if(!this.state.loaded) {
browserStyle.opacity = 0;
}
let browser = <WebViewBridge
contentInset={contentInset}
source={{uri: this.buildUrl()}}
style={browserStyle}
scrollEnabled={!this.state.locked}
onLoadEnd={this.handleLoadEnd.bind(this)}
onBridgeMessage={this.handleBridgeMessage.bind(this)}
startInLoadingState={false}
/>;
return <View style={styles.container}>
{browser}
</View>
}
Run web/patch-recharts.sh
to replace the original generateCategoricalChart.js
file in the Recharts library.
5. Deployment
If your app is an analysis app with many charts, I recommend you to deploy web parts in a server and load it in React Native remotely. By doing this, when the charts are updated, the user doesn't need to reinstall the app to have the latest version of charts. Otherwise, if the chart is only a small part of the app, you should build the web part then load index.html and bundle.js from internal device file system.
6. My Project
Visit and fork my project at https://github.com/thoqbk/RNCharts or contact me by thoqbk@gmail.com for further information.