[Solved] How to draw "Bar Chart" using HighCharts and MongoDB data reactively?

I’m facing problem to pass on data to bar chart from mongodb.
It will be great if someone pointed me to the tutorial for rendering bar chart using data from mongodb collections.(Not hard coded data).

I am building following analytics dashboard in meteor for one of my client.

Whenever, a user enters “Keyword” in search box, all charts from Block 2,3,4 and map needs to be updated.

Plus, Block 1 returns “count” of those keywords.

I was able to render block 4 “Pie Chart”, But Pie chart does not render chart unless I refresh window. I want to redraw chart after keyword is changed.

Also I’m struggling hard to render “Age Distribution” (Block 2) and Trend (Block 3) Charts. I know I needed to use aggregation package from arunoda. But not sure that how to use that data to render, once data is available after aggregation.

I know @jhuenges and @robfallows are master of it. Can you please help me on of this?

I’m using maazalik:highcharts package.

PS: Extremely Sorry, I needed to blur the text because of my client’s privacy concerns.

In order to reactively update the chart you need to do two things:

  1. Set up an autorun on the template instance which tracks changes to the data.
  2. Pass a correctly built array of data to your Highcharts object.

If you are using changes to a keyword to reactively select data for presentation, then you will also need to accommodate this within your codebase.

The example below assumes that you use a Session variable to store the current keyword and you pass the keyword as a subscription parameter. If you’re not doing those things, the gist below will not work!

Set up onRendered

Template.barChart.onRendered(function() {
  var self = this;
  var chart = $('#chart-id').highcharts({... bar chart options go here ...}).highcharts();
  self.autorun(function() { // this will always run once and then every time 'keyword' changes.
    var keyword = Session.get('keyword');
    self.subscribe('myCollection', keyword, function() { // subscribe to our data, passing in keyword
      // the subscription is ready here, so go ahead and get the data into a suitable
      // form for Highcharts - we need to build an array of data points.
      var myData = myCollection.find().fetch().map(function(doc) {
        return doc.someValueIwantToPlot;
      });
      chart.series[0].setData(myData); // tell Highcharts to use this data for the (first) series
    });
  });
});

Note that you will also need to ensure that the series name is properly set. For example, the Highcharts column chart options could include an empty series entry:

series: {
  name: 'Sales',
  data: []
}

or you could use chart.addSeries instead of setData (the first time through, anyway).

3 Likes

@robfallows gave a beautiful example. I have nothing to add :slight_smile: Nevertheless if you have more questions I ll try to answer them.

1 Like

@robfallows This is indeed an amazing example and was super quick! Actually when I woke up today, I tried it and voila chart was rendering whenever session variable changes. Thanks a ton for this !! :grinning:

As mentioned in thread earlier… I’m using meteorhacks:aggregate package for aggregation.

Here is my sample aggregation query:

Meteor.publish(‘testPublish’, function(sym){
var pipeline = [
{
$match: {symptoms: sym}
},
{
$group: { _id: “$age”, total : { $sum : 1 } }
},
{
$out : “Age”
}
];
var result = collectionName.aggregate(pipeline);
});

Result of above query is generally in below format (When $out is NOT USED)

[
{ _id: 50, total: 16 },
{ _id: 65, total: 5 },
{ _id: 35, total: 10 },
{ _id: 23, total: 12 }
]

Problem : I am facing problem to pass above result data format to my column chart. So, I used $out to write result in new collection. But I cannot see any object in collection named as Age? In console I can see output as “object” but I’m not able to access using query like Age.findOne({ _id: 50}).total;

@jhuenges @robfallows Well I’m trying to pass aggregation result data to Column chart. If you feel there’s some more easy way to to render data to chart, please do let me know.

Thanks in advance for help! :grinning:

If I had to guess, you are not able to use meteorhacks:aggregate in Meteor.publish() (see here). Can you give us a schema of you collection? Maybe I can help with getting the correct data.

Hi @jhuenges,

Schema for this particular app is as below:

{
"symptoms": ["Headache", "Vomiting"],
    "gender": "male",
    "age": 50,
    "latitude": 19.9944007,
    "longitude": 73.7695864,
    "date": "12-05-2015",
    "time": "12:29",
    "doctor id": "1234" 
}

