Display API result data on highcharts

I have a page which gets data from a users YouTube channel (views) using the Google API and then I am trying to display this data on a chart using Highcharts (line chart). The user can also use a date picker and be able to change the time period for the data that is shown (Example: Last 30 Days, Last week, Last Month, etc.) and I would like to have the chart automatically update once a new time period is selected. I’m guessing the best way for this is through ReactiveVars but I’m not having much luck making it work. My API call seems to be working fine, so now I need to have the output display on my chart.

Here is an example screenshot of my console.log for what my api result looks like (in the rows, each array is a day, 0 is the date and 1 is the corresponding view count):

Console.log of API result

I would like to have the date data appear along the bottom xAxis of the chart, and then have the view count along the yAxis. Here is an example of what my chart is looking like with the code below (with random example data):

Chart w/ example data & date selector

Here is my code for the chart/date picker/api call:

Template.apps.onCreated(function() {
  var self = this;
  apiStartDate = new ReactiveVar(moment().subtract(29, 'days').format('YYYY-MM-DD'));
  apiEndDate = new ReactiveVar(moment().format('YYYY-MM-DD'));
    this.autorun(function(){
	// View API Call
    GoogleApi.get('youtube/analytics/v1/reports', { params: { 'end-date': apiEndDate.get(), metrics: "views", ids: "channel==MINE", 'start-date': apiStartDate.get(), metrics: "views", dimensions: "day", sort: "-day"}}, function(error, result) {
      console.log(result);
});
  });
});

// Views Chart

Template.apps.helpers({
  viewsChart: function() {
    return {
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false,
            marginBottom: 50,
            spacingBottom: 40
        },
        title: {
            text: this.username + "'s views"
        },
        xAxis: {
            categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        },
        yAxis: {
            title: {
                text: 'Views'
            },
            plotLines: [{
                value: 0,
                width: 1,
                color: '#808080'
            }]
        },
        legend: {
            enabled: true,
            floating: true,
            verticalAlign: 'bottom',
            align:'center',
            y:40
        },
        series: [{
            name: 'Channel Name',
            data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8]
        }]
    };
  },
});

// Date Picker

Template.apps.rendered = function(){

    $('#reportrange span').html(moment().subtract(29, 'days').format('YYYY-MM-DD') + ' - ' + moment().format('YYYY-MM-DD'));

    $('#reportrange').daterangepicker({
        format: 'YYYY-MM-DD',
        startDate: moment().subtract(29, 'days'),
        endDate: moment(),
        showDropdowns: true,
        showWeekNumbers: true,
        timePicker: false,
        timePickerIncrement: 1,
        timePicker12Hour: true,
        ranges: {
            'Today': [moment(), moment()],
            'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
            'Last 7 Days': [moment().subtract(6, 'days'), moment()],
            'Last 30 Days': [moment().subtract(29, 'days'), moment()],
            'This Month': [moment().startOf('month'), moment().endOf('month')],
            'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
        },
        opens: 'left',
        drops: 'down',
        buttonClasses: ['btn', 'btn-sm'],
        applyClass: 'btn-primary',
        cancelClass: 'btn-default',
        separator: ' to ',
        locale: {
            applyLabel: 'Submit',
            cancelLabel: 'Cancel',
            fromLabel: 'From',
            toLabel: 'To',
            customRangeLabel: 'Custom',
            daysOfWeek: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr','Sa'],
            monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            firstDay: 1
        }
    }, function(start, end, label) {
    	// Start/End date - reactive vars to set 'start-date' & 'end-date' API Call params.
        apiStartDate.set(start.format('YYYY-MM-DD'));
        apiEndDate.set(end.format('YYYY-MM-DD'));
    });
};

I’ve been trying to create Reactive Vars for this but everything I seem to try isn’t working, anyone know how I can set this up? Fairly new to Meteor but learning lots and would greatly appreciate any help.

You mentioned you want to have the chart update once the date range has changed, but I don’t see anywhere in your code where you’re wiring the results of your API request up to be rendered in your template. Right now the viewsChart helper is returning static data, so when the date range changes the resulting data won’t change. You’ll need to wire this up so the results of your API call are wired up to show in your template. Unless of course this is just sample code; if so please post real code to help us troubleshoot.

