Extending a Pie 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 pie, bar, 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 Pie React component with front-end dynamic capabilities to make your pie charts more configurable and easier to create. This technique also works with other chart types.
The Usual Approach
If you look up the pie 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 slice of the pie, and uses dummy numbers in the data array to give the slices their size:
The arrays backgroundColor and borderColor give the slices their background and border colors. There is a background and a border color for each element (slice of the pie) in the data array.
In an actual application, this 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 PieChartHandler, 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, 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, 35 fall under the Open Data category, 120 fall under Development, and 45 fall under Finance.
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 PieChartHandler component should start looking for data. In this case, the entries array.
- The labels array contains the labels for each of the slices in the Pie chart.
- The datasets array contains the label for the chart as well as the data and colors for each of the slices.
- Notice that, instead of having fixed values, the data array now has @COUNT expressions.
- The data array has an amount of elements that matches the number of slices in the chart. Each expression has 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 would count the number of entries that do not fall under the Government category:
"@COUNT([Category] != 'Government')"
Here is how the entries in the data array map to the visualization:
For additional slides in the Pie chart, you can add new entries in the JSON file, and modify the labels 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 PieChartHandler class. For example, just changing the @COUNT expressions as follows:
would render a new Pie chart showing the total number of entries that do not match those categories:
Now let’s take a look at the PieChartHandler class.
The PieChartHandler 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 Pie 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 PieChartHandler 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 24 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 40 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.
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 Bar 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!