Navigate back to the homepage

Visualize Data Extension Rows With Chart.js

Jason Hanshaw
June 11th, 2020 · 4 min read

When developing for Salesforce Marketing Cloud, a common request that you might get from clients (or fellow developers) is a solution to visualize data that isn’t easily represented with the native functionality provided by Marketing Cloud. Whether you want to impress your business department with sleek engagement charts or create an interactive user-experience for your customers, having the capability to display aggregated data in an understandable way is important.

In this blog post, I’ll highlight one such method using CloudPages, Code Resources and the Chart.js Javascript library.

Setup:

Obviously before we can display our data in a meaningful way, we’ll need to gather and place it into a coherent structure. For this example, we’re going to create a data extension with some simple engagement metrics for the last five days, and then populate that table with some random values for demonstration. Our data extension will look something like this:

ColumnDataType
eventDateDate
sentNumber
openNumber
clickNumber
bounceNumber

While this post will cover the implementation and formatting necessary for displaying our data extension values with Chart.js, it will not serve as a comprehensive guide on chart customization available with that library. For more information on this subject, please visit the documentation link below.

Chart.js Introduction

Create JSON Code Resource:

Next, we’ll create a JSON Code Resource that will serve as the back-end for our implementation. All that we need to do is to initialize our data extension, using the customerKey, and then output the results in a JSON format for use within our example.

1<script runat=server>
2Platform.Load("Core", "1");
3// User Filter For Limited Response Size Or Pagination
4var data = DataExtension.Init("Simple Engagement Table").Rows.Retrieve(filter);
5Write(Stringify(data));
6</script>

Create CloudPage For Displaying Chart:

Then, we’ll need to create a CloudPage that will display the actual Chart.js output that we want to show our users. To do this, we’ll add some HTML that will provide the following:

  1. A helpful title and subheader to describe the data we are displaying
  2. A series of buttons that we can toggle to show different types of data based on user-selection
  3. The canvas element that will render our chart data

Let’s take a look and see what that markup looks like:

1<div class="wrapper">
2 <div class="main-panel">
3 <div class="content">
4 <div class="row">
5 <div class="col-12">
6 <div class="card card-chart">
7 <div class="card-header ">
8 <div class="row">
9 <div class="col-sm-6 text-left">
10 <h5 class="card-category">EMAIL</h5>
11 <h2 class="card-title">Performance</h2>
12 </div>
13 <div class="col-sm-6">
14 <div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
15 <label class="btn btn-sm btn-primary btn-simple active" id="0">
16 <input type="radio" name="options" checked>
17 <span class="d-none d-sm-block d-md-block d-lg-block d-xl-block">Sent</span>
18 <span class="d-block d-sm-none">
19 <i class="tim-icons icon-single-02"></i>
20 </span>
21 </label>
22 <label class="btn btn-sm btn-primary btn-simple" id="1">
23 <input type="radio" class="d-none d-sm-none" name="options">
24 <span class="d-none d-sm-block d-md-block d-lg-block d-xl-block">Opens</span>
25 <span class="d-block d-sm-none">
26 <i class="tim-icons icon-gift-2"></i>
27 </span>
28 </label>
29 <label class="btn btn-sm btn-primary btn-simple" id="2">
30 <input type="radio" class="d-none" name="options">
31 <span class="d-none d-sm-block d-md-block d-lg-block d-xl-block">Clicks</span>
32 <span class="d-block d-sm-none">
33 <i class="tim-icons icon-tap-02"></i>
34 </span>
35 </label>
36 <label class="btn btn-sm btn-primary btn-simple" id="3">
37 <input type="radio" class="d-none" name="options">
38 <span class="d-none d-sm-block d-md-block d-lg-block d-xl-block">Bounces</span>
39 <span class="d-block d-sm-none">
40 <i class="tim-icons icon-tap-02"></i>
41 </span>
42 </label>
43 </div>
44 </div>
45 </div>
46 </div>
47 <div class="card-body">
48 <div class="chart-area">
49 <canvas id="email_stats"></canvas>
50 </div>
51 </div>
52 </div>
53 </div>
54 </div>
55 </div>
56 </div>
57</div>

As you can see, we’re using a card-based layout that specifies our chart type as Email Performance. We’re also using radio inputs, that we’ll style as buttons, in order to offer our users the ability to alter chart data based on the engagement types present in our data extension. Note that each radio input is assigned a unique Id, which is what we’ll key off of in our script to determine selection.

Finally, we’ll declare our canvas node and assign it a unique Id value that we can use to bind our chart to that element.

Formatting Data Extension Data:

With our markup established, and our Code Resource page serving our Data Extension data, we’re ready to format our JSON and prepare it for display. To begin, let’s make an asynchronous call to our Code Resource using the getJSON() Jquery method. For the point of demonstration, we’ll forgo a promise-based approach or error-handling and make this a dead-simple implementation:

