Before this weekend I wouldn’t touch D3 or any data visualization library with a ten-foot pole…
But I decided to take the leap and dive in, because I was interested in making a mini crypto-exchange. This exchange lets you create an account based on a randomly generated hash. There’s a faucet to let you generate a few coins at a time and a function to let you send them to another wallet address. I thought it would be interesting to analyze a user’s interaction with the faucet aka God Wallet and other users. To keep things simple, I decided to just use two addresses.
Next came the monumental task of learning D3, I went through a few resources in a couple days and still felt overwhelmed. D3 is a very complex library that provides a plethora of functions to fit your every need. Most of the data visualizations you see on the internet are in fact made with D3. An interesting thing to mention is that the library is declarative, where most code that we’re used to is imperative. That means you have to define each and every attribute of what you’re trying to achieve. It might be a good match for some complex data analysis, but often overkill for simple visualizations. That being said, taking into account the scope of my project and the fact that my visualization was a simple stacked bar chart, I decided to opt for Chart.js instead. I plan to go back and dive deeper into the beast that is D3, but I did have some basic takeaways that I thought were important.
Data Manipulation & DOM Manipulation
Data manipulation in a nutshell is just curating your data to be “visualization-ready”. I had little idea how important this was until I actually got around to implementing this with Chart.js. I spent some time thinking of what piece of information I actually wanted to show as a visualization. Since the back-end models I was using were pretty simple (User & Transfer), I thought it might be cool to see what the cashflows were for each address the user associated with. I had to filter through API calls to my user’s own transfers (outgoing and incoming) and the transfers from the God Wallet. I realized my x-Axis would comprise of the different addresses I had interacted with and the y-Axis would be from -y → y, y being min/max. Now it also made sense that since my bar chart would be “stacked”, I needed to separate my data into two datasets: one incoming (positive values) and one outgoing (negative values). I ultimately curated the transfer data into two objects and passed them as props down to my Graph component.
DOM manipulation is just as important as data manipulation. With D3 in React, you have to create a Ref to the element you are rendering to give control of that node to D3. DOM manipulation has to do with being aware of if your visualization is static or dynamic. Then depending on that, you would use the appropriate lifecycle methods to update your chart to match incoming props. A big pain point when dealing with dynamic data and visualizations is dealing with the animations/transitions correctly. Below is a list of some important lifecycle methods to keep in mind:
- componentDidUpdate — This method can be used to watch out for incoming props and compare them to previous props to determine any changes you might need to make (or not) in the behavior of your visualization.
- componentDidMount — This lifecycle method is used to load any data after the mount and used to generate new elements such as the axises of a chart.
- static getDerivedStateFromProps — This method is important when it comes to making sure the transitions/animations that your data is supposed to have are actually rendered before any re-renders.
Chart.js turned out to be a good choice for what I wanted to do, albeit still a little difficult! I had to curate my data and send it off to my Graph component to handle. My Graph component would then fire off a function based on the props (remember the in-flow and out-flow objects?) and create the actual chart. My chart was being created on a canvas element and since Chart.js has animations out of the box, I didn’t have to worry about methods like static getDerivedStateFromProps. I used the standard componentDidMount and componentDidUpdate, but in addition, I used an extra lifecycle method: componentWillUnMount. I had to use it because Chart.js requires you to destroy your chart before updates. Apart from making sure that my props were flowing properly and making a few functions asynchronous, the majority of my work was probably spent in making sure the data was curated properly. The end result looked great and I was definitely pleased. Check below for the demo of my app and stay tuned for a more in depth article on D3 & possibly other data viz libraries in the future!