How I built an Interactive 30-Day Bitcoin Price Graph with React and an API
Preface
Don’t care about the back story? Here’s a live demo of the bitcoin price graph I’ll be walking through (note: It’s not mobile-friendly yet — PR’s welcome). The GitHub repository with all the code is linked at the bottom of the article.
Why?
Last week I published a tutorial on Simple Data Visualization with React JS. That tutorial walked you through building a simple line graph:
Yes, okay, it’s boring. But the point of that tutorial wasn’t to build something mind blowing, it was to learn the basics of SVG in React.
In the comments, Kris Morf asked how to add an area fill to the chart, showing me the graphs on CoinBase as an example. Here’s what those charts look like:
Yeah, that’s much cooler than my lame line chart.
After walking Kris through how to Add Fill to a Line Chart, I thought Why don’t I just make a clone of this? So that’s what I did this weekend.
Here’s what I built, and what I’ll be walking you through in this article:
A live demo is available here if you want to play around with it. Note: It may take a second to load as it’s currently deployed on the free tier at now.sh
Side note: If tutorials like this interest you and you want to learn how to build more cool stuff with React, check out the The Complete Web Development Tutorial Using React and Redux
Project Structure
Here’s what my project structure looks like. There are four react components.
app.js
— Parent component.InfoBox.js
—Renders our realtime Bitcoin prices and change since last month.ToolTip.js
— Renders the Tool Tip displaying the date and price of the hovered locationLineChart.js
—Renders the actual line chart. Returns a single SVG element.
The API
For this project, the best API I could find is the CoinDesk API. If you have a better bitcoin API, please tweet at me and let me know!
The CoinDesk API offers both real-time and historical bitcoin price data. For this project, I display the real-time bitcoin price in the top left corner (InfoBox.js). It updates every 90 seconds.
The historical price is what I use to provide data for the chart. The historical data endpoint is: https://api.coindesk.com/v1/bpi/historical/close.json
The API returns an object that looks like this:
{
“bpi”: {
“2017–06–24”: 2619.1188,
”2017–06–25": 2594.4538,
”2017–06–26": 2485.3588,
”2017–06–27": 2593.17,
”2017–06–28": 2584.5638,
// ...
}
}
Once I get the data back, I loop through it and format it into an array of objects:
for (let date in bitcoinData.bpi){
sortedData.push({
d: moment(date).format('MMM DD'),
p: bitcoinData.bpi[date].toLocaleString(),
x: count,
y: bitcoinData.bpi[date]
});
}
d
: Formatted Date (ex: Jul 31)p
: Formatted Currency String (ex: $2,000.46)x
: Count (numerical, beginning with 0)y
: Unformatted Price (2000.46738 — used for graphing)
The data is then sent to a child component that builds the chart. Here’s a higher level overview of the data flow.
Basic Data Flow
The Data flow of the project is pretty straight forward:
app.js
fetches historical data from the CoinDesk API and formats it into an array of objects.- Data is passed to the
LineChart.js
component which renders the chart based on the data supplied fromapp.js
. - When the
SVG
component rendered inLineChart.js
is hovered on, 3 things happen:LineChart.js
draws a vertical line on the cursor coordinates. It determines the closest data point to the cursor and draws a circle to highlight that data point. Finally, data is passed back toapp.js
indicating the hover location, and closest point. app.js
sends data toToolTip.js
so the tool tip can render in the correct location, and with the correct data.InfoBox.js
runs independently of the other components and fetches real time data from the CoinDesk API every 90 seconds. The data is formatted and then displayed to the user.
Breaking Down the Graph
The Graph is not just one shape being drawn to the screen. It’s a collection of shapes and lines within a single SVG element. If you look at LineChart.js
, you’ll see there are up to eight function calls used to create our graph:
this.makeAxis() Makes Graph Axis
this.makePath() Makes Graph Line
this.makeArea() Makes Shaded Graph Area (under line)
this.makeLabels() Makes Graph Labelsthis.getCoords(e) When Hovered Gets Coords of Hover
this.createLine() When Hovered Makes Vertical Line
this.makeActivePoint() When Hovered Finds Closest Pointthis.stopHover() Clears Line and Point When Hover Stops
Let’s look at an example. If we remove everything and only run makePath()
we’re left with just an SVG line along our data points. Here’s what that looks like:
Likewise, we can remove everything except makeArea()
and makeAxis()
. This will draw our two Axis lines, and the shaded in shape. Here’s the result:
It isn’t until we start adding multiple shapes and lines together that our graph starts to look interesting. Withholding the hover effects, Here’s our graph with makeAxis()
, makePath()
, makeLabels()
, and makeArea()
:
The fun stuff happens when the SVG element is hovered over. On hover I run a function that gets the coordinates of the mouse on the graph. In order to do this we need two pieces of information:
- The location of the SVG graph within the page
- The location of the mouse
Here’s what that code looks like:
I first get the svgLocation
of the line chart within the page. Then, I adjust for any padding the chart may have. Finally I take the x location of the mouse and subtract the pixels that are to the left of the SVG chart. This gives me the location of the mouse relative to the line chart.
State is updated, and a vertical line is drawn on the graph at the mouse’s X coordinate:
Meanwhile, I can simply loop through the coordinates on our chart to determine which one has an X value closest to the mouse:
let {svgWidth} = this.props;
let closestPoint = {};
for(let i = 0, c = svgWidth; i < svgData.length; i++){
if ( Math.abs(svgData[i].svgX — this.state.hoverLoc) <= c ){
c = Math.abs(svgData[i].svgX — this.state.hoverLoc);
closestPoint = svgData[i];
}
}
After finding the closest X value, we draw an SVG circle at that point:
makeActivePoint(){
const {color, pointRadius} = this.props;
return (
<circle
className='linechart_point'
style={{stroke: color}}
r={pointRadius}
cx={this.state.activePoint.svgX}
cy={this.state.activePoint.svgY}
/>
);
}
The result is this:
The Tool Tip
The final part of the equation is the Tool Tip. The Tool tip is in a completely different component, but works just like our vertical line does. The Tool Tip needs two pieces of information to work:
- The current mouse location
- The closest data point
Both of these pieces of information are received from LineChart.js
. ToolTip.js
then simply returns a <div>
element centered above the mouse. with the formatted data from the closest data point.
let placementStyles = {};
let width = 100;
placementStyles.width = width + 'px';
placementStyles.left = hoverLoc + svgLocation.left - (width/2);return (
<div className='hover' style={ placementStyles }>
<div className='date'>{ activePoint.d }</div>
<div className='price'>{activePoint.p }</div>
</div
)
...
Where’s the code?
Glad you asked. All the code for this project is open source and available in my GitHub Repo. The live demo, once again, is here.
Suggest
☞React Native: Build Your Own Mobile Apps
☞React Redux React-Router: From Beginner to Paid Professional