Custom React Charts

In our journey as software engineers, oftentimes we find ourselves trying to represent data in formats that are easily understandable to end-users. We could be building anything, from a simple application comprised of one or two charts to a complex dashboard with many moving parts - including charts, cards, and even graphs. Depending on the use case, it might be more beneficial to stick to a library that provides robust solutions for all the ways we might intend to display the data. Other times, it might be better to build a custom component in our application that we can use to display simple data.

In this article, we will be exploring how to build simple custom components using CSS and javascript (React) with which we would display information. We will not only explore a few examples but help you understand how to go about creating these components; this article will serve as a blueprint, and as such, we will be able to apply this knowledge in creating other components we desire.

Pie Chart

We'd kick off our implementation of custom CSS charts by building our own pie chart. First off, we'd create a sample pie chart using just CSS. We'll take advantage of the conic-gradient CSS function to turn a regular-looking circle into a pie chart. These articles by w3Schools.com and mdn can serve as quick refreshers on what conic-gradient function is and how it works.

background: conic-gradient(green 0deg 100deg, red 100deg 280deg, blue 280deg);

Here's a snippet of the code we've written so far. We have created a Piechart component and styled it using CSS, wrapped it in a Chart container component, and rendered it

Next, we'll add a bit of dynamism to it which will allow our component to take an object with values and craft a chart with labels. We would make our Piechart component stateful, making it able to maintain the state of the chart's colors and labels.

  const ChartContainer = () => {
    //The sample data we would be working with
    const fruits = {
      apples: 10,
      oranges: 6,
      bananas: 14,
      grapes: 12,
      pineapples: 3,
      berries: 6
    }
    return (
      <div className="chartContainer">
        <PieChart fruits={fruits} />
      </div>
    )
  }
//The piechart component takes in a fruits prop
  const PieChart = ({fruits}) => {
  //we are using a default background of grey for our chart
  const [background, setBackground] = React.useState('grey');
  const [labels, setLabels] = React.useState([])
  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
  const pieChartRef = React.useRef(null)
      return (
        <div className="piechart" ref={pieChartRef}></div>
      )
  }

The colors array stores values of colors that we will use in our custom chart, you can make use of normal color names, RGB, or HSL values too. Basically, we can make use of anything that is CSS compliant. The useRef hook will help us access our piechart DOM element directly, we will take advantage of this later to update the background of our PieChart element.

Next, we would write a useEffect function that will run once our component mounts, this function will help us process the data, and populate the colors on our background.

  React.useEffect(() => {
      const paintChart = () => {

         //This array will contain the number of fruits
        let chartData = []

        //This array will contain the names of fruits
        let chartLabels = []

        //We loop through the fruits object collecting the required data
        for (let fruit in fruits){
          chartData.push(fruits[fruit])
          chartLabels.push(`${fruit}`)
        }

        const sum = chartData.reduce((x, y) => x + y)

        // circle spans 360 degrees, here we convert the values using their ratio to the sum multiplied by 360
        chartData =  chartData.map((x) => Math.round(360 * x/sum))
        let str = ''
        let cumulativeDegree = 0 
        //Here, we loop through the data, creating a string for use in the conic-gradient function
        for (let i = 0; i < chartData.length; i++) {
          //Check if it's the first data in an array containing more than one data
          if(i === 0 && chartData.length > 1){
            str += `${colors[i]} 0 ${chartData[i]}deg,`
          } else if(i + 1 === chartData.length) { //Check if it's the last data
          //Take note of the space between space at the beginning of the string
            str += ` ${colors[i]} ${cumulativeDegree}deg ${cumulativeDegree + chartData[i]}deg`         
          } else {
          //Take note of the space between space at the beginning of the string
             str += ` ${colors[i]} ${cumulativeDegree}deg ${cumulativeDegree + chartData[i]}deg,`
          }
          cumulativeDegree += chartData[i]
        }
        //Set chart labels
        setLabels(chartLabels)

        set background value
        setBackground(`conic-gradient(${str})`)
      }

      //Call the paint chart function if fruits exist
      if(fruits) paintChart()
    },[])