And I want to draw column chart based on age for selected symptoms. Like how many people are affected by headache? And show data classified according to age. For Ex. In simple words I need to count that how many people with age: 50 have headache as a symptom?

I was not aware that I cannot use aggregate package in publish! :sweat:

my AgeColumn file is below format in

function buildAge() {

    $('#container-column').highcharts({
        
        chart: {
            type: 'column'
        },
        
        title: {
            text: 'Age Distribution'
        },
        
        subtitle: {
            text: ''
        },
        
        credits: {
            enabled: false
        },
        
        xAxis: {
            categories: [
                '0-2',
                '2-15',
                '16-25',
                '26-40',
                '41-60',
                '61 +'
            ]
        },
        
        yAxis: {
            min: 0,
            title: {
                text: ''
            }
        },
        
        tooltip: {
            headerFormat: '<span style="font-size:10px">{point.key}</span><table>',
            pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
                '<td style="padding:0"><b>{point.y:.1f}</b></td></tr>',
            footerFormat: '</table>',
            shared: true,
            useHTML: true
        },
        
        plotOptions: {
            column: {
                pointPadding: 0,
                borderWidth: 0,
                showInLegend: false
            }
        },
        
        series: [{
            name: 'No. of Patients',
            data: [49, 71, 294, 316, 176, 101]

        }]
    });
}

Template.agecolumn.onRendered(function() {    
     buildAge();
 });

hang on i am writing the solution

@piyush I didn’t get it. What had you done?

What i have done for my client dashboard is something like this,
`//in the template onCreated Method

Template.barchart.onCreated(function(){
      var instance=this;
       this.chartData=new ReactiveVar([]);
      this.subscribe(' letsSubscribeSomeData');

     charsData.find({....}).observeChanges({
         added:function(id, doc){
           var tempChart=instance.chartsData.get();
           tempChart.push(doc.someValue);
           instance.chartsData.set(tempChart);
 },
....wanna update try adding update callback
  });

  //fine we have made a method that automatically handles the value i mean the reactive 
 // now lets load the value to the chart
 Template.barchart.onRendered(function(){
   self=this;
   // lets rerun the function once the data to the chart changes
   this.autorun(function(){
       var chartData=self.instance().chartData.get();
      // init the chart to the dom with the chartData as a data source
     // is based on the library you are using
   }

})
1 Like

@piyush Thanks for above example! but at point of

I’m subscribing to data from aggregate function. Using meteorhacks:aggregate package. But problem is that it is not returning cursor of data, but object. I’m not able to pass on that object to column data series.

Have you used $out earlier? Does it work?

yeah but unfortunately the when you use Aggregation it will not be reactive i guess

Yes it’s not going to be reactive, and also I’m not concerned about reactiveness as of now. I just want to display output of my aggregation pipeline on above column chart.

then just set to the instance variable chartsData to the object its returning and will work… just filter the object that was returned from aggregate.

I am close to having a solution, give me some more minutes :wink:

1 Like

On reactivity part, we can have one solution that $out operation writes data completely to new collection. And I remember reading that, It overwrites the previous results. So, if we rerun aggregation query using setInterval, we can fetch updated data from $out collection…making the setup reactive. I think @joshowens did the same thing in this post.

then the problem is solved just change the reactiveVariable with certain interval then it automatically re plots the graph
code inside onCreated Callback
`

   setInterval(function(){
       var chartData=// fetch the chartData form server
       // you can also filter the data here
       instance.chartData.set(chartData);
      
  },duration);

`

1 Like

http://meteorpad.com/pad/iNfXQwMW3RwrokTe8/Chart

with reactivity. Of course some function can be simplified

3 Likes

@jhuenges Simply WoW… this is absolutely amazing. No need of complex aggregation stuff!! Thank you very much! :smiley: Extremely helpful!! :relaxed: I will sleep peacefully today, Thanks to you man for your efforts!! :thumbsup:

1 Like

I missed all the fun! That’ll teach me to sleep at night :wink:

Nice job @jhuenges :smile:

1 Like