Dynamic Chart.js inline charts

Code

December 27, 2019

If you need to add a chart to your layout, you can do this with a bit of javascript. This is much more convenient than adding a screenshot, because you can add and update data without having to go through the whole process of exporting, uploading and updating the image. If you discover an error in your dataset you simple change the value on the backend, and voilá! you’re done!

Here’s how you can learn how to add dynamic data!

We’ll be using a derivative of the introduction example on the chart.js docs page.

Because in Beaver Builder the module is constantly rendered, this will trigger errors in the javascript that is generated using our template. This will inevitably render our editor useless at some point.

A good workaround for this is creating the Twig template on your dashboard as a post in the CPT, remember the slug, and include the template as the Timber Posts Modules content. That way, each time you edit and save the template and subsequently refresh the page containing that module, errors could still be there but won’t affect your work.

Creating the dataset

Charts are all about data, so let’s start with ours. We are going to create a chart that will display the #votes for our four teams: Red, Blue, Yellow and Purple. We are going to use an array here, but if you use ACF you can also add a repeater that return this data.

{%
	set votes_per_color = [
						{ 
							'bar_color' 		: '#1D73BE',
							'bar_label' 		: 'Blue',
							'number_of_votes' 	: 12
						},
						{ 
							'bar_color' 		: '##DD3333',
							'bar_label' 		: 'Red',
							'number_of_votes' 	: 19
						},
						{ 
							'bar_color' 		: '##EFEE22',
							'bar_label' 		: 'Yellow',
							'number_of_votes' 	: 3
						},
						{ 
							'bar_color' 		: '##8124E4',
							'bar_label' 		: 'Purple',
							'number_of_votes' 	: 9
						}

	]

%}

The variable that we just set is an unkeyed array of keyed data. The parent array is unkeyed because we only need to loop over it. The child arrays are keyed so we can call the data using it’s key.

Arrays in Twig

If you haven’t noticed before, Twig uses two different notations for an array (as opposed to PHP):

{% set data = [ 'one' , 'two' , 'three' ] %} is an array of items WITHOUT a key-pair (square brackets).
{% set data = { 'name' : 'Graham', 'age' : 25 , 'occupation' : 'designer' } %} is an array WITH key-pairs (curly brackets).

It’s good to know they can be mixed, but be beware that once you start a key-paired array, each item in the array needs to be keyed.

Adding a unique ID for our chart

Chart.is uses a <canvas> element to draw the chart. We will need to give the canvas DOM-element a unique ID. For this, we will use a variable that is added to each Timber Posts Module at runtime:

module.id

This variable is also used by Beaver Builder to identify the module when editing and rendering. The id is unique throughout your layout.

<canvas id="myChart{{module.id}}" width="400" height="400"></canvas>

Adding the javascript

The last part is adding the javascript, needed to render the chart on the canvas element. We will be using a mix with jQuery to load the Chart.js-library needed and when that’s done, have it draw the chart!

<script>
  jQuery.getScript( 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js' ).done(
    function() {
      var ctx{{module.id}} = document.getElementById('myChart{{module.id}}');
      var myChart{{module.id}} = new Chart(ctx{{module.id}}, {
           type: 'bar',
           data: {
             labels: [
               {% for item in votes_per_color %}'{{item.bar_label}}'{{(not loop.last)?','}}{% endfor %}
             ],
           datasets: [{
             label: '# of Votes',
             data: [
               {% for item in votes_per_color %}{{ item.number_of_votes }}{{ (not loop.last)?',' }}{% endfor %}
             ],
             backgroundColor: [
               {% for item in votes_per_color %}'{{ item.bar_color }}'{{ (not loop.last)?',' }}{% endfor %}
             ],
             borderColor: [
               {% for item in votes_per_color %}'{{ item.bar_color }}'{{ (not loop.last)?',' }}{% endfor %}
             ],
             borderWidth: 1
           }]
         },
         options: {
         scales: {
         yAxes: [{
           ticks: {
             beginAtZero: true
           }
         }]
       }
     }
    });
    }
  );