We will also write another effect hook that runs when the background variable changes to help us update the DOM element with the new background value.

React.useEffect(() => {
    //Set the background of the DOM elements using the ref attribute
    pieChartRef.current.style.background = background
  }, [background])

Our Piechart is now all set, lastly, we have to add labels to make our data more intuitive. We will create a ChartLabel component and update our CSS accordingly. We will also add the ChartLabel component to our PieChart component accordingly

    const ChartLabel = ({text, color}) => {
      return (
        <div className="chartLabel">
         <div style={{background: color}} className="chartLabelIndicator"></div>
         <div className="chartLabelText">{text}</div>
        </div>
      )
    }

We will now update our Piechart component accordingly. The jsx being returned in our Piechart component now looks like this:

       return (
        <>
            <div className="piechart" ref={pieChartRef}></div>
            <div className="chartLabelContainer">
              {
                labels.map((label, index) => {
                  return (
                    <ChartLabel key={index} text={label} color = {colors[index]} />
                  )
                })
              }
            </div>
        </>
      )

Putting everything together, we can see the final snippet of our code, and it looks this way

Ternary (Triangular) Charts

Ternary or triangular charts are best used for hierarchical data. There are situations where you want to display dynamic data in relation to their hierarchy, this particular pick will be the best for this situation.

Using our knowledge from creating a custom pie chart, we should begin to understand that there are two major things that we need to get our charts to work

  • A way to display multiple colors on the preferred chart

  • A way to make the displayed colors dynamic

We would start out by creating a multi-colored triangle using linear-graident CSS function.

From here on out, it's all about creating our react component and adding dynamism like we did with the pie chart. We have to be careful to note that the linear-gradient function does not make use of deg as such, we will be switching to percentages and calculating our values based on 100%

The code for our ternary chart would look like this after we are finished:

  const TernaryChart = ({fruits}) => {
  const [background, setBackground] = React.useState('grey');
  const [labels, setLabels] = React.useState([])
  const colors = ['green', 'blue', 'indigo', 'red', 'orange', 'yellow', 'violet'];
  const ternaryChartRef = React.useRef(null)
  React.useEffect(() => {
    const paintChart = () => {
      let chartData = []
      let chartLabels = []
      for (let fruit in fruits){
        chartData.push(fruits[fruit])
        chartLabels.push(`${fruit}`)
      }
      const sum = chartData.reduce((x, y) => x + y)
      chartData =  chartData.map((x) => Math.round(100 * x/sum))
      let str = ''
      let cumulativeDegree = 0 
      for (let i = 0; i < chartData.length; i++) {
        if(i === 0 && chartData.length > 1){
          str += `${colors[i]} 0 ${chartData[i]}%,`
        } else if(i + 1 === chartData.length) {
          str += ` ${colors[i]} ${cumulativeDegree}% ${cumulativeDegree + chartData[i]}%`         
        } else {
           str += ` ${colors[i]} ${cumulativeDegree}% ${cumulativeDegree + chartData[i]}%,`
        }
        cumulativeDegree += chartData[i]
      }
      setLabels(chartLabels)
      setBackground(`linear-gradient(to bottom, ${str})`)
    }
    if(fruits) paintChart()
  },[])  

  React.useEffect(() => {
    ternaryChartRef.current.style.background = background
  }, [background])
    return (
      <>
          <div className="ternarychart" ref={ternaryChartRef}></div>
          <div className="chartLabelContainer">
            {
              labels.map((label, index) => {
                return (
                  <ChartLabel key={index} text={label} color = {colors[index]} />
                )
              })
            }
          </div>
      </>
    )
}

Putting everything together, here's a snippet of what our code should look like

Bonus

We can go ahead to create doughnut charts by cutting out a circular area beginning from the center, within our pie chart.

Summary

We have gone through the process of creating two custom charts using just react.js and CSS. From creating a pie chart using CSS, then making it dynamic using react and repeating the same with a triangular chart. This can come in handy in projects where we are doing minimal data visualization.

Cheers!