1$.getJSON("YOUR JSON RESOURCE PAGE", function(json) {
2
3});

Now that we’ve got a method for bringing in our JSON data, let’s do some simple date formatting of our eventDate values for presentation and then assign these to the extended_timeline variable which will be the x-axis of our chart implementation.

1$.getJSON("YOUR JSON RESOURCE PAGE", function(json) {
2 daily_obj_arr = json;
3 var extended_timeline = [];
4
5 for (var j = 0; j < daily_obj_arr.length; ++j) {
6 var getDate = daily_obj_arr[j].eventDate;
7 var formatter = new Intl.DateTimeFormat("en", {
8 month: "short",
9 day: "numeric"
10 }),
11 newDate = formatter.format(new Date(getDate));
12 extended_timeline.push(newDate.toUpperCase());
13});

We can then use the map() method to isolate our data into individual arrays based on the engagement type so that we can give our users the ability to select between them.

1$.getJSON("YOUR JSON RESOURCE PAGE", function(json) {
2 daily_obj_arr = json;
3 var extended_timeline = [];
4
5 for (var j = 0; j < daily_obj_arr.length; ++j) {
6 var getDate = daily_obj_arr[j].eventDate;
7 var formatter = new Intl.DateTimeFormat("en", {
8 month: "short",
9 day: "numeric"
10 }),
11 newDate = formatter.format(new Date(getDate));
12 extended_timeline.push(newDate.toUpperCase());
13
14 var sent = daily_obj_arr.map(function(a) {
15 return parseInt(a.sent);
16 });
17 var clicks = daily_obj_arr.map(function(a) {
18 return parseInt(a.click);
19 });
20 var opens = daily_obj_arr.map(function(a) {
21 return parseInt(a.open);
22 });
23 var bounces = daily_obj_arr.map(function(a) {
24 return parseInt(a.bounce);
25 });
26});

Our data is now bucketed and formatted correctly for use within our Chart.js implementation. Let’s move on to the final piece of this post, which will deal with the actual formatting and creation of the chart.

Chart Configuration:

For our chart configuration, there are a few functions that we’ll need to setup in order for our data to display and to be configurable by the end-user. First, let’s write a simple function that will selectively highlight which engagement type the user has selected from our radio inputs in our markup.

1initPickColor: function() {
2 $('.pick-class-label').click(function() {
3 var new_class = $(this).attr('new-class');
4 var old_class = $('#display-buttons').attr('data-class');
5 var display_div = $('#display-buttons');
6 if (display_div.length) {
7 var display_buttons = display_div.find('.btn');
8 display_buttons.removeClass(old_class);
9 display_buttons.addClass(new_class);
10 display_div.attr('data-class', new_class);
11 }
12 });
13}

Now comes the important piece of actually configuring what type of chart we will show, how the data should be mapped and any styling options that we’ll want to apply to ensure out chart is visually appealing as well as functional.

First, let’s go ahead and create a function that will house our configuration and initialize some styling options for our chart.

1initDashboardPageCharts: function() {
2 gradientChartOptionsConfigurationWithTooltipPurple = {
3 maintainAspectRatio: false,
4 legend: {
5 display: false
6 },
7 tooltips: {
8 backgroundColor: '#f5f5f5',
9 titleFontColor: '#333',
10 bodyFontColor: '#666',
11 bodySpacing: 4,
12 xPadding: 12,
13 mode: "nearest",
14 intersect: 0,
15 position: "nearest"
16 },
17 responsive: true,
18 scales: {
19 yAxes: [{
20 barPercentage: 1.6,
21 gridLines: {
22 drawBorder: false,
23 color: 'rgba(29,140,248,0.0)',
24 zeroLineColor: "transparent",
25 },
26 ticks: {
27 suggestedMin: 60,
28 suggestedMax: 125,
29 padding: 20,
30 fontColor: "#9a9a9a"
31 }
32 }],
33 xAxes: [{
34 barPercentage: 1.6,
35 gridLines: {
36 drawBorder: false,
37 color: 'rgba(225,78,202,0.1)',
38 zeroLineColor: "transparent",
39 },
40 ticks: {
41 padding: 20,
42 fontColor: "#9a9a9a"
43 }
44 }]
45 }
46 };
47 var chart_labels = extended_timeline;
48 var chart_data = sent;
49 var ctx = document.getElementById("email_stats").getContext('2d');
50 var gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
51 gradientStroke.addColorStop(1, 'rgba(72,72,176,0.1)');
52 gradientStroke.addColorStop(0.4, 'rgba(108,102,251,0.3)');
53 gradientStroke.addColorStop(0, 'rgba(119,52,169,0)'); //purple colors
54};

As you can see, we’re using the object gradientChartOptionsConfigurationWithTooltipPurple to provide some basic items that will make our chart responsive, hide the legend (in favor of our selectable radio input approach), format spacing and styles, etc… Next we’ll initialize our variables used for our x-axis timeseries and selected label. Then we’ll instantiate our chart and bind it to the email_stats Id that we specified on our canvas element in our markup. Finally, we’ll add a gradientStroke to our chart data for additional styling.

With our chart instantiated, and our base-styling configurations set, we’ll now move towards implementing the config object for our chart that will specify the key items.

1var config = {
2 type: 'line',
3 data: {
4 labels: chart_labels,
5 datasets: [{
6 label: "Sent",
7 fill: true,
8 backgroundColor: gradientStroke,
9 borderColor: '#6C66FB',
10 borderWidth: 2,
11 borderDash: [],
12 borderDashOffset: 0.0,
13 pointBackgroundColor: '#6C66FB',
14 pointBorderColor: 'rgba(255,255,255,0)',
15 pointHoverBackgroundColor: '#6C66FB',
16 pointBorderWidth: 20,
17 pointHoverRadius: 4,
18 pointHoverBorderWidth: 15,
19 pointRadius: 4,
20 data: chart_data,
21 }]
22 },
23 options: gradientChartOptionsConfigurationWithTooltipPurple,
24 scales: {
25 yAxes: [{
26 ticks: {
27 min: Math.min.apply(this, chart_data),
28 max: Math.max.apply(this, chart_data),
29 stepSize: 5
30 }
31 }]
32 }
33};

While there are several moving parts to this configuration, let’s highlight the most important ones that impact the basic functionality of our chart.

1type: 'line'

This specifies the type of chart that we want to display on our CloudPage. For a full listing of all the available chart types, visit the Chart.js documentation page linked earlier in this post.

1data: chart_data

This configuration tells Chart.js what data we would like to display on our chart. Note in the implementation above, the initial configuration is set to display our sent array that was mapped in our initial formatting of the data extension output. By default, the chart will display this dataset first.

1min: Math.min.apply(this, chart_data),
2max: Math.max.apply(this, chart_data),

This piece sets the range of values for our y-axes. We could declare explicit ranges if we wanted to keep this axis consistent across our engagement types but, for our example, lets set these values to scale with the minimum and maximum values in our data set.

Lastly, we’ll need a function that will update our chart configuration above whenever a user selects a different engagement type. This function should update both the data and label properties of our config object so that the chart responds accordingly. To do this, we’ll key off of the unique Id’s we’ve assigned to our radio inputs in the markup and then re-instantiate our chart with the new data set that maps to that Id selection.

1var myChartData = new Chart(ctx, config);
2$("#0").click(function() {
3 var data = myChartData.config.data;
4 data.datasets[0].data = sent;
5 data.datasets[0].label = "Sent";
6 data.labels = chart_labels;
7 myChartData.update();
8});
9$("#1").click(function() {
10 var chart_data = opens;
11 var data = myChartData.config.data;
12 data.datasets[0].data = chart_data;
13 data.datasets[0].label = "Opens";
14 data.labels = chart_labels;
15 myChartData.update();
16});
17
18$("#2").click(function() {
19 var chart_data = clicks;
20 var data = myChartData.config.data;
21 data.datasets[0].data = chart_data;
22 data.datasets[0].label = "Clicks";
23 data.labels = chart_labels;
24 myChartData.update();
25});
26
27$("#3").click(function() {
28 var chart_data = bounces;
29 var data = myChartData.config.data;
30 data.datasets[0].data = chart_data;
31 data.datasets[0].label = "Bounces";
32 data.labels = chart_labels;
33 myChartData.update();
34});

Conclusion:

chartDemo

That’s it! Now we’ve got a dynamic solution for visualizing data extension data on a CloudPage in a way that’s visually appealing and helpful for our users. Following the above guide, this solution could be easily configured to use different data structures, chart types and custom styles to provide an experience tailored to your specific use-case.

In addition to this blog post, you can find the code for this example in this github repository.

More articles from InvokeCreate

Create A Searchable Content Directory With CloudPages

Use SSJS, VueJS and Code Resources in order to create a searchable index of HTML Content Blocks.

May 10th, 2020 · 3 min read

Search A Data Extension From Sales Cloud

Create a lightning component to search a Send Logging data extension in Marketing Cloud.

February 14th, 2020 · 7 min read
© 2020 InvokeCreate
Link to $https://github.com/invokecreate/Link to $https://www.linkedin.com/in/jason-hanshaw-developer/Link to $https://salesforce.stackexchange.com/users/50722/jason-hanshaw