</script>

As you can see, jQuery.getScript() loads the script dynamically (so you don’t need to enqueue it) and when done, we can execute some code wrapped in an anonymous function.

Using the same unique {{module.id}} identifier for the various bits and pieces we make sure everything will execute correctly.

Returning the data as javascript

Lastly, let’s focus on the way we return the data needed for the labels. We do the same for the datasets, backgroundColor and borderColor with the same mechanism.

Let’s start with the desired end-result first:

{#
     desired endresult for the labels
#}
             labels: [
               'Red','Blue','Yellow','Purple'
             ],

We will need to loop over our teams, returning each team-label wrapped in quotes, seperating them with a comma.

Keep in mind that we are rendering javascript using Twig here, so whatever gets rendered needs to be valid javascript.

An easy way of doing this is:

             labels: [
               {% for item in votes_per_color %}'{{ item.bar_label }}'{{ (not loop.last)?',' }}{% endfor %}
             ],

The {% for item in votes_per_color %} {% endfor %} part is easy enough to spot. Also, notice the ‘ before and after the {{ item.bar_label }}.

Using the ternary operator

But the tricky part is the comma that we want to render on all but last iteration of the loop. For this we use the loop variable that is added by Twig as long as we are within the for .. endfor scope, so we can test if we are on the last iteration.

Here we use a ternary operator, which shortens if..else..endif a lot.

{{ (not loop.last)?',' }} means: “if we are NOT in last iteration of the loop, return ‘,’ and if we are (in the last iteration), return nothing”

If you look at the finished template below, you see we use this method multiple times, to get either the item.bar_label, item.bar_color (twice, for both backgroundColor and borderColor) or item.number_of_votes.

{#
    finished template
#}
{%
	set votes_per_color = [
						{ 
							'bar_color' 		: '#1D73BE',
							'bar_label' 		: 'Blue',
							'number_of_votes' 	: 12
						},
						{ 
							'bar_color' 		: '##DD3333',
							'bar_label' 		: 'Red',
							'number_of_votes' 	: 19
						},
						{ 
							'bar_color' 		: '##EFEE22',
							'bar_label' 		: 'Yellow',
							'number_of_votes' 	: 3
						},
						{ 
							'bar_color' 		: '##8124E4',
							'bar_label' 		: 'Purple',
							'number_of_votes' 	: 9
						}

	]

%}
<canvas id="myChart{{module.id}}" width="400" height="400"></canvas>
<script>
  jQuery.getScript( 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js' ).done(
    function() {
      var ctx{{module.id}} = document.getElementById('myChart{{module.id}}');
      var myChart{{module.id}} = new Chart(ctx{{module.id}}, {
           type: 'bar',
           data: {
             labels: [
               {% for item in votes_per_color %}'{{item.bar_label}}'{{(not loop.last)?','}}{% endfor %}
             ],
           datasets: [{
             label: '# of Votes',
             data: [
               {% for item in votes_per_color %}{{ item.number_of_votes }}{{ (not loop.last)?',' }}{% endfor %}
             ],
             backgroundColor: [
               {% for item in votes_per_color %}'{{ item.bar_color }}'{{ (not loop.last)?',' }}{% endfor %}
             ],
             borderColor: [
               {% for item in votes_per_color %}'{{ item.bar_color }}'{{ (not loop.last)?',' }}{% endfor %}
             ],
             borderWidth: 1
           }]
         },
         options: {
         scales: {
         yAxes: [{
           ticks: {
             beginAtZero: true
           }
         }]
       }
     }
    });
    }
  );
</script>

Beaverplugins

Web ninja with PHP/CSS/JS and Wordpress skills. Also stand-in server administrator, father of four kids and husband to a beautiful wife.
Always spends too much time figuring out ways to do simple things even quicker. So that you can benefit.