Extending a Bar Chart React Component with Front-end Dynamic Capabilities
By José De Jesús
If you have done data visualization with Javascript before, you have probably heard of Chart.js, a popular open-source JavaScript library which supports bar, pie, line, area, radar, polar, bubble, and scatter chart types. React-chartjs-2 is a React wrapper that encapsulates Chart.js elements as React components.
This article teaches you how to extend a Bar React component with front-end dynamic capabilities to make your bar charts more configurable and easier to create. This technique also works with other chart types.
The Usual Approach
If you look up the vertical bar chart example for React-chartjs-2, you will find the sample code that produces the following chart:
The code uses the labels and datasets arrays to capture the details for each month, and applies two datasets (seen as pink and blue bars above) to each of the months using fake data values produced with the faker library:
In an actual application, the data would come from a real data source such as a REST API that calls a database. To create different charts, even from the same data source, you would have to either massage the data on the frontend or create a new service to get the desired visualization for each chart.
A Better Approach
There is a cleaner, more configurable approach that allows you to dynamically create charts without writing additional code, as well as shift some of the query handling to the client side to avoid creating new services.
The technique involves creating a core chart component, which in this example we call BarChartHandler, to generate charts dynamically from details found in a JSON file. These details can also include queries you could handle on the frontend.
It’s a pretty simple concept. For each new chart, you create a new JSON file and pass it as a parameter to a component that can handle that chart type. The JSON object does not have to come from a file. You could instead dynamically generate a JSON object based on user input, for example, and then pass it to the right visualization component:
So let’s get started.
Suppose we wanted to retrieve a list of available public APIs by category using the public API found at https://api.publicapis.org/entries.
First type https://api.publicapis.org/entries in your browser (or use curl) to confirm that you get a response:
Here is a formatted snippet of the start of that data:
The count variable shows there are a total of 1,425 public APIs, and the entries array contains the list of those APIs with additional information about each one.
In this example, we are interested in four categories: Government, Open Data, Development, and Finance. Now suppose you want to visualize a count of how many entries match those categories and how many do not, and separate the matching vs. non-matching entries into two sets of bars, such as follows:
After running it through the chart handler, you would get the generated chart above showing that, out of the 1,425 entries, 86 fall under the Government category as shown in the first bar of the first set on the left, while 1,339 do not fall under the Government category, as shown in the first bar of the second set on the right.
Here is the corresponding JSON file. Let’s walk through it step by step.
Walkthrough of the JSON File
- The title variable holds the title of the chart (Public APIs), as seen in the chart above.
- The datasource variable holds the API to call for retrieving the raw data.
- The rootobj variable points to where the BarChartHandler component should start looking for data. In this case, the entries array.
- The labels array contains the number of chart sets you wish to see and how they should each be labeled.
- The datasets array contains four entries, one for each of the bars we want in each of the chart sets. Each entry represents a bar and contains a label for the bar, the data to generate for each data set, and the background (fill) color of the bar.
- The data array in each of the entries has an amount of elements that matches the number of chart sets. In this example, each data array contains two entries (one for each set) with the following format:
"@COUNT([variable] operand 'string')"
This tells the handler class to count how many variables match the expression variable operand string. For example, the following entry tells the handler that it should count how many entries have a Category variable equal to the string ‘Government’:
"@COUNT([Category] == 'Government')"
Similarly, the following entry counts the number of entries that do not fall under the Government category:
"@COUNT([Category] != 'Government')"
The data array, in this case, contains both entries, one for each of the chart sets in the visualization. Here is how they map:
The bar on the left shows the total number of public APIs that fall within the Government category, while the bar on the right shows the total number of entries that do not.
For additional datasets, you can add new entries in the JSON file, and modify the labels, datasets, and data arrays accordingly.
That’s it for the JSON file. Changing any of the entries or creating a new JSON file would change the visualization without requiring changes to the BarChartHandler class. Now let’s take a look at the BarChartHandler class.
The BarChartHandler Class
Here is the complete listing for this example:
The first thing this class does is parse the JSON file that is passed to it and use the datasource variable in the JSON object to retrieve the API data. It then handles the string replacements necessary for the JSON object to have a format that the React-chartjs-2 Bar component expects. This means replacing all the @functions (in this case instances of the @COUNT function) with the real calculated values from the data, effectively shifting the querying of the data to the frontend.
You can extend the BarChartHandler class to handle other functions in the JSON object such as @SUM and @AVERAGE, for example, to allow expressions like these in the JSON object:
@SUM([Score])
@AVG([Score])
The handler class could then support those features with these additional functions (which you can place after Line 47 above):
They are not part of the main listing to keep our example short, but you can add them as well as your own functions to the handler class. For the two functions above, the parseOperation method would have to include additional else if statements to handle the new operations (placed after Line 63 above):
else if (operationName === 'SUM') {
return this.calculateSum(variableName);
}
else if (operationName === 'AVG') {
return this.calculateAvg(variableName);
}
In the calculateCount function, the code composes an expression based on the @COUNT format discussed above and uses the javascript eval function to evaluate the expression. If the expression is true, the code increments the count variable, and finally returns the total count for that expression.
The expressions in the data array of the JSON file are decomposed and processed in this manner one by one until they are all replaced with the calculated counts.
Finally, the defaultOptions constant allows you to either include options in the JSON object that the React-chartjs-2 Bar component recognizes, or, as we’ve done in this example, just have the handler class embed those default options for you in the Bar chart.
Conclusion
Using a combination of a handler class and an external JSON file (or a dynamically generated JSON object as discussed earlier) reduces your code footprint and allows you to shift some of the querying capabilities for your charts to the frontend code. It also allows non-programmers to create new charts by simply adding JSON files.
This approach makes your code much easier to maintain too. Any updates to the chart library from the vendor would mainly involve changing the handler class.
As mentioned earlier, this technique works with other chart types as well. Check out a sister article here that is very similar to this one, but addresses a Pie chart component instead.
Since many of the functions in both examples are identical, you may choose to combine them into a single handler class that can support both chart types. Depending on what you are doing, this may make sense, but keeping separate handler classes will improve your code’s maintainability.
Enjoy!