A few extra things to note:

  • In your onCreated lifecycle method you should keep your ReactiveVar's associated with the template by using this.apiStartDate and this.apiEndDate.
  • You’re using the older Template.apps.rendered = function () { approach (which has been deprecated); you should consider moving to the newer Template.apps.onRendered(function () { approach.
1 Like

@hwillson Thanks for the reply!

Yes the content in the chart right now is simply static example data, I did not have any code included to wire up the result of my API to attach it to the chart, this is sort of the main thing I’ve been stuck at and trying to figure out. I’m not exactly sure how to grab the result in the proper format and then implement it to my chart. I’m thinking doing this with reactive vars would be the best way?

For example, I’ve tried adding a new reactive var called “apiResult” (shown below) but this doesn’t seem to grab the data if I try and console.log it with: console.log(that.apiResult.get());

Template.apps.onCreated(function() {
  var self = this;
  self.apiStartDate = new ReactiveVar(moment().subtract(29, 'days').format('YYYY-MM-DD'));
  self.apiEndDate = new ReactiveVar(moment().format('YYYY-MM-DD'));
  self.apiResult = new ReactiveVar();
  var that = this;
    this.autorun(function(){
      // API Call
    GoogleApi.get('youtube/analytics/v1/reports', { params: { 'end-date': that.apiEndDate.get(), metrics: "views", ids: "channel==MINE", 'start-date': that.apiStartDate.get(), metrics: "views", dimensions: "day", sort: "-day"}}, function(error, result) {
      // Set API result to reactive var to be connected to the chart
      that.apiResult.set(result);
      console.log(result);
});
  });
});

I appreciate the extra notes as well, I’ve made those changes as shown below:

Template.apps.onCreated(function() {
  var self = this;
  self.apiStartDate = new ReactiveVar(moment().subtract(29, 'days').format('YYYY-MM-DD'));
  self.apiEndDate = new ReactiveVar(moment().format('YYYY-MM-DD'));
  var that = this;
    this.autorun(function(){
	// View API Call
    GoogleApi.get('youtube/analytics/v1/reports', { params: { 'end-date': that.apiEndDate.get(), metrics: "views", ids: "channel==MINE", 'start-date': that.apiStartDate.get(), metrics: "views", dimensions: "day", sort: "-day"}}, function(error, result) {
      console.log(result);
});
  });
});

// Views Chart

Template.apps.helpers({
  viewsChart: function() {
    return {
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false,
            marginBottom: 50,
            spacingBottom: 40
        },
        title: {
            text: this.username + "'s views"
        },
        xAxis: {
            categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        },
        yAxis: {
            title: {
                text: 'Views'
            },
            plotLines: [{
                value: 0,
                width: 1,
                color: '#808080'
            }]
        },
        legend: {
            enabled: true,
            floating: true,
            verticalAlign: 'bottom',
            align:'center',
            y:40
        },
        series: [{
            name: 'Channel Name',
            data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8]
        }]
    };
  },
});

// Date Picker

Template.apps.onRendered(function () {
    var template = this;

    $('#reportrange span').html(moment().subtract(29, 'days').format('YYYY-MM-DD') + ' - ' + moment().format('YYYY-MM-DD'));

    $('#reportrange').daterangepicker({
        format: 'YYYY-MM-DD',
        startDate: moment().subtract(29, 'days'),
        endDate: moment(),
        showDropdowns: true,
        showWeekNumbers: true,
        timePicker: false,
        timePickerIncrement: 1,
        timePicker12Hour: true,
        ranges: {
            'Today': [moment(), moment()],
            'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
            'Last 7 Days': [moment().subtract(6, 'days'), moment()],
            'Last 30 Days': [moment().subtract(29, 'days'), moment()],
            'This Month': [moment().startOf('month'), moment().endOf('month')],
            'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
        },
        opens: 'left',
        drops: 'down',
        buttonClasses: ['btn', 'btn-sm'],
        applyClass: 'btn-primary',
        cancelClass: 'btn-default',
        separator: ' to ',
        locale: {
            applyLabel: 'Submit',
            cancelLabel: 'Cancel',
            fromLabel: 'From',
            toLabel: 'To',
            customRangeLabel: 'Custom',
            daysOfWeek: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr','Sa'],
            monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            firstDay: 1
        }
    }, function(start, end, label) {
    	// Start/End date - reactive vars to set 'start-date' & 'end-date' API Call params.
        template.apiStartDate.set(start.format('YYYY-MM-DD'));
        template.apiEndDate.set(end.format('YYYY-MM-DD'));
    });
});

Been quite lost with this part of my project and stuck on it for quite a long time so any help is greatly appreciated!

Yes using ReactiveVar's for the output makes sense. Regarding not getting results back from your API call, I can’t see how you’ve configured access to Google. You should make sure you’ve given the Google API access to the proper scope (in your case https://www.googleapis.com/auth/yt-analytics.readonly). Here’s the code from a really quick example I’ve thrown together that shows how to use the Google API via percolate:google-api, along with accounts-google and service-configuration (note: this is a manual example to show the config; using accounts-ui for example makes configuring this a bit easier):

if (Meteor.isClient) {
  Template.body.onCreated(function () {

    this.autorun(() => {
      if (Accounts.loginServicesConfigured()) {
        Meteor.loginWithGoogle({
          requestPermissions: [
            'https://www.googleapis.com/auth/yt-analytics.readonly'
          ]
        }, () => {
          GoogleApi.get('youtube/analytics/v1/reports', {
            params: {
              ids: 'channel==MINE',
              'start-date': '2016-01-01',
              'end-date': '2016-02-01',
              metrics: 'views'
            }
          }, (error, result) => {
            console.log(error);
            console.log(result);
          });
        });
      }
    });

  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // Get connection details from https://console.cloud.google.com.
    ServiceConfiguration.configurations.upsert(
      { service: 'google' },
      {
        $set: {
          clientId: 'XXX',
          secret: 'XXX'
        }
      }
    );
  });
}

You can get the running app here (just add your credentials in the google-api.js file).

1 Like

Doesn’t he implement a reactive example at the bottom of the page?

http://highcharts-demo.meteor.com/

and provide the source code here

@hwillson Thanks for the reply!

My setup for the API call seems to be working good, I’m getting the proper results with that (as shown in this screenshot from my first post):

So it’s not an issue with the API call setup, where I’m having issues is figuring out how to setup the reactive var to get the result of the api call and then implement that into my chart so it appears on there.

Once you have your results from the API call, add them to the ReactiveVar you want to use, then reference this ReactiveVar when rendering your chart. Here’s another really quick (and contrived) example showing how you can use ReactiveVar's with Highcharts (this just displays static data, then refreshes after 5 seconds with new data). You could leverage something like this with the results from your API call instead.

if (Meteor.isClient) {

  Template.body.onCreated(function () {
    const instance = Template.instance();
    instance.chartData = new ReactiveVar([
      30.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8
    ]);
    Meteor.setTimeout(function () {
      instance.chartData.set([
        4.9, 3.2, 9.7, 12.5, 17.9, 5.2, 12.0, 11.6, 12.2, 9.3, 3.6, 2.8
      ]);
    }, 5000);
  });

  Template.body.onRendered(function () {

    this.autorun(function () {
      Highcharts.chart('chart-container', {
        chart: {
          plotBackgroundColor: null,
          plotBorderWidth: null,
          plotShadow: false,
          marginBottom: 50,
          spacingBottom: 40
        },
        title: {
          text: "My views"
        },
        xAxis: {
          categories: [
            'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
            'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
          ]
        },
        yAxis: {
          title: {
            text: 'Views'
          },
          plotLines: [{
            value: 0,
            width: 1,
            color: '#808080'
          }]
        },
        legend: {
          enabled: true,
          floating: true,
          verticalAlign: 'bottom',
          align:'center',
          y:40
        },
        series: [{
          name: 'Channel Name',
          data: Template.instance().chartData.get()
        }]
      });
    });

  });

}

(working app here)

2 Likes

There seems to be some overhead if you create/draw the chart in the autorun. Dont you think?

The intent of the quick example was to show how ReactiveVar's work and can feed a Highchart - it hasn’t been performance / memory tested. That being said using autorun doesn’t necessarily introduce a lot of overhead (it really depends on how frequently the run function’s dependencies change).

If you want to avoid the re-creation of the chart, look into using Highchart’s API instead (see examples like this one - step 3).

1 Like

@hwillson Thanks for the help, that example helped me a lot and got me in the right direction. Very much appreciated!